同步操作将从 hubert-樂xx/tiny 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
轻量级java应用异步框架. 基于 enet 事件环型框架结构
系统只一个公用线程池: 所有的执行都被抽象成Runnable加入到公用线程池中执行
系统中的任务只有在线程池到达最大后,才需要排对执行(和默认线程池的行为不同)
上层业务不需要创建线程池和线程增加复杂度, 而使用Devourer控制执行并发
所以系统性能只由线程池大小属性 sys.exec.corePoolSize=8, sys.exec.maximumPoolSize=30 和 jvm内存参数 -Xmx512m 控制
<dependency>
<groupId>cn.xnatural.app</groupId>
<artifactId>tiny</artifactId>
<version>1.0.9</version>
</dependency>
final AppContext app = new AppContext(); // 创建一个应用
app.addSource(new ServerTpl("server1") { // 添加服务 server1
@EL(name = "sys.starting")
void start() {
log.info("{} start", name);
}
});
app.addSource(new TestService()); // 添加自定义服务
app.start(); // 应用启动(会依次触发系统事件)
基于事件环型框架结构图
以AppContext#EP为事件中心挂载服务结构
- 系统属性(-Dconfigname): configname 指定配置文件名. 默认:app
- 系统属性(-Dprofile): profile 指定启用特定的配置
- 系统属性(-Dconfigdir): configdir 指定额外配置文件目录
只读取properties文件. 按顺序读取app.properties, app-[profile].properties 两个配置文件
配置文件支持简单的 ${} 属性替换
加载顺序(优先级从低到高):
### app.propertiees
web.hp=:8080
app.addSource(new ServerTpl("web") { //添加web服务
HttpServer server;
@EL(name = "sys.starting", async = true)
void start() {
server = new HttpServer(app().attrs(name), exec());
server.buildChain(chain -> {
chain.get("get", hCtx -> {
hCtx.render("xxxxxxxxxxxx");
});
}).start();
}
@EL(name = "sys.stopping")
void stop() {
if (server != null) server.stop();
}
});
### app.properties
jpa_local.url=jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root
app.addSource(new ServerTpl("jpa_local") { //数据库 jpa_local
Repo repo;
@EL(name = "sys.starting", async = true)
void start() {
repo = new Repo(attrs()).init();
exposeBean(repo); // 把repo暴露给全局
ep.fire(name + ".started");
}
@EL(name = "sys.stopping", async = true)
void stop() { if (repo != null) repo.close(); }
});
@EL(name = "sys.inited")
void sysInited() {
if (!app.attrs("redis").isEmpty()) { //根据配置是否有redis,创建redis客户端工具
app.addSource(new RedisClient())
}
}
需要用 sched 添加 sched.after 事件监听
@EL(name = "sched.after")
void after(Duration duration, Runnable fn) {sched.after(duration, fn);}
每隔一段时间触发一次心跳, 1~4分钟(两个配置相加)随机心跳
- 配置(sys.heartbeat.minInterval) 控制心跳最小时间间隔
- 配置(sys.heartbeat.randomInterval) 控制心跳最大时间间隔
推荐所有被加入到AppContext中的服务都是ServerTpl的子类
app.addSource(new ServerTpl("服务名") {
@EL(name = "sys.starting", async = true)
void start() {
// 初始化服务
}
})
app.addSource(new ServerTpl() {
@Inject Repo repo; //自动注入, 按类型
@EL(name = "sys.started", async = true)
void init() {
List<Map> rows = repo.rows("select * from test")
log.info("========= {}", rows);
}
});
app.addSource(new ServerTpl("testNamed") {
@Named ServerTpl server1; //自动注入, 按类型和名字
@EL(name = "sys.started", async = true)
void init() {
log.info("{} ========= {}", name, server1.getName());
}
});
app.addSource(new ServerTpl() {
@EL(name = "sys.started", async = true)
void start() {
String str = bean(Repo).firstRow("select count(1) as total from test").get("total").toString();
log.info("=========" + str);
}
});
两种bean容器: AppContext是全局bean容器, 每个服务(ServerTpl)都是一个bean容器
获取bean对象: 先从全局查找, 再从每个服务中获取
app.addSource(new TestService());
Repo repo = new Repo("jdbc:mysql://localhost:3306/test?user=root&password=root").init();
exposeBean(repo); // 加入到bean容器,暴露给外部使用
服务(ServerTpl)提供便捷方法获取配置.包含: getLong, getInteger, getDouble, getBoolean等
## app.properties
testSrv.prop1=1
testSrv.prop2=2.2
app.addSource(new ServerTpl("testSrv") {
@EL(name = "sys.starting")
void init() {
log.info("print prop1: {}, prop2: {}", getInteger("prop1"), getDouble("prop2"));
}
})
async(() -> {
// 异步执行任务
})
queue("toEs", () -> {
// 提交数据到es
})
当需要控制任务最多 一个一个, 两个两个... 的执行时
服务基础类(ServerTpl)提供方法: queue
queue("save")
.failMaxKeep(10000) // 最多保留失败的任务个数, 默认不保留
.parallel(2) // 最多同时执行任务数, 默认1(one-by-one)
.errorHandle {ex, me ->
// 当任务执行抛错时执行
};
// 方法1
queue("save", () -> {
// 执行任务
});
// 方法2
queue("save").offer(() -> {
// 执行任务
});
// 暂停执行, 一般用于发生错误时
// 注: 必须有新的任务入对, 重新触发继续执行. 或者resume方法手动恢复执行
queue("save")
.errorHandle {ex, me ->
// 发生错误时, 让对列暂停执行(不影响新任务入对)
// 1. 暂停一段时间
me.suspend(Duration.ofSeconds(180));
// 2. 条件暂停
// me.suspend(queue -> true);
};
// 是否只使用队列最后一个, 清除队列前面的任务
// 适合: 入队的频率比出队高, 前面的任务可有可无
// 例: increment数据库的一个字段的值
Devourer q = queue("increment").useLast(true);
for (int i = 0; i < 20; i++) {
// 入队快, 任务执行慢, 中间的可以不用执行
q.offer(() -> repo.execute("update test set count=?", i));
}
当被执行代码块需要控制同时线程执行的个数时
final LatchLock lock = new LatchLock();
lock.limit(3); // 设置并发限制. 默认为1
if (lock.tryLock()) { // 尝试获取一个锁
try {
// 被执行的代码块
} finally {
lock.release(); // 释放一个锁
}
}
解决java无尾递归替换方案. 例:
System.out.println(factorialTailRecursion(1, 10_000_000).invoke());
/**
* 阶乘计算
* @param factorial 当前递归栈的结果值
* @param number 下一个递归需要计算的值
* @return 尾递归接口,调用invoke启动及早求值获得结果
*/
Recursion<Long> 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));
}
}
备忘录模式:提升递归效率. 例:
System.out.println(fibonacciMemo(47));
/**
* 使用同一封装的备忘录模式 执行斐波那契策略
* @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);
}
// 添加缓存服务
app.addSource(new CacheSrv());
## app.properties 缓存最多保存100条数据
cacheSrv.itemLimit=100
// 1. 设置缓存
bean(CacheSrv).set("缓存key", "缓存值", Duration.ofMinutes(30));
// 2. 获取缓存
bean(CacheSrv).get("缓存key");
// 3. 过期设置
bean(CacheSrv).expire("缓存key", Duration.ofMinutes(30));
// 4. 手动删除
bean(CacheSrv).remove("缓存key");
封装是一个延迟计算值(只计算一次)
final Lazier<String> _id = new Lazier<>(() -> {
String id = getHeader("X-Request-ID");
if (id != null && !id.isEmpty()) return id;
return UUID.randomUUID().toString().replace("-", "");
});
final Lazier<String> _name = new Lazier<>(() -> getAttr("sys.name", String.class, "app"));
final Lazier<Integer> _num = new Lazier(() -> new Random().nextInt(10));
_num.get();
_num.clear(); // 清除重新计算
_num.get();
DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true");
repo.row("select * from test order by id desc");
repo.rows("select * from test limit 10");
repo.rows("select * from test where id in (?, ?)", 2, 7);
// 只支持 Integer.class, Long.class, String.class, Double.class, BigDecimal.class, Boolean.class, Date.class
repo.single("select count(1) from test", Integer.class);
repo.execute("insert into test(name, age, create_time) values(?, ?, ?)", "方羽", 5000, new Date());
repo.execute("update test set age = ? where id = ?", 10, 1)
// 执行多条sql语句
repo.trans(() -> {
// 插入并返回id
Object id = repo.insertWithGeneratedKey("insert into test(name, age, create_time) values(?, ?, ?)", "方羽", 5000, new Date());
repo.execute("update test set age = ? where id = ?", 18, id);
return null;
});
// get
Utils.http().get("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().execute();
// post
Utils.http().post("http://xnatural.cn:9090/test/cus")
.debug().execute();
// post 表单
Utils.http().post("http://xnatural.cn:9090/test/form")
.param("p1", "p1")
.debug().execute();
// post 上传文件
Utils.http().post("http://xnatural.cn:9090/test/upload")
.param("file", new File("d:/tmp/1.txt"))
.debug().execute();
// post 上传文件流. 一般上传大文件 可配合 汇聚流 使用
Utils.http().post("http://xnatural.cn:9090/test/upload")
.fileStream("file", "test.md", new FileInputStream("d:/tmp/test.md"))
.debug().execute();
// post json
Utils.http().post("http://xnatural.cn:9090/test/json")
.jsonBody(new JSONObject().fluentPut("p1", 1).toString())
.debug().execute();
// post 普通文本
Utils.http().post("http://xnatural.cn:9090/test/string")
.textBody("xxxxxxxxxxxxxxxx")
.debug().execute();
// 把bean转换成map
Utils.toMapper(bean).build();
// 添加属性
Utils.toMapper(bean).add("属性名", 属性值).build();
// 忽略属性
Utils.toMapper(bean).ignore("属性名").build();
// 转换属性
Utils.toMapper(bean).addConverter("属性名", Function<原属性值, 转换后的属性值>).build();
// 衍生属性
Utils.toMapper(bean).addConverter("属性名", "新属性", Function<原属性值, 转换后的属性值>).build();
// 忽略null属性
Utils.toMapper(bean).ignoreNull().build();
// 属性更名
Utils.toMapper(bean).aliasProp(原属性名, 新属性名).build();
// 排序map
Utils.toMapper(bean).sort().build();
// 显示class属性
Utils.toMapper(bean).showClassProp().build();
Utils.tailer().tail("d:/tmp/tmp.json", 5);
最佳实践: Demo(java) , Demo(scala) , GRule(groovy)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。