# app
**Repository Path**: yzey/app
## Basic Information
- **Project Name**: app
- **Description**: 轻量级java异步框架
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 15
- **Created**: 2021-08-09
- **Last Updated**: 2022-05-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 介绍
轻量级java应用异步框架. 基于 [enet](https://gitee.com/xnat/enet)
> 系统只一个公用线程池: 所有的执行都被抽象成Runnable加入到公用线程池中执行
> 上层业务不需要创建线程池和线程增加复杂度, 而使用Devourer控制执行并发
> 所以系统性能只由线程池大小属性 sys.exec.corePoolSize=4, 和 jvm内存参数 -Xmx512m 控制

# 安装教程
```xml
cn.xnatural.app
app
1.0.6
```
# 系统事件
+ sys.inited: 应用始化完成(环境配置, 系统线程池, 事件中心)
+ sys.starting: 通知所有服务启动. 一般为ServerTpl
+ sys.started: 应用启动完成
+ sys.stopping: 应用停止事件(kill pid)
# 可搭配其它服务
[http](https://gitee.com/xnat/http), [jpa](https://gitee.com/xnat/jpa),
[sched](https://gitee.com/xnat/sched), [remoter](https://gitee.com/xnat/remoter)
# 初始化
```java
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(); // 应用启动
```

## 添加http服务
> web.hp=:8080
```java
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();
}
});
```
## 添加jpa
> jpa_local.url=jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root
```java
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(); }
});
```
## 服务基础类: ServerTpl
> 推荐所有被加入到AppContext中的服务都是ServerTpl的子类
```java
app.addSource(new ServerTpl("服务名") {
@EL(name = "sys.starting", async = true)
void start() {
// 初始化服务
}
})
```
### 依赖注入
> 每个服务都是一个bean容器, AppContext是全局bean容器
* 暴露一个bean对象
```java
Repo repo = new Repo("jdbc:mysql://localhost:3306/test?user=root&password=root").init();
exposeBean(repo);
```
* 获取bean对象: 先从全局查找, 再从每个服务中获取
```java
bean(Repo).firstRow("select count(1) as total from db").get("total")
```
### 异步任务
```java
async(() -> {
// 异步执行任务
})
```
### 创建任务对列
```java
queue("toEs", () -> {
// 提交数据到es
})
```
## 动态添加服务
```java
@EL(name = "sys.inited")
void sysInited() {
if (!app.attrs("redis").isEmpty()) { //根据配置是否有redis,创建redis客户端工具
app.addSource(new RedisClient())
}
}
```
## 系统心跳
> 需要用 [sched](https://gitee.com/xnat/sched) 添加监听
```java
@EL(name = "sched.after")
void after(Duration duration, Runnable fn) {sched.after(duration, fn);}
```
> 每隔一段时间触发一次心跳, 1~4分钟随机心跳
> + 配置(sys.heartbeat.minInterval) 控制心跳最小时间间隔
> + 配置(sys.heartbeat.randomInterval) 控制心跳最大时间间隔
## bean注入
```java
app.addSource(new ServerTpl() {
@Named ServerTpl server1; //自动注入, 按类型和名字
@Inject Repo repo; //自动注入, 按类型
@EL(name = "sys.started", async = true)
void init() {
log.info("{} ========= {}", name, server1.getName());
}
});
```
## 动态bean获取
```java
app.addSource(new ServerTpl() {
@EL(name = "sys.started", async = true)
void start() {
log.info(bean(Repo).firstRow("select count(1) as total from test").get("total").toString());
}
});
```
## 环境配置
* 系统属性(-Dconfigdir): configdir 指定配置文件路径. 默认:类路径
* 系统属性(-Dconfigname): configname 指定配置文件名. 默认:app
* 系统属性(-Dprofile): profile 指定启用特定的配置
* 只读取properties文件. 按顺序读取app.properties, app-[profile].properties 两个配置文件
* 配置文件支持简单的 ${} 属性替换
* 系统属性: System.getProperties() 优先级最高
## 对列执行器/并发控制器 Devourer
> 当需要控制任务最多 一个一个, 两个两个... 的执行时
> + 服务基础类(ServerTpl)提供方法: queue
```java
// 初始化一个 save 对列执行器
queue("save")
.failMaxKeep(10000) // 最多保留失败的任务个数, 默认不保留
.parallel(2) // 最多同时执行任务数, 默认1(one-by-one)
.errorHandle {ex, me ->
// 当任务执行抛错时执行
};
```
```java
// 添加任务执行, 方法1
queue("save", () -> {
// 执行任务
});
// 添加任务执行, 方法2
queue("save").offer(() -> {
// 执行任务
});
```
```java
// 暂停执行, 一般用于发生错误时
// 注: 必须有新的任务入对, 重新触发继续执行. 或者resume方法手动恢复执行
queue("save")
.errorHandle {ex, me ->
// 发生错误时, 让对列暂停执行(不影响新任务入对)
// 1. 暂停一段时间
me.suspend(Duration.ofSeconds(180));
// 2. 条件暂停
// me.suspend(queue -> true);
};
```
```java
// 是否只使用队列最后一个, 清除队列前面的任务
// 适合: 入队的频率比出队高, 前面的任务可有可无
// 例: increment数据库的一个字段的值
Devourer q = queue("increment").useLast(true);
for (int i = 0; i < 20; i++) {
// 入队快, 任务执行慢, 中间的可以不用执行
q.offer(() -> repo.execute("update test set count=?", i));
}
```
### 并发流量控制锁 LatchLock
> 当被执行代码块需要控制同时线程执行的个数时
```java
final LatchLock lock = new LatchLock();
lock.limit(3); // 设置并发限制. 默认为1
if (lock.tryLock()) { // 尝试获取一个锁
try {
// 被执行的代码块
} finally {
lock.release(); // 释放一个锁
}
}
```
## 无限递归优化实现 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);
}
```
参照:
- https://www.cnblogs.com/invoker-/p/7723420.html
- https://www.cnblogs.com/invoker-/p/7728452.html
## 简单缓存 CacheSrv
```java
// 添加缓存服务
app.addSource(new CacheSrv());
```
```java
// 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");
```
## 延迟对象 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"));
```
## http客户端
```java
// 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();
```
```java
// post
Utils.http().post("http://xnatural.cn:9090/test/cus")
.debug().execute();
```
```java
// post 表单
Utils.http().post("http://xnatural.cn:9090/test/form")
.param("p1", "p1")
.debug().execute();
```
```java
// 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();
```
```java
// post json
Utils.http().post("http://xnatural.cn:9090/test/json")
.jsonBody(new JSONObject().fluentPut("p1", 1).toString())
.debug().execute();
```
```java
// post 文本
Utils.http().post("http://xnatural.cn:9090/test/string")
.textBody("xxxxxxxxxxxxxxxx")
.debug().execute();
```
## Map构建器
```java
// 把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();
```
## 应用例子
[Demo](https://gitee.com/xnat/appdemo)
[GRule](https://gitee.com/xnat/grule)
# 1.0.7 ing
- [x] feat: Devourer use last:队列最后任务有效
- [ ] CacheSrv accessTime
# 参与贡献
xnatural@msn.cn