{"id":37193565,"url":"https://github.com/xnat9/tiny","last_synced_at":"2026-01-14T22:28:12.405Z","repository":{"id":40322306,"uuid":"327766661","full_name":"xnat9/tiny","owner":"xnat9","description":"小巧的java应用微内核框架, 可用于构建小工具项目，web项目，各种大大小小的项目","archived":false,"fork":false,"pushed_at":"2024-02-29T02:41:57.000Z","size":471,"stargazers_count":26,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-07-11T14:02:34.185Z","etag":null,"topics":["async","framework","ioc"],"latest_commit_sha":null,"homepage":"https://gitee.com/xnat/tiny","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xnat9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-08T01:27:00.000Z","updated_at":"2024-05-21T09:34:46.000Z","dependencies_parsed_at":"2023-01-19T16:33:33.601Z","dependency_job_id":null,"html_url":"https://github.com/xnat9/tiny","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/xnat9/tiny","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnat9%2Ftiny","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnat9%2Ftiny/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnat9%2Ftiny/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnat9%2Ftiny/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xnat9","download_url":"https://codeload.github.com/xnat9/tiny/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xnat9%2Ftiny/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28436397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T21:32:52.117Z","status":"ssl_error","status_checked_at":"2026-01-14T21:32:33.442Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","framework","ioc"],"created_at":"2026-01-14T22:28:11.665Z","updated_at":"2026-01-14T22:28:12.385Z","avatar_url":"https://github.com/xnat9.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 介绍\n小巧的java应用微内核框架. 基于 [enet](https://gitee.com/xnat/enet) 事件环型框架结构\n\n目前大部分高级语言都解决了编程内存自动管理的问题(垃圾回收)， 但并没有解决cpu资源自动管理的问题。\n基本上都是程序员主动创建线程或线程池，这些线程是否被充分利用了，在应用不同的地方创建是否有必要，\n太多的线程是否造成竞争性能损耗。毕竟线程再多，而决定并发的是cpu的个数。\n\n所以需要框架来实现一个智能执行器(线程池)：根据应用的忙碌程度自动创建和销毁线程,\n即线程池会自己根据排对的任务数和当前池中的线程数的比例判断是否需要新创建线程(和默认线程池的行为不同)。\n会catch所有异常， 不会被业务异常给弄死(永动机)。  \n程序只需通过配置最大最小资源自动适配, 类似现在的数据库连接池。这样既能充分利用线程资源，\n也减少线程的空转和线程过多调度的性能浪费。\n\n\u003e 所以系统性能只由线程池大小属性 sys.exec.corePoolSize=8, sys.exec.maximumPoolSize=16 和 jvm内存参数 -Xmx1024m 控制\n\n框架设计一种轻量级执行器(伪协程): __Devourer__ 来控制执行模式(并发,暂停/恢复,速度等)  \n上层服务应该只关注怎么组装执行任务，然后提交给 __Devourer__ 或直接给执行线程池。如下图：\n\n\u003c!--\nskinparam ConditionEndStyle hline\n\n:服务层ServerTpl;\n\nsplit\n    -\u003e 1. 使用对列执行器(Devourer);\n    :queue(执行器名);\n\n    partition \"对列执行器(Devourer)\" {\n        split\n            -\u003e 控制并发;\n            :.parallel(n);\n            -\u003e n-by-n提交;\n        split again\n            -\u003e 速度控制;\n            :.speed(\"20/s\");\n            -\u003e 均匀分布在每秒提交\\n最多20个任务;\n        split again\n            -\u003e 暂停;\n            :.suspend(时间/条件);\n            -\u003e 恢复;\n            :.resume();\n        split again\n            -\u003e 队列最后任务有效;\n            :.useLast(true);\n            -\u003e 自动清除队列前面的任务\\n只提交对列中最后的任务;\n        end split\n\n    :.offer(任务);\n    }\n\n' split again\n'     partition \"XTask:任务集/任务编排库\" {\n'         :TaskWrapper;\n'         note\n'             任务\n'         end note\n'         split\n'             -\u003e 执行步骤;\n'             :.step(执行函数);\n'         split again\n'             -\u003e 重复执行步骤;\n'             :.reStep(最多重复次数, 执行函数, 判断是否重复函数);\n'         split again\n'             -\u003e 并发执行;\n'             :.parallel(执行函数1,执行函数2,...);\n'         end split\n'         -\u003e 添加进;\n'         :TaskContext;\n'         note\n'             任务编排容器/任务执行引擎\n'         end note\n'         :.start();\n'     }\n\nsplit again\n    -\u003e 2. 直接提交异步任务;\n    :async(任务);\n\nend split\n\n\npartition \"系统执行器/线程池\" {\n    \n    if (任务(Runnable)) then (闲)\n    :加入等待队列;\n    fork\n        -\u003e poll 任务;\n        :线程 1;\n    fork again\n        -\u003e poll 任务;\n        :线程 2;\n    fork again\n        -\u003e poll 任务;\n        :线程 n;\n    end fork\n    else\n        -[#red]-\u003e 忙: **当前等待任务数和当前线程数的比例超过一半(默认)**;\n        :创建新线程;\n    endif\n    :执行;\n}\n\n@enduml\n\n--\u003e\n![Image text](https://www.plantuml.com/plantuml/png/dLHVRnf747-_Jx5o7t9dsJZc4QIgKjktFjMHMgdfmwKipSddvNf_ebfL9SUj0pW6ktRy7yiXmZ529IHjH37OuSlSx77VeWjpXkDGBPfxGEpipFn-C_ERjPOrPgYcka8-px2KPciPzYLBBTchEYMFTOrHIP8Il5I0pJAyMr-YvXDgFZ3qf2HPXgxP4X7V_ATaCKRScwxteWgDAyWTylnbhxm5nrNv2_eauvZKL983ryHF3dMeFBo7dOAu6Lm95lO0dypyPv8Pyej4Wc-8Zn_ouCLBo3NXgWdRVoJ7BXEnVfcwJdMPASbe79j_j3hF-FQEswuano68-gEgiMY0ltOExTS85mMo34fJyapy_e8rCma5PrdOMeFSCsZz1gKgRsnxbxk8_93nqXfKJkBttLRDxNH4qwSYmq_MuMbfWeOZYB2Kp0-R_k7x1NvMTZlDIJxywIke5AB19hMS5IehqpNZwBm_By5zfuYqUIdFztFHf8v5lr8jMxPDXquIwMLhi5dbhGt_k88P8L_mprvv9xzZqeSCjclOALI8sweZwD1bb5HK7aX4Gl1CEarD6Tq2y5ybwLwuBd6AAF7R1wgrdC0W__JP0lxphWzuSHVQUqAF68C5zfs_CLN3e6OoP6SPc-9n-64UD0xfHloGFPv3RSAruFKBlrP1bB6XszIuNQ_i3Tz_guHy8hL6fvWj227SdTwaItq0b6aGy6TPmCoH4AWuGQx23-hyg04xhz7l_zB19SQieQ3eCeTX5-V2f_XSB1P3l0bDt1lhw3zY62zxtTDaT9ZYJRJfp_PKmmn4yUOAZgk1JW8sr_jyhtZNh75mGdKoqaLfXhiJC8t7Y7VwdXSlWYsuNXU32Yi_eLghx8UHogNG6aWXNNj_TxpE-V25NV3QNQ_wdBzxUVl23dwqD0bIoLyxZAcFTAeen7vC6P7zmxyKX1IzWuqBhvw73nkujyWbdJ6NfL2RZOoka-YQ9X2PB8vYiEIf8-CV7SdG95equafrYuuIVFU9ILFVzyNOFKwGsLcusODy0Kl5h49diBk5TamhBE8vueqNxeVdlUP6hvjrMsjGs9Jzpb7lJKMPdGqnWTmfTDhyu2t63WbfQUpjhVo573uJPcT5b_u5)\n\n# 安装教程\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecn.xnatural\u003c/groupId\u003e\n    \u003cartifactId\u003etiny\u003c/artifactId\u003e\n    \u003cversion\u003e1.1.6\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n# 初始化\n```java\n// 创建一个应用\nfinal AppContext app = new AppContext();\n\n// 添加服务 server1\napp.addSource(new ServerTpl(\"server1\") {\n    @EL(name = \"sys.starting\")\n    void start() {\n        log.info(\"{} start\", name);\n    }\n});\n// 添加自定义服务\napp.addSource(new TestService());\n\n// 应用启动(会依次触发系统事件)\napp.start();\n```\n\n\u003e 基于事件环型微内核框架结构图\n\u003e \n\u003e \u003e 以AppContext#EP为事件中心的挂载服务结构\n\u003c!--\n@startuml\nclass AppContext {\n  #EP ep\n  #Map\u003cString, Object\u003e sourceMap\n  #Map\u003cString, Devourer\u003e queues\n\n  +addSource(Object source)\n  +start()\n}\n\nnote left of AppContext::ep\n  事件中心\nend note\n\nnote right of AppContext::sourceMap\n  所有被添加的Server\nend note\n\nnote left of AppContext::queues\n  所有被Server#queue 方添加的\n  对列执行器\nend note\n\nnote right of AppContext::addSource\n  添加服务\nend note\n\nnote left of AppContext::start\n1. 触发事件 sys.inited 系统配置加载完成, 线程池, 事件中心初始化已完成\n2. 触发事件 sys.starting 调用所有服务所包含@EL(name = \"sys.starting\") 的监听器方法\n3. sys.starting 所有监听执行完成后, 继续触发 sys.started 事件\nend note\n\nclass EP {\n  #{field} Map\u003cString, Listener\u003e lsMap;\n  +{method} fire(String 事件名, Object...参数);\n}\n\nnote left of EP::lsMap\n  所有监听器映射: 关联@EL方法\nend note\n\nnote right of EP::fire\n  事件触发\nend note\n\nAppContext \u003c|-- EP\nEP \u003c|-- Server1 : 收集监听器\nEP \u003c|-- Server2 : 收集监听器\nEP \u003c|-- Server3 : 收集监听器\n\n\nclass Server1 {\n  ...启动监听...\n  @EL(name = \"sys.starting\")\n  void start()\n  \n  ...停止监听...\n  @EL(name = \"sys.stopping\")\n  void stop()\n\n  ...其它监听...\n  @EL(name = \"xx\")\n  {method} void xx()\n\n  ...基本功能...\n\n  {method} bean(Class bean类型, String bean名)\n\n  {method} queue(String 对列名, Runnabel 任务)\n\n  {method} async(Runnable 异步任务)\n\n  {method} get[Integer, Long, Boolean, Str](String 属性名)\n}\n\nclass Server2  {\n  ...启动监听...\n  @EL(name = \"sys.starting\")\n  void start()\n\n  ...其它监听...\n  @EL(name = \"oo\")\n  {method} String oo()\n\n  ...基本功能...\n\n  {method} bean(Class bean类型, String bean名)\n\n  {method} queue(String 对列名, Runnabel 任务)\n\n  {method} async(Runnable 异步任务)\n\n  {method} get[Integer, Long, Boolean, Str](String 属性名)\n}\n\nclass Server3  {  \n  ...停止监听...\n  @EL(name = \"sys.stopping\")\n  void stop()\n\n  ...其它监听...\n  @EL(name = \"aa\")\n  {method} aa()\n\n  ...基本功能...\n\n  {method} bean(Class bean类型, String bean名)\n\n  {method} queue(String 对列名, Runnabel 任务)\n\n  {method} async(Runnable 异步任务)\n\n  {method} get[Integer, Long, Boolean, Str](String 属性名)\n}\n@enduml\n--\u003e\n\n![Image text](http://www.plantuml.com/plantuml/png/vLJDJjjE7BpxANw2Iw9_Y0JS0dz4ItEeH5LKZbKF9ju4LuutjHqKH960a0gIK884z8EK3wGsaI1y50aARiJBPDVXBRhs6kDGe4YLUk6stfsPdPsTTRzkY9gHJYf2J15r7HwbKWDODL36W0a1e3qw12Xb3vw9gTvXGvFLH0YUZxn6CQCFT9pMOeYjN0SyGMDi2Mbzy2QDqaWN6E0_KPA67KA0yrrwq5vpN0I2mgGWgDX0eA2u0JZkinE9E3uQPuM6UTpuKIFdMG6f4jXmbwJ9YT7VM7wFT7wAbkURsplqn2JvJUlpx33Inf3c2TsnEp-8NuHpsvq5eAkddYW3aVrJClU1pbUQMqNogNelfru-ZC-rQ7c1vBVkuyx9J-WCGxFoZImkyPH07zV3iYeRI0BhoBJCZOlSWbNVOyhDUfti5UbSAGJMsRbLBT33pL1Bk6Jk2waKI76Ld7pdKA7h1dbdOtRdq3p8MijL7WxtpSQac2EbdVxeO40LamZ-XpO_foq8B2rhROcKTbb8TeH7Aq9tk5MOIt8K3vJR8QNtpBnPiSmQTtL5Gv9x55zqlDxH8LxhYRYC56aI_AKTb7K3gNPf5PtDzzYzd4WYOnGpO5pMK80ZNMrIMhXy2U5mc2pEq9M3OC_r1hCT8n55z_Vlwi0VDyd1R0H8xgWvlSnIuWdSKXOkPVlmdW4_jm_lUxszRpiw64E83l4XRsidH80k7r-ilVDSN4Dq_H7HVGF2pTVRnGxPJgMqJ_9L3cEVRFBsBh35CInBSFah070rfikqjdst1awbMZNO39Dm1NB7P2zxcq0cuz2yYtRucSmLU-ECbdT9VgEPhTjiFtO4YMfWm3wuCxGEJR9U206lYJF5IXBqK_Z_q2sI-vTmYlGYhQhY25AWOPhixRIIH7rSZGKuH450VixGsjURW0bal7og6YY1DDPdRBVwCSOAC-AuUkLjVBXEfogEkSdMg-k2lx-x-mMFSMlmhZMC7shqtKpj7vLU55kp5yK757e_KgLqKla5)\n\n\n## 系统事件: app.start() 后会依次触发 sys.inited, sys.starting, sys.started\n+ sys.inited: 应用始化完成(环境配置, 系统线程池, 事件中心)\n+ sys.starting: 通知所有服务启动. 一般为ServerTpl\n+ sys.started: 应用启动完成\n+ sys.stopping: 应用停止事件(kill pid)\n\n## 配置\n\u003e 配置文件加载顺序(优先级从低到高):\n  * classpath: app.properties, classpath: app-[profile].properties\n  * file: ./app.properties, file: ./app-[profile].properties\n  * configdir: app.properties, configdir: app-[profile].properties\n  * 自定义环境属性配置(重写方法): AppContext#customEnv\n  * System.getProperties()\n  \n\u003e+ 系统属性(-Dconfigname): configname 指定配置文件名. 默认: app\n\u003e+ 系统属性(-Dprofile): profile 指定启用特定的配置\n\u003e+ 系统属性(-Dconfigdir): configdir 指定额外配置文件目录\n\n* 只读取properties文件. 按顺序读取app.properties, app-[profile].properties 两个配置文件\n* 配置文件支持简单的 ${} 属性替换\n\n\n## 添加 [xhttp](https://gitee.com/xnat/xhttp) 服务\n```properties\n### app.properties\nweb.hp=:8080\n```\n```java\napp.addSource(new ServerTpl(\"web\") { //添加web服务\n    HttpServer server;\n    \n    @EL(name = \"sys.starting\", async = true)\n    void start() {\n        server = new HttpServer(attrs(), exec());\n        server.buildChain(chain -\u003e {\n            chain.get(\"get\", hCtx -\u003e {\n                hCtx.render(\"xxxxxxxxxxxx\");\n            });\n        }).start();\n    }\n    \n    @EL(name = \"sys.stopping\")\n    void stop() {\n        if (server != null) server.stop();\n    }\n});\n```\n\n## 添加 [xjpa](https://gitee.com/xnat/xjpa) 数据库操作服务\n```properties\n### app.properties\njpa_local.url=jdbc:mysql://localhost:3306/test?useSSL=false\u0026user=root\u0026password=root\u0026allowPublicKeyRetrieval=true\n```\n```java\napp.addSource(new ServerTpl(\"jpa_local\") { //数据库 jpa_local\n    Repo repo;\n    \n    @EL(name = \"sys.starting\", async = true)\n    void start() {\n        repo = new Repo(attrs()).init();\n        exposeBean(repo); // 把repo暴露给全局, 即可以通过@Inject注入\n        ep.fire(name + \".started\");\n    }\n\n    @EL(name = \"sys.stopping\", async = true, order = 2f)\n    void stop() { if (repo != null) repo.close(); }\n});\n```\n\n## 添加 [sched](https://gitee.com/xnat/jpa) 时间调度服务\n```java\napp.addSource(new ServerTpl(\"sched\") {\n    Sched sched;\n    @EL(name = \"sys.starting\", async = true)\n    void start() {\n        sched = new Sched(attrs(), exec()).init();\n        exposeBean(sched);\n        ep.fire(name + \".started\");\n    }\n\n    @EL(name = \"sched.after\")\n    void after(Duration duration, Runnable fn) {sched.after(duration, fn);}\n\n    @EL(name = \"sys.stopping\", async = true)\n    void stop() { if (sched != null) sched.stop(); }\n});\n```\n\n## 动态按需添加服务\n```java\n@EL(name = \"sys.inited\")\nvoid sysInited() {\n    if (!app.attrs(\"redis\").isEmpty()) { //根据配置是否有redis,创建redis客户端工具\n        app.addSource(new RedisClient())\n    }\n}\n```\n\n## 让系统心跳(即:让系统安一定频率触发事件 sys.heartbeat)\n\u003e 需要用 [sched](https://gitee.com/xnat/sched) 添加 _sched.after_ 事件监听\n```java\n@EL(name = \"sched.after\")\nvoid after(Duration duration, Runnable fn) {sched.after(duration, fn);}\n```\n\u003e 每隔一段时间触发一次心跳, 1~4分钟(两个配置相加)随机心跳\n\u003e + 配置(sys.heartbeat.minInterval) 控制心跳最小时间间隔\n\u003e + 配置(sys.heartbeat.randomInterval) 控制心跳最大时间间隔\n\n```java\n// 心跳事件监听器\n@EL(name = \"sys.heartbeat\", async = true)\nvoid myHeart() {\n    System.out.println(\"咚\");\n}\n```\n\n## 服务基础类: ServerTpl\n\u003e 推荐所有被加入到AppContext中的服务都是ServerTpl的子类\n```properties\n### app.properties\n服务名.prop=1\n```\n```java\napp.addSource(new ServerTpl(\"服务名\") {\n    \n    @EL(name = \"sys.starting\", async = true)\n    void start() {\n        // 初始化服务\n    }\n})\n```\n\n### bean注入 @Inject(name = \"beanName\")\n\u003e 注入匹配规则: (已经存在值则不需要再注入)\n\u003e \u003e 1. 如果 @Inject name 没配置\n\u003e \u003e \u003e 先按 字段类型 和 字段名 匹配, 如无匹配 再按 字段类型 匹配\n\u003e \u003e 2. 则按 字段类型 和 @Inject(name = \"beanName\") beanName 匹配\n```java\napp.addSource(new ServerTpl() {\n    @Inject Repo repo;  //自动注入\n\n    @EL(name = \"sys.started\", async = true)\n    void init() {\n        List\u003cMap\u003e rows = repo.rows(\"select * from test\")\n        log.info(\"========= {}\", rows);\n    }\n});\n```\n\n### 动态bean获取: 方法 bean(Class bean类型, String bean名字)\n```java\napp.addSource(new ServerTpl() {\n    @EL(name = \"sys.started\", async = true)\n    void start() {\n        String str = bean(Repo.class).firstRow(\"select count(1) as total from test\").get(\"total\").toString()；\n        log.info(\"=========\" + str);\n    }\n});\n```\n\n### bean依赖注入原理\n\u003e 两种bean容器: AppContext是全局bean容器, 每个服务(ServerTpl)都是一个bean容器\n\u003e \u003e 获取bean对象: 先从全局查找, 再从每个服务中获取\n\n* 暴露全局bean\n  ```java\n  app.addSource(new TestService());\n  ```\n* 服务(ServerTpl)里面暴露自己的bean\n  ```java\n  Repo repo = new Repo(\"jdbc:mysql://localhost:3306/test?user=root\u0026password=root\").init();\n  exposeBean(repo); // 加入到bean容器,暴露给外部使用\n  ```\n\n### 属性直通车\n\u003e 服务(ServerTpl)提供便捷方法获取配置.包含: getLong, getInteger, getDouble, getBoolean等\n```properties\n## app.properties\ntestSrv.prop1=1\ntestSrv.prop2=2.2\n```\n```java\napp.addSource(new ServerTpl(\"testSrv\") {\n    @EL(name = \"sys.starting\")\n    void init() {\n        log.info(\"print prop1: {}, prop2: {}\", getInteger(\"prop1\"), getDouble(\"prop2\"));    \n    }\n})\n```\n\n### 对应上图的两种任务执行\n#### 异步任务\n```java\nasync(() -\u003e {\n    // 异步执行任务\n})\n```\n#### 创建任务对列\n```java\nqueue(\"队列名\", () -\u003e {\n    // 执行任务\n})\n```\n\n## _对列执行器_: Devourer\n会自旋执行完队列中所有任务  \n当需要控制任务最多 一个一个, 两个两个... 的执行时   \n服务基础类(ServerTpl)提供方法创建: queue\n\n### 添加任务到队列\n```java\n// 方法1\nqueue(\"save\", () -\u003e {\n    // 执行任务\n});\n// 方法2\nqueue(\"save\").offer(() -\u003e {\n    // 执行任务\n});\n```\n### 队列特性\n#### 并发控制\n最多同时执行任务数, 默认1(one-by-one)\n```java\nqueue(\"save\").parallel(2)\n```\n\u003e 注: parallel 最好小于 系统最大线程数(sys.exec.maximumPoolSize), 即不能让某一个执行对列占用所有可用的线程\n\n#### 执行速度控制\n把任务按速度均匀分配在时间线上执行  \n支持: 每秒(10/s), 每分(10/m), 每小时(10/h), 每天(10/d)\n```java\n// 例: 按每分钟执行30个任务的频率\nqueue(\"save\").speed(\"30/m\")\n```\n```java\n// 清除速度控制(立即执行)\nqueue(\"save\").speed(null)\n```\n\n#### 队列 暂停/恢复\n```java\n// 暂停执行, 一般用于发生错误时\n// 注: 必须有新的任务入对, 重新触发继续执行. 或者resume方法手动恢复执行\nqueue(\"save\")\n    .errorHandle {ex, me -\u003e\n        // 发生错误时, 让对列暂停执行(不影响新任务入对)\n        // 1. 暂停一段时间\n        me.suspend(Duration.ofSeconds(180));\n        // 2. 条件暂停(每个新任务入队都会重新验证条件)\n        // me.suspend(queue -\u003e true);\n    };\n\n// 手动恢复执行\n// queue(\"save\").resume()\n```\n#### 队列最后任务有效\n是否只使用队列最后一个, 清除队列前面的任务  \n适合: 入队的频率比出队高, 前面的任务可有可无  \n```java\n// 例: increment数据库的一个字段的值\nDevourer q = queue(\"increment\").useLast(true);\nfor (int i = 0; i \u003c 20; i++) {\n    // 入队快, 任务执行慢， 中间的可以不用执行\n    q.offer(() -\u003e repo.execute(\"update test set count=?\", i));\n}\n```\n\n```java\n// 例: 从服务端获取最新的数据\nDevourer q = queue(\"newer\").useLast(true);\n// 用户不停的点击刷新\nq.offer(() -\u003e {\n    Utils.http().get(\"http://localhost:8080/data/newer\").execute();    \n})\n```\n\n#### 原理: 并发流量控制锁 LatchLock\n当被执行代码块需要控制同时线程执行的个数时\n```java\nfinal LatchLock lock = new LatchLock();\nlock.limit(3); // 设置并发限制. 默认为1\nif (lock.tryLock()) { // 尝试获取一个锁\n    try {\n        // 被执行的代码块    \n    } finally {\n        lock.release(); // 释放一个锁\n    }\n}\n```\n\n## 数据库操作工具\n#### 创建一个数据源\n```java\nDB repo = new DB(\"jdbc:mysql://localhost:3306/test?useSSL=false\u0026user=root\u0026password=root\u0026allowPublicKeyRetrieval=true\");\n```\n#### 查询单条记录\n```java\nrepo.row(\"select * from test order by id desc\");\n```\n#### 查询多条记录\n```java\nrepo.rows(\"select * from test limit 10\");\nrepo.rows(\"select * from test where id in (?, ?)\", 2, 7);\n```\n#### 查询单个值\n```java\n// 只支持 Integer.class, Long.class, String.class, Double.class, BigDecimal.class, Boolean.class, Date.class\nrepo.single(\"select count(1) from test\", Integer.class);\n```\n#### 插入一条记录\n```java\nrepo.execute(\"insert into test(name, age, create_time) values(?, ?, ?)\", \"方羽\", 5000, new Date());\n```\n#### 更新一条记录\n```java\nrepo.execute(\"update test set age = ? where id = ?\", 10, 1)\n```\n#### 事务\n```java\n// 执行多条sql语句\nrepo.trans(() -\u003e {\n    // 插入并返回id\n    Object id = repo.insertWithGeneratedKey(\"insert into test(name, age, create_time) values(?, ?, ?)\", \"方羽\", 5000, new Date());\n    repo.execute(\"update test set age = ? where id = ?\", 18, id);\n    return null;\n});\n```\n\n## http客户端\n```java\n// get\nUtils.http().get(\"http://xnatural.cn:9090/test/cus?p2=2\")\n    .header(\"test\", \"test\") // 自定义header\n    .cookie(\"sessionId\", \"xx\") // 自定义 cookie\n    .connectTimeout(5000) // 设置连接超时 5秒\n    .readTimeout(15000) // 设置读结果超时 15秒\n    .param(\"p1\", 1) // 添加参数\n    .debug().execute();\n```\n```java\n// post\nUtils.http().post(\"http://xnatural.cn:9090/test/cus\")\n    .debug().execute();\n```\n```java\n// post 表单\nUtils.http().post(\"http://xnatural.cn:9090/test/form\")\n    .param(\"p1\", \"p1\")\n    .debug().execute();\n```\n```java\n// post 上传文件\nUtils.http().post(\"http://xnatural.cn:9090/test/upload\")\n    .param(\"file\", new File(\"d:/tmp/1.txt\"))\n    .debug().execute();\n\n// post 上传文件流. 一般上传大文件 可配合 汇聚流 使用\nUtils.http().post(\"http://xnatural.cn:9090/test/upload\")\n    .fileStream(\"file\", \"test.md\", new FileInputStream(\"d:/tmp/test.md\"))\n    .debug().execute();\n```\n```java\n// post json\nUtils.http().post(\"http://xnatural.cn:9090/test/json\")\n    .jsonBody(new JSONObject().fluentPut(\"p1\", 1).toString())\n    .debug().execute();\n```\n```java\n// post 普通文本\nUtils.http().post(\"http://xnatural.cn:9090/test/string\")\n    .textBody(\"xxxxxxxxxxxxxxxx\")\n    .debug().execute();\n```\n\n## 对象拷贝器\n#### javabean 拷贝到 javabean\n```java\nUtils.copier(\n      new Object() {\n          public String name = \"徐言\";\n      }, \n      new Object() {\n          private String name;\n          public void setName(String name) { this.name = name; }\n          public String getName() { return name; }\n      }\n).build();\n```\n#### 对象 转换成 map\n```java\nUtils.copier(\n      new Object() {\n          public String name = \"方羽\";\n          public String getAge() { return 5000; }\n      }, \n      new HashMap()\n).build();\n```\n#### 添加额外属性源\n```java\nUtils.copier(\n      new Object() {\n          public String name = \"云仙君\";\n      }, \n      new Object() {\n          private String name;\n          public Integer age;\n          public void setName(String name) { this.name = name; }\n          public String getName() { return name; }\n          \n      }\n).add(\"age\", () -\u003e 1).build();\n```\n#### 忽略属性\n```java\nUtils.copier(\n      new Object() {\n          public String name = \"徐言\";\n          public Integer age = 22;\n      }, \n      new Object() {\n          private String name;\n          public Integer age = 33;\n          public void setName(String name) { this.name = name; }\n          public String getName() { return name; }\n          \n      }\n).ignore(\"age\").build(); // 最后 age 为33\n```\n#### 属性值转换\n```java\nUtils.copier(\n      new Object() {\n          public long time = System.currentTimeMillis();\n      }, \n      new Object() {\n          private String time;\n          public void setTime(String time) { this.time = time; }\n          public String getTime() { return time; }\n          \n      }\n).addConverter(\"time\", o -\u003e new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\").format(new Date((long) o)))\n        .build();\n```\n#### 忽略空属性\n```java\nUtils.copier(\n      new Object() {\n          public String name;\n      }, \n      new Object() {\n          private String name = \"方羽\";\n          public void setName(String name) { this.name = name; }\n          public String getName() { return name; }\n          \n      }\n).ignoreNull(true).build(); // 最后 name 为 方羽\n```\n#### 属性名映射\n```java\nUtils.copier(\n      new Object() {\n          public String p1 = \"徐言\";\n      }, \n      new Object() {\n          private String pp1 = \"方羽\";\n          public void setPp1(String pp1) { this.pp1 = pp1; }\n          public String getPp1() { return pp1; }\n          \n      }\n).mapProp( \"p1\", \"pp1\").build(); // 最后 name 为 徐言\n```\n\n## 文件内容监控器(类linux tail)\n```java\nUtils.tailer().tail(\"d:/tmp/tmp.json\", 5);\n```\n\n## nanoId(长度): nano算法生成动态唯一字符\n```java\nString id = Utils.nanoId();\n```\n\n## ioCopy(输入流, 输出流, 速度)\n```java\n// 文件copy\ntry (InputStream is = new FileInputStream(\"d:/tmp/陆景.png\"); OutputStream os = new FileOutputStream(\"d:/tmp/青子.png\")) {\n    Utils.ioCopy(is, os);\n}\n```\n\n## 简单缓存 CacheSrv\n```java\n// 添加缓存服务\napp.addSource(new CacheSrv());\n```\n```properties\n## app.properties 缓存最多保存100条数据\ncacheSrv.itemLimit=100\n```\n### 缓存操作\n```java\n// 1. 设置缓存\nbean(CacheSrv).set(\"缓存key\", \"缓存值\", Duration.ofMinutes(30));\n// 2. 过期函数\nbean(CacheSrv).set(\"缓存key\", \"缓存值\", record -\u003e {\n    // 缓存值: record.value\n    // 缓存更新时间: record.getUpdateTime()\n    return 函数返回过期时间点(时间缀), 返回null(不过期,除非达到缓存限制被删除);    \n});\n// 3. 获取缓存\nbean(CacheSrv).get(\"缓存key\");\n// 4. 获取缓存值, 并更新缓存时间(即从现在开始重新计算过期时间)\nbean(CacheSrv).getAndUpdate(\"缓存key\");\n// 5. 手动删除\nbean(CacheSrv).remove(\"缓存key\");\n```\n\n### hash缓存操作\n```java\n// 1. 设置缓存\nbean(CacheSrv).hset(\"缓存key\", \"数据key\", \"缓存值\", Duration.ofMinutes(30));\n// 2. 过期函数\nbean(CacheSrv).hset(\"缓存key\", \"数据key\", \"缓存值\", record -\u003e {\n    // 缓存值: record.value\n    // 缓存更新时间: record.getUpdateTime()\n    return 函数返回过期时间点(时间缀), 返回null(不过期,除非达到缓存限制被删除);    \n});\n// 3. 获取缓存\nbean(CacheSrv).hget(\"缓存key\", \"数据key\");\n// 4. 获取缓存值, 并更新缓存时间(即从现在开始重新计算过期时间)\nbean(CacheSrv).hgetAndUpdate(\"缓存key\", \"数据key\");\n// 5. 手动删除\nbean(CacheSrv).hremove(\"缓存key\", \"数据key\");\n```\n\n## 无限递归优化实现 Recursion\n\u003e 解决java无尾递归替换方案. 例:\n  ```java\n  System.out.println(factorialTailRecursion(1, 10_000_000).invoke());\n  ```\n  ```java\n  /**\n   * 阶乘计算\n   * @param factorial 当前递归栈的结果值\n   * @param number 下一个递归需要计算的值\n   * @return 尾递归接口,调用invoke启动及早求值获得结果\n   */\n  Recursion\u003cLong\u003e factorialTailRecursion(final long factorial, final long number) {\n      if (number == 1) {\n          // new Exception().printStackTrace();\n          return Recursion.done(factorial);\n      }\n      else {\n          return Recursion.call(() -\u003e factorialTailRecursion(factorial + number, number - 1));\n      }\n  }\n  ```\n\u003e 备忘录模式:提升递归效率. 例:\n  ```java\n  System.out.println(fibonacciMemo(47));\n  ```\n  ```java\n  /**\n   * 使用同一封装的备忘录模式 执行斐波那契策略\n   * @param n 第n个斐波那契数\n   * @return 第n个斐波那契数\n   */\n  long fibonacciMemo(long n) {\n      return Recursion.memo((fib, number) -\u003e {\n          if (number == 0 || number == 1) return 1L;\n          return fib.apply(number-1) + fib.apply(number-2);\n      }, n);\n  }\n  ```\n\n\u003c!--\n参照: \n  - https://www.cnblogs.com/invoker-/p/7723420.html\n  - https://www.cnblogs.com/invoker-/p/7728452.html\n--\u003e\n\n## 延迟对象 Lazier\n\u003e 封装是一个延迟计算值(只计算一次)\n```java\nfinal Lazier\u003cString\u003e _id = new Lazier\u003c\u003e(() -\u003e {\n    String id = getHeader(\"X-Request-ID\");\n    if (id != null \u0026\u0026 !id.isEmpty()) return id;\n    return UUID.randomUUID().toString().replace(\"-\", \"\");\n});\n```\n* 延迟获取属性值\n  ```java\n  final Lazier\u003cString\u003e _name = new Lazier\u003c\u003e(() -\u003e getAttr(\"sys.name\", String.class, \"app\"));\n  ```\n* 重新计算\n  ```java\n  final Lazier\u003cInteger\u003e _num = new Lazier(() -\u003e new Random().nextInt(10));\n  _num.get();\n  _num.clear(); // 清除重新计算\n  _num.get();\n  ```\n\n\n## 应用例子\n最佳实践: [Demo(java)](https://gitee.com/xnat/appdemo)\n, [Demo(scala)](https://gitee.com/xnat/tinyscalademo)\n, [GRule(groovy)](https://gitee.com/xnat/grule)\n\n\n# 1.1.9 ing\n- [x] fix: Utils#nanoId(0) 卡死的问题\n- [x] feat: Httper 工具支持 get 传body\n- [ ] upgrade: enet:1.1.1\n- [ ] refactor: 心跳新配置 60~180\n- [ ] feat: 空闲任务\n- [ ] feat: 增加日志级别配置\n- [ ] fix: Copier is开头的属性被忽略了\n- [ ] feat: Httper 工具支持 websocket\n- [ ] feat: 自定义注解\n\n\n# 参与贡献\n\nxnatural@msn.cn\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxnat9%2Ftiny","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxnat9%2Ftiny","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxnat9%2Ftiny/lists"}