# tiny **Repository Path**: xnat/tiny ## Basic Information - **Project Name**: tiny - **Description**: 小巧的java应用微内核通用框架, 可用于构建小工具,web,各种大大小小的应用 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 68 - **Forks**: 15 - **Created**: 2020-12-10 - **Last Updated**: 2026-02-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: 异步, 轻量级, 事件驱动, 多线程, 微内核 ## README # 介绍 小巧的java应用微内核框架. 基于事件驱动框架结构 目前大部分高级语言都解决了编程内存自动管理的问题(垃圾回收), 但并没有解决cpu资源自动管理的问题。 基本上都是程序员主动创建线程或线程池,这些线程是否被充分利用了,在应用不同的地方创建是否有必要, 太多的线程是否造成竞争性能损耗。毕竟线程再多,而决定并发的是cpu的个数。 所以需要框架来实现一个智能执行器(线程池):根据应用的忙碌程度自动创建和销毁线程, 即线程池会自己根据排对的任务数和当前池中的线程数的比例判断是否需要新创建线程(和默认线程池的行为不同)。 会catch所有异常, 不会被业务异常给弄死(永动机)。 程序只需通过配置最大最小资源自动适配, 类似现在的数据库连接池。这样既能充分利用线程资源, 也减少线程的空转和线程过多调度的性能浪费,代码不用直接和线程交互。 > 所以系统性能只由线程池大小属性 sys.exec.corePoolSize=8, sys.exec.maximumPoolSize=16 和 jvm内存参数 -Xmx1g 控制 框架设计一种轻量级执行器(伪协程): __XQueue__ 来控制执行模式(并发,暂停/恢复,速度等) 上层服务应该只关注怎么组装执行任务,然后提交给 __XQueue__ 或直接给执行线程池。如下图: ![Image text](https://www.plantuml.com/plantuml/png/dLHTRnf757tVNp5o7x9ODevv1AcgMlMjBsqYDLBJXqqPcfNNmtO_f5fL9SUj0pW6ktPiZC5oY72CK0b96r4CTlWpUsVPVz61kuPbKIEQVK3CpBtddBdtt37NLAOhXh9ElaYpf6gfQVObIzwpDZHA7cigev8KvvKyx5JXxTRdL67tTIqXIQQkgPP4nBVyALcD4kUcxpwlG-iAiaMUknnKTw5KbnzyRLERHXAZmFXFmuKyFeNzl3WP7Ga1reYQBFm7n0BvHKJYD-R63pNmmYLast3LXhq_4bE7yUZmVff6DPcXb60UMtwqiSputhxJhIMcAOZoewAoS8AtUGxTLqWN9Z9rIfFoGkpkYhamImLd6TZTXEnJwFm6bJflxRcN1yZyaF7A9jHEOtUTJiFfzKIvvnB3JxVWIML6XYs4iAJCYssl-6w5drMTNdjM3t_o4chvSB91JNkTof7vYd5oBherrBvVGDcyakD7sJ2eRTAl5DEIBSEciq86cGRir7bdsolL415oUU_ifNlwEu8-PB3Nm4gXFvnE4qd78HDGSNFV8XAIfrCfQiWOvfF_AabtWlSIgv0F7znNpBKu9ddbfolupxq-veK3w1uAFs8ENd0O_AsXw3er4e5zbhOeBiSVuk3cM6ue3jNZEb0c70hUlF0QDMIPQhf5zTlXojpmepUHG4puhg4kPlB221-6D-Yz8G7Qr4_uiofebaY0LAmWM-F7JNxIWlrN6FT_vU0G8seWAJpCGThLfU0gjXIDHf5lnXDtfYhwZsZ6Yq6_d0WtKpo65ltPcgkPlYbUNq6fxll4LtDzn0yB7-erDawuJaQvQCrWTNobiIXno-zuuSAVWkijqQYWy-EAkS5-cBXS2eo15F2r-tVFxPsFtzSLjzjtwy_usnUtB-peHqqH7qh-ncPCUQJHI8HOZoaZibVyAuAKedQmVU4sS_1-6-SMjQJInfeKABEUrZGI7jCuCfFLIGvUUUxCs4TdCh65xPXefPf99m3VVMBGvFS21_gbE5bSX9j3oENngeWgV0O7kxYFi697F4UyzTnv_spGtXxqCqvd4_8xijSx6RokHHON0qeWMuPUbn-_mTon83M5jRsrlx33PsBRNjV-1G00) # 安装教程 ```xml cn.xnatural tiny 2.1.test1 ``` # 初始化 ```java // 创建一个应用 final App app = new App(); app.source(new XQL("db.test")); // 应用启动(会自动扫描并创建当前包下所有@bean注解的类, 依次触发系统事件) app.start("需要扫描的包"); ``` # 自创统一事件驱动 EP 事件由协议空间和路径组成 让java应用不拘泥于(控制层-服务层-DAO层)一种结构模式 ```java @EL(ns = "协议空间", path = "事件路径(以/分割)", async="是否不排队处理", parallel="进入此事件并发", order = "相同事件的执行顺序", onProperty="某属性存在此事件才生效") ``` ## 类型1: 系统事件 App#start 后会依次触发 + @EL(ns = "sys", path = "inited"): 应用始化完成(环境配置, 系统线程池, 事件中心) + @EL(ns = "sys", path = "starting"): 通知所有服务执行各自初始化启动逻辑 + @EL(ns = "sys", path = "started"): 应用启动完成通知各服务各自运行 + @EL(ns = "sys", path = "stopping"): 应用停止事件(kill pid) ## 类型2:HTTP事件 > 启用XNET(HTTP)服务(基于事件EP的HTTP事件驱动服务) ```properties ### app.properties xnet.hp=:8080 ### 配置前端目录. 相对路径: 运行目录下边 #xnet.webDir=web ### 集群节点探测地址 cluster.detect=http://localhost:7070 ### 用数据库做集群锚点 # cluster.detect=jdbc:db.test ``` ### 控制层接口例子 ```java @Bean @EL(ns = "HTTP", path = "test", exclusive = true, async = true, parallel = 2, log = {true}) public class TestCtrl extends BeanTpl { // get 请求 @EL(path = "get", op = "get") R get(Integer p1, String p2) { return R.ok().attr("p1", p1).attr("p2", p2); } // 异步响应 @EL(path = "async") CompletableFuture asynctest() { CompletableFuture f = new CompletableFuture<>(); async(() -> { f.complete("qqqqqqqqqqqqqqqqqqq"); }); return f; } // 下载文件 @EL(path = "download/{fName}", op = "get") File download(String fName, XResponse response) { File f = new File("/tmp/" + fName); response.contentDisposition("attachment;filename=" + f.getName()); return f; } } ``` ### 自定义错误处理 ```java @EL(ns = "http:error", path = "{_}", async = false, exclusive = true) // 让自定义排前头 R err(XContext ctx, Throwable ex) { log.error(ctx.ep.logStr(ctx), ex); return R.fail(ex == null ? null : ex.getMessage()); } ``` ### 自定义结果处理 ```java @EL(ns = "http:response:json", path = "{_}", exclusive = true) Object responseJson(XResponse resp) { Object r = resp.ctx.result(); if (r instanceof String) return r; String ct = resp.getContentType(); if (ct != null && ct.contains("application/json")) { r = JSON.toJSONString(r, getStr("response.json.dateformat", "millis"), JSONWriter.Feature.WriteMapNullValue); } return r; } ``` ### 自定义会话处理 ```java @EL(ns = "http:session", path = "{_}", exclusive = true) Map session(XContext ctx) { String ccn = getStr("session.cookieName", app.name() + "_sid"); String sId = ctx.request.getCookie(ccn); if (sId == null || sId.isEmpty()) { sId = Util.msID(21); } Duration expire = getDuration("session.expire", ofHours(2)); Map delegate = sessions.computeIfAbsent(sId, s -> new ConcurrentHashMap<>()); long now = System.currentTimeMillis(); Long lastTime = (Long) delegate.get("_time"); if (lastTime == null || now - lastTime < expire.toMillis()) delegate.put("_time", now); else { delegate = new ConcurrentHashMap<>(); sessions.put(sId, delegate); delegate.put("_time", now); } delegate.put("_sid", sId); ctx.response.cookie(ccn, sId, (int) expire.getSeconds()); // 删除过期的session sessions.entrySet().removeIf(e -> now - (long) e.getValue().get("_time") > expire.toMillis()); return delegate; } ``` ## 类型3:无协议空间事件 ```java // 数据库启动后触发的事件 @EL(path = "/db/test/started", async=true) ``` ## 其他类型的自定义事件。。。 ```java @EL(ns = "业务1", path = "功能1") ``` ## 事件触发 ```java ep.fire("/db/test/started"); ``` # 配置 > 配置文件加载顺序(优先级从低到高): * classpath: app.properties, classpath: app-[profile].properties * file: ./app.properties, file: ./app-[profile].properties * configdir: app.properties, configdir: app-[profile].properties * 自定义环境属性配置(重写方法): App#customEnv * System.getProperties() >+ 系统属性(-Dconfigname): configname 指定配置文件名. 默认: app >+ 系统属性(-Dprofile): profile 指定启用特定的配置 >+ 系统属性(-Dconfigdir): configdir 指定额外配置文件目录 * 只读取properties文件. 按顺序读取app.properties, app-[profile].properties 两个配置文件 * 配置文件支持简单的 ${} 属性替换 # X-ap: 同名节点数据同步 > 扩展协议 X-Upgrade: X-ap 一般用于同名多节点有内存缓存,缓存更改后和其他节点之间的数据同步通知 ### 变更监听器 ```java @EL(path = "user", ns = AP.DATA_VERSION) void ap_user(String dataKey, Long version) { // 处理同名应用其他节点的用户数据更改通知 } ``` ### 变更通知 ```java ap.update("user", "user1", System.currentTimeMillis()); ``` # 五种时间任务调度: Sched ### Cron 时间表达式 ```java sched.cron("0 0/5 * * * ? ", () -> { System.out.println("每隔5分钟执行"); }) ``` ```java sched.cron("0 0/5 8-11:20,13:10-17:50 * * ? ", () -> { System.out.println("在(8-11:20,13:10-17:50)范围内每隔5分钟执行"); }) ``` ### 在将来的某个时间点执行 ```java sched.time( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2020-12-12 11:55:33"), () -> System.out.println("2020-12-12 11:55:33 执行") ); ``` ### 一段时间之后执行 ```java sched.after(Duration.ofMinutes(3), () -> { System.out.println("3分钟之后执行") }); ``` ### 任务间隔执行 #### fixedDelay ```java sched.fixedDelay(Duration.ofSeconds(10), Duration.ofSeconds(5), () -> { System.out.println("fixedDelay====第一次执行延时5秒执行"); }); ``` ```java sched.fixedDelay(Duration.ofSeconds(10), () -> { System.out.println("fixedDelay===="); }); ``` #### fixedRate ```java sched.fixedRate(Duration.ofSeconds(10), Duration.ofSeconds(5), () -> { System.out.println("fixedRate====第一次执行延时5秒执行"); }); ``` ```java sched.fixedRate(Duration.ofSeconds(10), () -> { System.out.println("fixedRate===="); }); ``` ### 动态任务调度执行. 自定义下次执行时间 #### dynRate ```java sched.dynRate(() -> { //每次执行完任务函数,会获取一次下次执行时间 if (new Random().nextInt(100) == 70) return null; // 返回null任务停止 Calendar cal = Calendar.getInstance(); cal.add(Calendar.MINUTE, new Random().nextInt(30) + 10); return cal.getTime(); }, () -> System.out.println("动态任务执行")); ``` #### dynDelay ```java sched.dynDelay(() -> { //每次执行完任务函数,会获取一次下次执行时间 if (new Random().nextInt(100) == 70) return null; // 返回null任务停止 Calendar cal = Calendar.getInstance(); cal.add(Calendar.MINUTE, new Random().nextInt(30) + 10); return cal.getTime(); }, () -> System.out.println("动态任务执行")); ``` # 动态按需添加服务 ```java @EL(ns = "sys", name = "inited") void inited() { if (!app.attrs("redis").isEmpty()) { //根据配置是否有redis,创建redis客户端工具 app.source(new RedisClient()); } } ``` # 服务基础类: BeanTpl > 推荐所有被加入到App中的服务都是BeanTpl的子类 ```properties ### app.properties 服务名.prop=1 ``` ```java app.source(new BeanTpl("服务名") { @EL(ns = "sys", path = "starting") void start() { // 初始化服务 } }) ``` ### bean注入 @Inject(name = "beanName") > 注入匹配规则: (已经存在值则不需要再注入) > > 1. 如果 @Inject name 没配置 > > > 先按 字段类型 和 字段名 匹配, 如无匹配 再按 字段类型 匹配 > > 2. 则按 字段类型 和 @Inject(name = "beanName") beanName 匹配 ```java app.source(new BeanTpl() { @Inject XQL repo; //自动注入 @EL(ns = "sys", path = "started") void init() { List rows = xql.rows("select * from test") log.info("========= {}", rows); } }); ``` ### 动态bean获取: 方法 bean(Class bean类型, String bean名字) ```java app.source(new BeanTpl() { @EL(ns = "sys", path = "started") void start() { Integer cnt = app.bean(XQL.class).single("select count(1) as total from test", Integer.class); log.info("=========" + cnt); } }); ``` ### bean依赖注入原理 > 两种bean容器: App是全局bean容器, 每个服务(BeanTpl)都是一个bean容器 > > 获取bean对象: 先从全局查找, 再从每个服务中获取 * 暴露全局bean ```java app.source(new TestService()); ``` * 服务(BeanTpl)里面暴露自己的bean ```java TestService repo = new TestService(); expose(repo); // 加入到bean容器,暴露给外部使用 ``` ### 属性直通车 > 服务(BeanTpl)提供便捷方法获取配置.包含: getLong, getInteger, getDouble, getBoolean等 ```properties ## app.properties testSrv.prop1=1 testSrv.prop2=2.2 ``` ```java app.source(new BeanTpl("testSrv") { @EL(ns = "sys", path = "starting") void init() { log.info("print prop1: {}, prop2: {}", getInteger("prop1"), getDouble("prop2")); } }) ``` ### 对应上图的两种任务执行 #### 异步任务 ```java async(() -> { // 异步执行任务 }) ``` #### 创建任务对列 ```java queue("队列名", () -> { // 执行任务 }) ``` ## _对列执行器_: XQueue 会自旋执行完队列中所有任务 当需要控制任务最多 一个一个, 两个两个... 的执行时 ### 添加任务到队列 ```java // 方法1 app.queue("save", () -> { // 执行任务 }); // 方法2 app.queue("save").offer(() -> { // 执行任务 }); ``` ### 队列特性 #### 并发控制 最多同时执行任务数, 默认1(one-by-one) ```java app.queue("save").parallel(2) ``` > 注: parallel 最好小于 系统最大线程数(sys.exec.maximumPoolSize), 即不能让某一个执行对列占用所有可用的线程 #### 执行速度控制 把任务按速度均匀分配在时间线上执行 支持: 每秒(10/s), 每分(10/m), 每小时(10/h), 每天(10/d) ```java // 例: 按每分钟执行30个任务的频率 queue("save").speed("30/m") ``` ```java // 清除速度控制(立即执行) queue("save").speed(null) ``` #### 队列 暂停/恢复 ```java // 暂停执行, 一般用于发生错误时 // 注: 必须有新的任务入对, 重新触发继续执行. 或者resume方法手动恢复执行 queue("save") .errorHandle {ex, me -> // 发生错误时, 让对列暂停执行(不影响新任务入对) // 1. 暂停一段时间 me.suspend(Duration.ofSeconds(180)); // 2. 条件暂停(每个新任务入队都会重新验证条件) // me.suspend(queue -> true); }; // 手动恢复执行 // queue("save").resume() ``` #### 队列最后任务有效 是否只使用队列最后一个, 清除队列前面的任务 适合: 入队的频率比出队高, 前面的任务可有可无 ```java // 例: increment数据库的一个字段的值 XQueue q = queue("increment").useLast(true); for (int i = 0; i < 20; i++) { // 入队快, 任务执行慢, 中间的可以不用执行 q.offer(() -> xql.execute("update test set count=?", i)); } ``` ```java // 例: 从服务端获取最新的数据 XQueue q = queue("newer").useLast(true); // 用户不停的点击刷新 q.offer(() -> { util.http().get("http://localhost:8080/data/newer").execute(); }) ``` #### 原理: 并发流量控制锁 Latcher 当被执行代码块需要控制同时线程执行的个数时 ```java final Latcher lock = new Latcher(); lock.limit(3); // 设置并发限制. 默认为1 if (lock.tryLock()) { // 尝试获取一个锁 try { // 被执行的代码块 } finally { lock.release(); // 释放一个锁 } } ``` # 数据库操作工具: XQL ### 创建一个数据源 ```java XQL xql = new XQL("db.rule"); ``` > 对应的配置 ```properties db.test.url=jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true db.test.username=root db.test.password=root db.test.minimumIdle=1 db.test.maximumPoolSize=2 # 自动DDL db.test.ddl=update ``` ### 创建实体 ```java @Table public static class TestE1 { @Column(type = "char(13)", primary = true) String id; @Column(type = "tinyint(1)", nullable = false, defaultValue = "1", comment = "是否可用") Boolean enabled; @Column(type = "varchar(100)", indexed = true, comment = "名") String name; @Column(type = "int", comment = "年龄") Integer age; @Column(type = "varchar(10)") Status status; @Column(type = "bigint", converter = TestE2.Converter.class) TestE2 testE2; @Column(type = "datetime", nullable = false, defaultValue = "now()", comment = "创建时间") Date ctime; Date dbTime; public TestE1() {} public TestE1(String name) { this.name = name; } public static enum Status { Ready,Running,End; } } @Table public static class TestE2 { @Column(type = "bigint", primary = true, autoIncrement = true) Long id; @Column String name; @Column(type = "int") Integer age; @Column(type = "bigint", converter = TestE3.Converter.class) TestE3 testE3; public static class Converter extends Column.Converter { @Override public Long toCol(TestE2 testE2) { return testE2 == null ? null : testE2.id; } @Override public TestE2 toProp(Long s) { TestE2 e = new TestE2(); e.id = s; return e; } } } @Table public static class TestE3 { @Column(type = "bigint", autoIncrement = true, primary = true) Long id; @Column String name; @Column(type = "int") Integer age; public static class Converter extends Column.Converter { @Override public Long toCol(TestE3 testE3) { return testE3 == null ? null : testE3.id; } @Override public TestE3 toProp(Long s) { TestE3 e = new TestE3(); e.id = s; return e; } } } ``` #### 查询单条记录 ```java TestE1 t = xql.row(TestE1.class, "age=5 order by ctime desc"); // 或者 t = xql.row(TestE1.class, new HashMap() {{ put("age", 5); }}, Arrays.asList("ctime desc")); t = xql.row(TestE1.class, "testE2.testE3.age=? order by ctime desc", 40); t = xql.byPk(TestE1.class, Arrays.asList("*", "CURRENT_TIMESTAMP(3) dbTime")); ``` #### 查询多条记录 ```java xql.rows(TestE1.class, "age > ? limit 10", 10); xql.rows(TestE1.class, Arrays.asList("*","testE2.*"), "age > ? limit 10", 10); ``` #### 查询单个值 ```java // 只支持 Integer.class, Long.class, String.class, Double.class, BigDecimal.class, Boolean.class, Date.class xql.single("select count(1) from test", Integer.class); ``` #### 插入一条记录 ```java xql.execute("insert into test(name, age, create_time) values(?, ?, ?)", "方羽", 5000, new Date()); ``` #### 更新一条记录 ```java xql.execute("update test set age = ? where id = ?", 10, 1) ``` #### 事务 ```java // 执行多条sql语句 xql.trans(() -> { // 插入并返回id Object id = xql.insertWithGeneratedKey("insert into test(name, age, create_time) values(?, ?, ?)", "方羽", 5000, new Date()); xql.execute("update test set age = ? where id = ?", 18, id); return null; }); ``` # http客户端 ```java // get util.http("http://xnatural.cn:9090/test/cus?p2=2") .header("test", "test") // 自定义header .cookie("sessionId", "xx") // 自定义 cookie .connectTimeout(5000) // 设置连接超时 5秒 .readTimeout(15000) // 设置读结果超时 15秒 .param("p1", 1) // 添加参数 .debug().get(); ``` ```java // post util.http("http://xnatural.cn:9090/test/cus") .debug().post(); ``` ```java // post 表单 util.http("http://xnatural.cn:9090/test/form") .param("p1", "p1") .debug().post(); ``` ```java // post 上传文件 util.http("http://xnatural.cn:9090/test/upload") .param("p1", "xxx") .param("file", new File("/tmp/1.txt")) .debug().post(); ``` ```java // post 上传文件+json util.http("http://xnatural.cn:9090/test/upload") .param("p1", new JSONObject().fluentPut("a", "qqq").fluentPut("b", 1), "application/json") .param("file", new File("/tmp/1.txt")) .debug().post(); ``` ```java // post json util.http("http://xnatural.cn:9090/test/json") .jsonBody(new JSONObject().fluentPut("p1", 1).toString()) .debug().post(); ``` ```java // post 普通文本 util.http("http://xnatural.cn:9090/test/string") .textBody("xxxxxxxxxxxxxxxx") .debug().post(); ``` ```java // 文件下载 util.http("http://localhost:5000/test/download/79975d5cfce74fceb4138932f3873a2d.png").fileHandle(filename -> { try { return Files.newOutputStream(Paths.get("/tmp/" + (filename == null || filename.isEmpty() ? "tmp" : filename))); } catch (IOException e) { throw new RuntimeException(e); } }) // .perSecondKb(10) // 每秒10kb读取限速 .get(); ``` # 对象拷贝器 #### javabean 拷贝到 javabean ```java util.copier( new Object() { public String name = "徐言"; }, new Object() { private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } } ).build(); ``` #### 对象 转换成 map ```java util.copier( new Object() { public String name = "方羽"; public String getAge() { return 5000; } }, new HashMap() ).build(); ``` #### 添加额外属性源 ```java util.copier( new Object() { public String name = "云仙君"; }, new Object() { private String name; public Integer age; public void setName(String name) { this.name = name; } public String getName() { return name; } } ).add("age", () -> 1).build(); ``` #### 忽略属性 ```java util.copier( new Object() { public String name = "徐言"; public Integer age = 22; }, new Object() { private String name; public Integer age = 33; public void setName(String name) { this.name = name; } public String getName() { return name; } } ).ignore("age").build(); // 最后 age 为33 ``` #### 属性值转换 ```java util.copier( new Object() { public long time = System.currentTimeMillis(); }, new Object() { private String time; public void setTime(String time) { this.time = time; } public String getTime() { return time; } } ).addConverter("time", o -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date((long) o))) .build(); ``` #### 忽略空属性 ```java util.copier( new Object() { public String name; }, new Object() { private String name = "方羽"; public void setName(String name) { this.name = name; } public String getName() { return name; } } ).ignoreNull(true).build(); // 最后 name 为 方羽 ``` #### 属性名映射 ```java util.copier( new Object() { public String p1 = "徐言"; }, new Object() { private String pp1 = "方羽"; public void setPp1(String pp1) { this.pp1 = pp1; } public String getPp1() { return pp1; } } ).mapProp( "p1", "pp1").build(); // 最后 name 为 徐言 ``` # 文件内容监控器(类linux tail) ```java util.tailer().tail("d:/tmp/tmp.json", 5); ``` # 相对时间 * 昨天凌晨: 0s:0m:0h:-1d * 当天凌晨: 0s:0m:0h * 3天前: -3d * 1分钟后: +1m * 两小时前: -2h ```java SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(Util.timeRelative("0s:0m:0h:-2d"))); ``` # ognl 对象表达式 ### 设置属性值 ```java Util.ognl(o1, "o2.o3.i", 1); ``` ### 获取属性值 ```java Util.ognl(o1, "o2.o3.i"); ``` # 通用本地命令执行 ```java Util.cmd("ffmpeg -i 视频文件 -vcodec h264 -max_muxing_queue_size 5120 -b:v 500k -r 25 -y out.mp4", Duration.ofSeconds(120)); ``` # 简单缓存: Cacher ```properties ## app.properties 缓存最多保存100条数据 cacher.limit=100 ``` ### 缓存操作 ```java // 1. 设置缓存 bean(Cacher).set("缓存key", "缓存值", Duration.ofMinutes(30)); // 2. 过期函数 bean(Cacher).set("缓存key", "缓存值", record -> { // 缓存值: record.value // 缓存更新时间: record.getUpdateTime() return 函数返回过期时间点(时间缀), 返回null(不过期,除非达到缓存限制被删除); }); // 3. 获取缓存 bean(Cacher).get("缓存key"); // 4. 获取缓存值, 并更新缓存时间(即从现在开始重新计算过期时间) bean(Cacher).getAndUpdate("缓存key"); // 5. 手动删除 bean(Cacher).remove("缓存key"); ``` ### hash缓存操作 ```java // 1. 设置缓存 bean(Cacher).hset("缓存key", "数据key", "缓存值", Duration.ofMinutes(30)); // 2. 过期函数 bean(Cacher).hset("缓存key", "数据key", "缓存值", record -> { // 缓存值: record.value // 缓存更新时间: record.getUpdateTime() return 函数返回过期时间点(时间缀), 返回null(不过期,除非达到缓存限制被删除); }); // 3. 获取缓存 bean(Cacher).hget("缓存key", "数据key"); // 4. 获取缓存值, 并更新缓存时间(即从现在开始重新计算过期时间) bean(Cacher).hgetAndUpdate("缓存key", "数据key"); // 5. 手动删除 bean(Cacher).hremove("缓存key", "数据key"); ``` # 无限递归优化实现: Recursion > 解决java无尾递归替换方案. 例: ```java System.out.println(factorialTailRecursion(1, 10_000_000).invoke()); ``` ```java /** * 阶乘计算 * @param factorial 当前递归栈的结果值 * @param number 下一个递归需要计算的值 * @return 尾递归接口,调用invoke启动及早求值获得结果 */ Recursion factorialTailRecursion(final long factorial, final long number) { if (number == 1) { // new Exception().printStackTrace(); return Recursion.done(factorial); } else { return Recursion.call(() -> factorialTailRecursion(factorial + number, number - 1)); } } ``` > 备忘录模式:提升递归效率. 例: ```java System.out.println(fibonacciMemo(47)); ``` ```java /** * 使用同一封装的备忘录模式 执行斐波那契策略 * @param n 第n个斐波那契数 * @return 第n个斐波那契数 */ long fibonacciMemo(long n) { return Recursion.memo((fib, number) -> { if (number == 0 || number == 1) return 1L; return fib.apply(number-1) + fib.apply(number-2); }, n); } ``` # 延迟对象: Lazier > 封装是一个延迟计算值(只计算一次) ```java final Lazier _id = new Lazier<>(() -> { String id = getHeader("X-Request-ID"); if (id != null && !id.isEmpty()) return id; return UUID.randomUUID().toString().replace("-", ""); }); ``` * 延迟获取属性值 ```java final Lazier _name = new Lazier<>(() -> getAttr("sys.name", String.class, "app")); ``` * 重新计算 ```java final Lazier _num = new Lazier(() -> new Random().nextInt(10)); _num.get(); _num.clear(); // 清除重新计算 _num.get(); ``` # 时间段统计工具: Counter ```java // 按小时统计 Counter counter = new Counter("MM-dd HH", (time, count) -> { // time: 指具体某个小时 // count: 指具体某个小时的统计个数 }); counter.increment(); // 当前小时统计加一 ``` # 应用例子 最佳实践: [Demo(java)](https://gitee.com/xnat/appdemo) , [GRule(groovy)](https://gitee.com/xnat/grule) # 2.1.0 ing - [x] feat: 重构事件中心 - [ ] feat: 支持 @PreDestroy @PostConstruct - [ ] feat: Httper 代理 - [ ] feat: 增加日志级别配置 - [ ] feat: Httper 工具支持 websocket - [ ] feat: 自定义注解 # 参与贡献 xnatural@msn.cn