diff --git a/.gitignore b/.gitignore
index 35a9e54b8e9c5c7ab54379d3af9351d37864d2dd..11bcd0b69d2d3300ae30151792138a868ae3937a 100755
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,10 @@ bin/
lib/
target/
+node_modules/
+dist/
+package-lock.json
+
# Package Files #
*.jar
*.war
diff --git a/misc/package.json b/misc/package.json
new file mode 100755
index 0000000000000000000000000000000000000000..4bb945e4b2ce92d9d71161afc7d8aca12530dbe7
--- /dev/null
+++ b/misc/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "site",
+ "private": true,
+ "scripts": {
+ "build": "vuepress build site",
+ "dev": "vuepress dev site",
+ "lint-md": "yarn lint-md:style && yarn lint-md:wording",
+ "lint-md:style": "remark --quiet --frail .",
+ "lint-md:wording": "textlint ./site/**/*.md",
+ "help": "vuepress --help",
+ "info": "vuepress info site"
+ },
+ "devDependencies": {
+ "@textlint-rule/textlint-rule-no-unmatched-pair": "^1.0.7",
+ "@vuepress/plugin-back-to-top": "^1.2.0",
+ "@vuepress/plugin-google-analytics": "^1.2.0",
+ "@vuepress/plugin-medium-zoom": "^1.2.0",
+ "@vuepress/plugin-pwa": "^1.2.0",
+ "@vuepress/theme-vue": "^1.2.0",
+ "remark-cli": "^7.0.0",
+ "remark-lint": "^6.0.5",
+ "remark-preset-lint-consistent": "^2.0.3",
+ "remark-preset-lint-recommended": "^3.0.3",
+ "textlint": "^11.3.1",
+ "textlint-filter-rule-comments": "^1.2.2",
+ "textlint-rule-apostrophe": "^1.0.0",
+ "textlint-rule-common-misspellings": "^1.0.1",
+ "textlint-rule-diacritics": "^1.0.0",
+ "textlint-rule-en-capitalization": "^2.0.2",
+ "textlint-rule-stop-words": "^1.0.17",
+ "textlint-rule-terminology": "^1.1.30",
+ "textlint-rule-write-good": "^1.6.2",
+ "vue-toasted": "^1.1.25",
+ "vuepress": "^1.2.0",
+ "vuepress-plugin-flowchart": "^1.4.2"
+ }
+}
diff --git a/misc/site/.vuepress/config.js b/misc/site/.vuepress/config.js
new file mode 100755
index 0000000000000000000000000000000000000000..2500a99a674f61fc535159ee1cb29af7ca021e70
--- /dev/null
+++ b/misc/site/.vuepress/config.js
@@ -0,0 +1,49 @@
+module.exports = {
+ title: 'YMP',
+ lang: 'zh-CN',
+ description: '一个轻量级、模块化、简单而强大的Java应用程序开发框架。',
+ head: [
+ ['link', {rel: 'icon', href: '/logo.png'}],
+ ['meta', {name: 'theme-color', content: '#3eaf7c'}]
+ ],
+ themeConfig: {
+ title: null,
+ logo: '/logo.png',
+ repoLabel: '查看源码',
+ smoothScroll: true,
+ editLinks: false,
+ nav: require('./nav'),
+ sidebar: {
+ '/guide/': buildGuideSidebar('指南')
+ }
+ },
+ plugins: [
+ ['@vuepress/back-to-top', true],
+ ['@vuepress/medium-zoom', true],
+ require('./plugin-stat/stat')
+ ]
+};
+
+function buildGuideSidebar(title) {
+ return [
+ {
+ title: title,
+ collapsable: false,
+ children: [
+ '',
+ 'core',
+ 'configuration',
+ 'log',
+ 'persistence/',
+ 'persistence/jdbc',
+ 'persistence/mongodb',
+ 'persistence/redis',
+ 'plugin',
+ 'serv',
+ 'validation',
+ 'cache',
+ 'webmvc'
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/misc/site/.vuepress/nav.js b/misc/site/.vuepress/nav.js
new file mode 100755
index 0000000000000000000000000000000000000000..3cf3a36887c490d7740681d351ecba130fe48e07
--- /dev/null
+++ b/misc/site/.vuepress/nav.js
@@ -0,0 +1,56 @@
+module.exports = [
+ {
+ text: '快速上手',
+ link: '/quickstart'
+ },
+ {
+ text: '指南',
+ link: '/guide/'
+ },
+ {
+ text: '了解更多',
+ ariaLabel: '了解更多',
+ items: [
+ {
+ text: '相关文档',
+ items: [
+ {
+ text: '接口开发技术规范',
+ link: '/dev_spec.html'
+ }
+ ]
+ },
+ {
+ text: '相关视频',
+ items: [
+ {
+ text: '基于YMP框架快速搭建JavaWeb工程',
+ link: 'http://v.youku.com/v_show/id_XMzU2NzQyODI4NA==.html?spm=a2h3j.8428770.3416059.1'
+ },
+ {
+ text: 'CentOS服务器应用实战——服务环境快速搭建',
+ link: 'http://v.youku.com/v_show/id_XMzYxODg1NTYxMg==.html?spm=a2h3j.8428770.3416059.1'
+ }
+ ]
+ }
+ ]
+ },
+ {
+ text: '查看源码',
+ ariaLabel: '查看源码',
+ items: [
+ {
+ items: [
+ {
+ text: 'Gitee(码云)',
+ link: 'https://gitee.com/suninformation/ymate-platform-v2'
+ },
+ {
+ text: 'GitHub',
+ link: 'https://github.com/suninformation/ymate-platform-v2'
+ }
+ ]
+ }
+ ]
+ }
+];
\ No newline at end of file
diff --git a/misc/site/.vuepress/plugin-stat/enhanceAppFile.js b/misc/site/.vuepress/plugin-stat/enhanceAppFile.js
new file mode 100755
index 0000000000000000000000000000000000000000..6d628da40afb22cb25f9427e25434e315b6d00b5
--- /dev/null
+++ b/misc/site/.vuepress/plugin-stat/enhanceAppFile.js
@@ -0,0 +1,29 @@
+export default ({router}) => {
+ if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
+ const script = document.createElement('script');
+ script.src = 'https://s4.cnzz.com/z_stat.php?id=1254908110&web_id=1254908110';
+ script.language = 'JavaScript';
+ document.body.appendChild(script);
+
+ router.afterEach(function (to) {
+ if (to.path) {
+ if (window._czc) {
+ window._czc.push(['_trackPageview', to.fullPath, '/'])
+ }
+ //
+ const _hmt = _hmt || [];
+ window._hmt = _hmt;
+ (function () {
+ document.getElementById('baidu_stat') && document.getElementById('baidu_stat').remove();
+ //
+ const hm = document.createElement("script");
+ hm.id = 'baidu_stat';
+ hm.src = 'https://hm.baidu.com/hm.js?d732d09a2ccea77b26ad0581cd9bd91c';
+ const s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(hm, s);
+ })();
+ }
+ })
+ }
+}
+
diff --git a/misc/site/.vuepress/plugin-stat/stat.js b/misc/site/.vuepress/plugin-stat/stat.js
new file mode 100755
index 0000000000000000000000000000000000000000..644c297013b1d366964aa25af26b4ed69a326965
--- /dev/null
+++ b/misc/site/.vuepress/plugin-stat/stat.js
@@ -0,0 +1,5 @@
+const { path } = require('@vuepress/shared-utils');
+
+module.exports = (options = {}, context) => ({
+ enhanceAppFiles: path.resolve(__dirname, 'enhanceAppFile.js')
+});
\ No newline at end of file
diff --git a/misc/site/.vuepress/public/logo.png b/misc/site/.vuepress/public/logo.png
new file mode 100755
index 0000000000000000000000000000000000000000..fa4f865c72b6a15cbe67f52f9b9c18efee56b7cd
Binary files /dev/null and b/misc/site/.vuepress/public/logo.png differ
diff --git a/misc/site/.vuepress/public/logo_big.png b/misc/site/.vuepress/public/logo_big.png
new file mode 100755
index 0000000000000000000000000000000000000000..bfa10f3de574f44154a9d21ed43f12c368bda590
Binary files /dev/null and b/misc/site/.vuepress/public/logo_big.png differ
diff --git a/misc/site/.vuepress/public/logo_small.png b/misc/site/.vuepress/public/logo_small.png
new file mode 100755
index 0000000000000000000000000000000000000000..bd5fdda92bc2972aff5b54c2fbb46a972ee1d4c9
Binary files /dev/null and b/misc/site/.vuepress/public/logo_small.png differ
diff --git a/misc/site/.vuepress/public/structure_diagram.png b/misc/site/.vuepress/public/structure_diagram.png
new file mode 100755
index 0000000000000000000000000000000000000000..85d1b1eb131fa5015b472eeb42404d5a8e7c573c
Binary files /dev/null and b/misc/site/.vuepress/public/structure_diagram.png differ
diff --git a/misc/site/README.md b/misc/site/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..ce0c3b308b63bc304bc0efafc5ee8d23f6ba37a6
--- /dev/null
+++ b/misc/site/README.md
@@ -0,0 +1,21 @@
+---
+home: true
+heroImage: /logo_big.png
+heroText: null
+tagline: 轻量级、组件化、简单高效的Java应用开发框架
+actionText: 快速上手
+actionLink: /quickstart
+features:
+- title: 轻量级
+ details: 采用微内核实现AutoScan、AOP、IoC、Event等特性,涵盖SSH框架中绝大部分核心功能!
+- title: 组件化
+ details: 采用模块方式打包,按需装配,灵活扩展,独特的服务开发体验,完善的插件机制,助力于更细颗粒度的业务拆分!
+- title: 简单高效
+ details: 统一日志系统和配置体系结构,轻量的持久层封装,灵活的缓存服务,配置简单的MVC和参数验证,让您更专注于业务!
+footer: Apache License Version 2.0 | Copyright © 2015-2019 yMate.Net. All Rights Reserved.
+---
+
+
+::: tip 提示
+当前版本 = `2.0.8`
+:::
diff --git a/misc/site/dev_spec.md b/misc/site/dev_spec.md
new file mode 100755
index 0000000000000000000000000000000000000000..5b51c649ad2823b053fa4fbeb1b9ba390f16742d
--- /dev/null
+++ b/misc/site/dev_spec.md
@@ -0,0 +1,112 @@
+---
+sidebar: auto
+sidebarDepth: 2
+---
+
+# 接口开发技术规范
+
+## 基础数据类型约定
+
+|类型名称|Java类型|字段类型|说明|
+|---|---|---|---|
+|布尔型|Integer|smallint(1)|用`1`表示`true`,用`0`表示`false`|
+|日期时间型|Long|bigint(13)|用毫秒数值表示,长度为`13`位|
+|金额 / 货币型|Long|bigint|采用整数形式存储(即去除小数,以`分/厘`为单位)|
+
+## 数据库及表字段命名原则
+
+表名称格式:[`前缀_`]<`名称段1`[`_名称段n`]>
+
+字段名称格式:[`is_`]<`名称段1`[`_名称段n`]>
+
+说明:
+
+- 数据库表和字段名称由`A-Z`,`a-z`,`0-9`和`_`下划线组成,尽量避免出现数字,多个单词之间用`_`下划线分隔,单词不允许使用复数形式;
+- 数据库表和字段名称除数据库特殊情况外,应尽量采用小写英文单词或英文短语或相应缩写,禁止使用汉语拼音和汉字;
+- 数据表主键名称尽量使用`id`,避免使用复合主键,建议使用索引替代;
+- 布尔型字段名称以`is_`作为前缀,如:`is_deleted`;
+- 数据库中字段应预留`create_time`和`last_modify_time`作为版本字段;
+
+示例:
+
+- 数据表名称:
+
+ ymate_user
+ ymate_user_attribute
+
+- 字段名称:
+
+ is_deleted
+
+ create_time
+ last_modify_time
+
+## 接口通用签名规则
+
+- 将所有发送或者接收到的数据放入集合中,将集合中的参数名称按`ASCII`码从小到大(字典序)排序;
+- 参数值为空不参与签名;
+- 参数名称区分大小写且参数内容不要进行`URLEncoder`编码处理;
+- 签名参数(如:`signature`)本身不参与签名;
+- 将参与签名的参数以URL键值对的格式进行拼接,示例如下:
+
+ String _signStr = "client_id=CLIENT_ID&create_time=1487178385184&event=subscribe&qrscene=QRSCENE&union_id=o6_bmasdasdsad6_2sgVt7hMZOPfL"
+
+- 将拼接好的`_signStr`字符串与`client_secret`密钥进行拼接并生成签名,示例如下:
+
+ // 拼接密钥
+ _signStr = _signStr + "&client_secret=6bf18fa2f9a136273fb90e58dff4a964";
+ // 执行MD5签名并将字符转换为大写
+ _signStr = MD5(_signStr).toUpperCase();
+
+- 如果有必要,可以在请求参数集合中增加随机参数(如:`nonce_str`),通过随机数函数生成并转换为字符串,从而保证签名的不可预测性;
+- 客户端与服务端均采用相同规则进行参数签名后进行结果比对,两端签名结果一致则验签通过;
+
+## 接口响应报文基础结构
+
+基础报文示例一:
+
+ {
+ "ret": -1,
+ "msg": "参数验证无效",
+ "data": {
+ "username": "用户名称不能为空",
+ "passwd": "登录密码不能为空"
+ }
+ }
+
+基础报文示例二:
+
+ { "ret": -50, "msg": "系统忙,请稍后重试" }
+
+参数说明:
+
+|参数|类型|是否必须|说明|
+|---|---|---|---|
+|ret|int|是|返回码|
+|msg|string|否|消息内容,当`ret!=0`则此项为必须项|
+|data|object|否|业务数据内容,根据业务逻辑决定其具体数据类型及是否为必须项|
+
+## 全局返回码说明
+
+> `ret = 0`:正确返回;
+>
+> `ret > 0`:具体业务相关的接口调用错误;
+>
+> `-50 < ret < 0`:接口调用不能通过接口服务校验;
+>
+> `ret <= -50`:系统内部错误;
+
+全局返回码:
+
+|返回码|说明|
+|---|---|
+|0|请求成功|
+|-1|参数验证无效|
+|-2|访问的资源未找到或不存在|
+|-3|请求的方法不支持或不正确|
+|-4|请求的资源未授权或无权限|
+|-5|用户会话无效或超时|
+|-6|请求的操作被禁止|
+|-7|用户会话已授权(登录)|
+|-20|数据版本不匹配|
+|-50|系统内部错误|
\ No newline at end of file
diff --git a/misc/site/guide/README.md b/misc/site/guide/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..f22a1696f0c715ef870661fa9ccc5c1c4d8e9b10
--- /dev/null
+++ b/misc/site/guide/README.md
@@ -0,0 +1,137 @@
+---
+sidebarDepth: 2
+---
+
+# 概述
+
+
+
+> A lightweight modular simple and powerful Java application development framework.
+
+> 一个非常简单、易用的一套轻量级Java应用开发框架,设计原则主要侧重于简化工作任务、规范开发流程、提高开发效率,让开发工作像搭积木一样轻松是我们一直不懈努力的目标!
+
+## 技术特点
+
+> - 采用组件化、模块方式打包,可按需装配,灵活可扩展;
+> - 采用微内核实现`AutoScan`、`AOP`、`IoC`、`Event`等,涵盖`SSH&M`框架中绝大部分核心功能;
+> - 统一配置体系结构,感受不一样的文件资源配置及管理模式;
+> - 整合多种日志系统(`Log4j`、`JCL`、`Slf4j`等)、日志文件可分离存储;
+> - 轻量级持久化层封装,针对`RDBMS`(`MySQL`、`SQLServer`、`Oracle`、`PostgreSQL`等)和`NoSQL`(`MongoDB`、`Redis`等)提供支持;
+> - 完善的插件机制,助力于更细颗粒度的业务拆分;
+> - 独特的独立服务开发体验;
+> - 功能强大的验证框架,完全基于`Java`注解,易于使用和扩展;
+> - 灵活的缓存服务,支持`EhCache`、`Redi`s和多级缓存(`MultiLevel`)技术;
+> - 配置简单的`MVC`架构,强大且易于维护和扩展,支持`RESTful`风格,支持`JSP`、`HTML`、`Binary`、`Freemarker`、`Velocity`、`Beetl`等多种视图技术;
+
+## 模块及功能
+
+`YMP`框架主要是由核心(`Core`)和若干模块(`Modules`)组成,整体结构简约、清晰,如图所示:
+
+
+
+### 核心(Core)
+
+主要负责框架的初始化和模块的加载及其生命周期管理,功能包括:
+
+> - `Beans`:类对象管理器(微型的`Spring`容器),提供包类的自动扫描(`AutoScan`)以及类生命周期管理、依赖注入(`IoC`)和方法拦截(`AOP`)等特性;
+> - `Event`:事件服务,通过事件注册和广播的方式触发和监听事件动作,并支持同步和异步两种模式执行事件队列;
+> - `Module`:模块(是`YMP`框架所有功能特性封装的基础形式),负责模块的生命周期管理,模块将在框架初始化时自动加载并初始化,在框架销毁时自动销毁;
+> - `I18N`:国际化资源管理器,提供统一的资源文件加载、销毁和内容读取,支持自定义资源加载和语言变化的事件监听;
+> - `Lang`:提供了一组自定义的数据结构,它们在部分模块中起到了重要的作用,包括:
+> + `BlurObject`:用于解决常用数据类型间转换的模糊对象;
+> + `PairObject`:用于将两个独立的对象捆绑在一起的结对对象;
+> + `TreeObject`:使用级联方式存储各种数据类型,不限层级深度的树型对象;
+> - `Util`:提供框架中需要的各种工具类;
+
+### 配置体系 (Configuration)
+
+通过简单的目录结构实现在项目开发以及维护过程中,对配置文件等各种资源的统一管理,为模块化开发和部署提供灵活的、简单有效的解决方案:
+
+> - 从开发角度规范了模块化开发流程、统一资源文件的生命周期管理;
+> - 从可维护角度将全部资源集成在整个体系中,具备有效的资源重用和灵活的系统集成构建、部署和数据备份与迁移等优势;
+> - 简单的配置文件检索、加载及管理模式;
+> - 模块间资源共享,模块(`modules`)可以共用所属项目(`projects`)的配置、类和`JAR`包等资源文件;
+> - 默认支持`XML`和`Properties`配置文件解析,可以通过`IConfigurationProvider`接口自定义文件格式,支持缓存,避免重复加载;
+> - 配置对象支持`@Configuration`注解方式声明,无需编码即可自动加载并填充配置内容到类对象;
+> - 修改配置文件无需重启服务,支持自动重新加载;
+> - 集成模块的构建(编译)与分发、服务的启动与停止,以及清晰的资源文件分类结构可快速定位;
+
+### 日志 (Log)
+
+基于开源日志框架`Log4j 2`实现,提供对日志记录器对象的统一管理,可以在任意位置调用任意日志记录器输出日志,实现系统与业务日志的分离,并针对`apache-commons-logging`日志框架和`Slf4j`日志系统提供支持;
+
+### 持久化 (Persistence)
+
+#### JDBC
+
+针对关系型数据库(`RDBMS`)数据存取的一套简单解决方案,主要关注数据存取的效率、易用性和透明,其具备以下功能特性:
+
+> - 基于`JDBC`框架`API`进行轻量封装,结构简单、便于开发、调试和维护;
+> - 优化批量数据更新、标准化结果集、预编译`SQL`语句处理;
+> - 支持单实体`ORM`操作,无需编写`SQL`语句;
+> - 提供脚手架工具,快速生成数据实体类,支持链式调用;
+> - 支持通过存储器注解自定义`SQL`语句或从配置文件中加载`SQL`并自动执行;
+> - 支持结果集与值对象的自动装配,支持自定义装配规则;
+> - 支持多数据源,默认支持`C3P0`、`DBCP`、`JND`I连接池配置,支持数据源扩展;
+> - 支持多种数据库(如:`Oracle`、`MySQL`、`SQLServer`、`SQLite`、`H2`、`PostgreSQL`等);
+> - 支持面向对象的数据库查询封装,有助于减少或降低程序编译期错误;
+> - 支持数据库事务嵌套;
+> - 支持数据库视图和存储过程;
+
+#### MongoDB
+
+针对`MongoDB`的数据存取操作的特点,以`JDBC`模块的设计思想进行简单封装,采用会话机制,支持多数据源配置和实体操作、基于对象查询、`MapReduce`、`GridFS`、聚合及函数表达式集成等;
+
+#### Redis
+
+基于`Jedis`驱动封装,以`JDBC`模块的设计思想进行简单封装,采用会话机制,简化订阅(`subscribe`)和发布(`publish`)处理,支持多数据源及连接池配置,支持`jedis`、`shard`、`sentinel`和`cluster`等数据源连接方式;
+
+### 插件 (Plugin)
+
+采用独立的`ClassLoader`类加载器来管理私有`JAR`包、类、资源文件等,设计目标是在接口开发模式下,将需求进行更细颗粒度拆分,从而达到一个理想化可重用代码的封装形态;
+
+每个插件都是封闭的世界,插件与外界之间沟通的唯一方法是通过业务接口调用,管理这些插件的容器被称之为插件工厂(`IPluginFactory`),负责插件的分析、加载和初始化,以及插件的生命周期管理,插件模块支持创建多个插件工厂实例,工厂对象之间完全独立,无任何依赖关系;
+
+### 服务 (Serv)
+
+基于`NIO`实现的通讯服务框架,提供`TCP`、`UDP`协议的客户端(`Client`)与服务端(`Server`)封装,灵活的消息监听与消息内容编/解码,简约的配置使二次开发更加便捷;
+
+同时默认提供服务端会话管理和客户端断线重连、链路维护(心跳)等服务支持,您只需了解业务即可轻松完成开发工作;
+
+### 验证 (Validation)
+
+服务端参数有效性验证工具,采用注解声明方式配置验证规则,更简单、更直观、更友好,支持方法参数和类成员属性验证,支持验证结果国际化`I18N`资源绑定,支持自定义验证器,支持多种验证模式;
+
+### 缓存 (Cache)
+
+以`EhCache`作为默认`JVM`进程内缓存服务,通过整合外部`Redis`服务实现多级缓存(`MultiLevel`)的轻量级缓存框架,并与`YMP`框架深度集成(支持针对类方法的缓存,可以根据方法参数值进行缓存),灵活的配置、易于使用和扩展;
+
+### WebMVC
+
+`WebMVC`模块在`YMP`框架中是除了`JDBC`模块以外的另一个非常重要的模块,集成了`YMP`框架的诸多特性,在功能结构的设计和使用方法上依然保持一贯的简单风格,同时也继承了主流MVC框架的基因,对于了解和熟悉`SSH&M`等框架技术的开发人员来说,上手极其容易,毫无学习成本;
+
+其主要功能特性如下:
+
+> - 标准`MVC`实现,结构清晰,完全基于注解方式配置简单;
+> - 支持约定(`Conversion`)模式,无需编写控制器代码,直接匹配并执行视图;
+> - 支持多种视图技术(`JSP`、`Freemarker`、`Velocity`、`Text`、`HTML`、`JSON`、`Binary`、`Forward`、`Redirect`、`HttpStatus`、`Beetl`等);
+> - 支持`RESTful`模式及`URL`风格;
+> - 支持请求参数与控制器方法参数的自动绑定;
+> - 支持参数有效性验证;
+> - 支持控制器方法的拦截;
+> - 支持注解配置控制器请求路由映射;
+> - 支持自动扫描控制器类并注册;
+> - 支持事件和异常的自定义处理;
+> - 支持`I18N`资源国际化;
+> - 支持控制器方法和视图缓存;
+> - 支持控制器参数转义;
+> - 支持插件扩展;
+
+::: tip One More Thing
+
+`YMP`不仅提供便捷的`Web`及其它`Java`项目的快速开发体验,也将不断提供更多丰富的项目实践经验。
+
+感兴趣的小伙伴儿们可以加入 官方`QQ`群`480374360`,一起交流学习,帮助`YMP`成长!
+
+了解更多有关`YMP`框架的内容,请访问官网:[https://ymate.net](https://ymate.net)
+:::
diff --git a/misc/site/guide/cache.md b/misc/site/guide/cache.md
new file mode 100755
index 0000000000000000000000000000000000000000..6d285541083dd8172d36a8760e5da2751895c26c
--- /dev/null
+++ b/misc/site/guide/cache.md
@@ -0,0 +1,234 @@
+---
+sidebarDepth: 2
+---
+
+# 缓存(Cache)
+
+缓存模块是以EhCache作为默认JVM进程内缓存服务,通过整合外部Redis服务实现多级缓存(MultiLevel)的轻量级缓存框架,并与YMP框架深度集成(支持针对类方法的缓存,可以根据方法参数值进行缓存),灵活的配置、易于使用和扩展;
+
+## Maven包依赖
+
+
+ net.ymate.platform
+ ymate-platform-cache
+
+
+
+> **注**:
+> - 在项目的pom.xml中添加上述配置,该模块已经默认引入核心包依赖,无需重复配置。
+> - 若需要启用redis作为缓存服务,请添加以下依赖配置:
+>
+>
+> net.ymate.platform
+> ymate-platform-persistence-redis
+>
+>
+
+## 基础接口概念
+
+开发者可以根据以下接口完成对缓存模块的自定义扩展实现;
+
+- 缓存服务提供者(ICacheProvider)接口:
+
+ + DefaultCacheProvider - 基于EhCache缓存服务的默认缓存服务提供者接口实现类;
+ + RedisCacheProvider - 基于Redis数据库的缓存服务提供者接口实现类;
+ + MultievelCacheProvider - 融合EhCache和Redis两者的缓存服务提供者接口实现类,通过MultilevelKey决定缓存对象的获取方式;
+
+- 缓存Key生成器(IKeyGenerator)接口:
+
+ + DefaultKeyGenerator - 根据提供的类方法和参数对象生成缓存Key,默认是将方法和参数对象进行序列化后取其MD5值;
+
+- 序列化服务(ISerializer)接口:
+
+ + DefaultSerializer - 默认序列化服务采用JDK自带的对象序列化技术实现;
+
+- 缓存事件监听(ICacheEventListener)接口:用于监听被缓存对象发生变化时的事件处理,需开发者实现接口;
+
+- 缓存作用域处理器(ICacheScopeProcessor)接口:用于处理@Cacheable注解的Scope参数设置为非DEFAULT作用域的缓存对象,需开发者实现接口;
+
+## 模块配置
+
+### 初始化参数配置
+
+ #-------------------------------------
+ # 缓存模块初始化参数
+ #-------------------------------------
+
+ # 缓存提供者,可选参数,默认值为default,目前支持[default|redis|multilevel]或自定义类名称
+ ymp.configs.cache.provider_class=
+
+ # 缓存对象事件监听器,可选参数,默认值为net.ymate.platform.cache.impl.DefaultCacheEventListener
+ ymp.configs.cache.event_listener_class=
+
+ # 缓存作用域处理器,可选参数,默认值为空
+ ymp.configs.cache.scope_processor_class=
+
+ # 缓存Key生成器,可选参数,默认采用框架默认net.ymate.platform.cache.impl.DefaultKeyGenerator
+ ymp.configs.cache.key_generator_class=
+
+ # 对象序列化接口实现,可选参数,默认值为ISerializer.SerializerManager.getDefaultSerializer()
+ ymp.configs.cache.serializer_class=
+
+ # 默认缓存名称,可选参数,默认值为default,对应于Ehcache配置文件中设置name="__DEFAULT__"
+ ymp.configs.cache.default_cache_name=
+
+ # 缓存数据超时时间,可选参数,数值必须大于等于0,为0表示默认缓存300秒
+ ymp.configs.cache.default_cache_timeout=
+
+ # 是否采用SET进行缓存数据存储,默认值为false
+ ymp.params.cache.storage_with_set=
+
+ # 禁用Redis订阅缓存元素过期事件,可选参数,默认值为false
+ ymp.params.cache.disabled_subscribe_expired=
+
+ # Multilevel模式下是否自动同步Master和Slave级缓存,可选扩展参数, 默认值为false
+ ymp.params.cache.multilevel_slave_autosync=
+
+### EhCache配置示例
+
+请将以下内容保存在ehcache.xml文件中,并放置在classpath根路径下;
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 模块事件
+
+当`event_listener_class`采用默认配置时,可以通过CacheEvent捕获缓存事件,事件枚举对象包括以下事件类型:
+
+|事务类型|说明|
+|---|---|
+|ELEMENT_PUT|添加元素到缓存|
+|ELEMENT_UPDATED|缓存元素更新|
+|ELEMENT_EXPIRED|缓存元素过期|
+|ELEMENT_EVICTED||
+|ELEMENT_REMOVED|缓存元素删除|
+|ELEMENT_REMOVED_ALL|缓存清空|
+
+## 通过代码手工初始化模块示例
+
+ // 创建YMP实例
+ YMP owner = new YMP(ConfigBuilder.create(
+ // 设置缓存模块配置
+ ModuleCfgProcessBuilder.create().putModuleCfg(CacheModuleConfigurable.create()
+ .defaultCacheName("default")
+ .defaultCacheTimeout(7200)
+ .serializerClass("default")
+ .providerClass(ICache.DEFAULT)).build())
+ .proxyFactory(new DefaultProxyFactory())
+ .developMode(true)
+ // 扩展参数配置
+ .param(ICacheModuleCfg.PARAMS_CACHE_STORAGE_WITH_SET, "false")
+ .param(ICacheModuleCfg.PARAMS_CACHE_DISABLED_SUBSCRIBE_EXPIRED, "false")
+ .runEnv(IConfig.Environment.PRODUCT).build());
+ // 向容器注册模块
+ owner.registerModule(Caches.class);
+ // 执行框架初始化
+ owner.init();
+
+## 模块使用
+
+### 示例一:直接通过缓存模块操作缓存数据
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ // 操作默认缓存
+ Caches.get().put("key1", "value1");
+ System.out.println(Caches.get().get("key1"));
+ // 操作指定名称的缓存
+ Caches.get().put("default", "key2", "value2");
+ System.out.println(Caches.get().get("default", "key2"));
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+
+**注**:当指定缓存名称时,请确认与名称对应的配置是否已存在;
+
+执行结果:
+
+ value1
+ value2
+
+### 示例二:基于注解完成类方法的缓存
+
+这里用到了@Cacheable注解,作用是标识类中方法的执行结果是否进行缓存,需要注意的是:
+
+> 首先@Cacheable注解必须在已注册到YMP类对象管理器的类上声明,表示该类支持缓存;
+>
+> 其次,在需要缓存执行结果的方法上添加@Cacheable注解;
+
+@Cacheable注解参数说明:
+
+> cacheName:缓存名称, 默认值为default;
+>
+> key:缓存Key, 若未设置则使用keyGenerator自动生成;
+>
+> generator:Key生成器接口实现类,默认为DefaultKeyGenerator.class;
+>
+> scope:缓存作用域,可选值为APPLICATION、SESSION和DEFAULT,默认为DEFAULT,非DEFAULT设置需要缓存作用域处理器(ICacheScopeProcessor)接口配合;
+>
+> timeout:缓存数据超时时间, 可选参数,数值必须大于等于0,为0表示默认缓存300秒;
+
+示例代码:
+
+ @Bean
+ @Cacheable
+ public class CacheDemo {
+
+ @Cacheable
+ public String sayHi(String name) {
+ System.out.println("No Cached");
+ return "Hi, " + name;
+ }
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ CacheDemo _demo = YMP.get().getBean(CacheDemo.class);
+ System.out.println(_demo.sayHi("YMP"));
+ System.out.println(_demo.sayHi("YMP"));
+ //
+ System.out.println("--------");
+ //
+ System.out.println(_demo.sayHi("YMPer"));
+ System.out.println(_demo.sayHi("YMP"));
+ System.out.println(_demo.sayHi("YMPer"));
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+ }
+
+执行结果:
+
+ No Cached
+ Hi, YMP
+ Hi, YMP
+ --------
+ No Cached
+ Hi, YMPer
+ Hi, YMP
+ Hi, YMPer
+
+以上结果输出可以看出,sayHi方法相同参数首次被调用时将输出“No Cached”字符串,说明它没有使用缓存,再次调用时直接从缓存中返回值;
diff --git a/misc/site/guide/configuration.md b/misc/site/guide/configuration.md
new file mode 100755
index 0000000000000000000000000000000000000000..66c768e90d1b168a08175ede02f602e778b5e9c3
--- /dev/null
+++ b/misc/site/guide/configuration.md
@@ -0,0 +1,288 @@
+---
+sidebarDepth: 2
+---
+
+# 配置体系 (Configuration)
+
+配置体系模块,是通过简单的目录结构实现在项目开发以及维护过程中,对配置等各种文件资源的统一管理,为模块化开发和部署提供灵活的、简单有效的解决方案;
+
+## Maven包依赖
+
+
+ net.ymate.platform
+ ymate-platform-configuration
+
+
+
+> **注**:在项目的pom.xml中添加上述配置,该模块已经默认引入核心包依赖,无需重复配置。
+
+## 特点
+
+- 从开发角度规范了模块化开发流程、统一资源文件的生命周期管理;
+- 从可维护角度将全部资源集成在整个体系中,具备有效的资源重用和灵活的系统集成构建、部署和数据备份与迁移等优势;
+- 简单的配置文件检索、加载及管理模式;
+- 模块间资源共享,模块(modules)可以共用所属项目(projects)的配置、类和jar包等资源文件;
+- 默认支持XML和Properties配置文件解析,可以通过IConfigurationProvider接口自定义文件格式,支持缓存,避免重复加载;
+- 配置对象支持`@Configuration`注解方式声明,无需编码即可自动加载并填充配置内容到类对象;
+- 修改配置文件无需重启服务,支持自动重新加载;
+- 集成模块的构建(编译)与分发、服务的启动与停止,以及清晰的资源文件分类结构可快速定位;
+
+## 配置体系目录结构
+
+按优先级由低到高的顺序依次是:全局(configHome) -> 项目(projects) -> 模块(modules):
+
+
+ CONFIG_HOME\
+ |--bin\
+ |--cfgs\
+ |--classes\
+ |--dist\
+ |--lib\
+ |--logs\
+ |--plugins\
+ |--projects\
+ | |--
+ | | |--cfgs\
+ | | |--classes\
+ | | |--lib\
+ | | |--logs\
+ | | |--modules\
+ | | | |--
+ | | | | |--cfgs\
+ | | | | |--classes\
+ | | | | |--lib\
+ | | | | |--logs\
+ | | | | |--plugins\
+ | | | | |--<......>
+ | | | |--<......>
+ | | |--plugins\
+ | |--<......>
+ |--temp\
+ |--......
+
+## 模块配置
+
+配置体系模块初始化参数, 将下列配置项按需添加到ymp-conf.properties文件中, 否则模块将使用默认配置进行初始化:
+
+
+ #-------------------------------------
+ # 配置体系模块初始化参数
+ #-------------------------------------
+
+ # 配置体系根路径,必须绝对路径,前缀支持${root}、${user.home}和${user.dir}变量,默认为${root}
+ ymp.configs.configuration.config_home=
+
+ # 项目名称,做为根路径下级子目录,对现实项目起分类作用,默认为空
+ ymp.configs.configuration.project_name=
+
+ # 模块名称,此模块一般指现实项目中分拆的若干子项目的名称,默认为空
+ ymp.configs.configuration.module_name=
+
+ # 配置文件检查时间间隔(毫秒),默认值为0表示不开启
+ ymp.configs.configuration.config_check_time_interval=
+
+ # 指定配置体系下的默认配置文件分析器,默认为net.ymate.platform.configuration.impl.DefaultConfigurationProvider
+ ymp.configs.configuration.provider_class=
+
+> **注**:配置体系根路径`config_home`配置参数,可以通过`JVM`启动参数方式进行配置,如:`java -jar -Dymp.config_home=...`,这种方式将优先于配置文件。
+
+## 通过代码手工初始化模块示例
+
+ // 创建YMP实例
+ YMP owner = new YMP(ConfigBuilder.create(
+ // 设置配置体系模块配置
+ ModuleCfgProcessBuilder.create().putModuleCfg(
+ ConfigModuleConfigurable.create()
+ .configHome("${root}")
+ .projectName("demo")
+ .moduleName("core")
+ .providerClass(DefaultConfigurationProvider.class)
+ .configCheckTimeInterval(30000L)).build())
+ .proxyFactory(new DefaultProxyFactory())
+ .developMode(true)
+ .runEnv(IConfig.Environment.PRODUCT).build());
+ // 向容器注册模块
+ owner.registerModule(Cfgs.class);
+ // 执行框架初始化
+ owner.init();
+ // 销毁
+ owner.destroy();
+
+## 示例一:解析XML配置
+
+- 基于XML文件的基础配置格式如下, 为了配合测试代码, 请将该文件命名为configuration.xml并放置在`config_home`路径下的cfgs目录里:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ iphone
+ ipad
+ imac
+ itouch
+
+
+
+
+
+ - red
+ - 120g
+ - small
+ - 2015
+
+
+
+
+
+- 新建配置类DemoConfig, 通过`@Configuration`注解指定配置文件相对路径
+
+
+ @Configuration(value = "cfgs/configuration.xml", reload = true)
+ public class DemoConfig extends DefaultConfiguration {
+ }
+
+
+- 测试代码, 完成模块初始化并加载配置文件内容:
+
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ DemoConfig _cfg = new DemoConfig();
+ if (Cfgs.get().fillCfg(_cfg)) {
+ System.out.println(_cfg.getString("company_name"));
+ System.out.println(_cfg.getMap("product_spec"));
+ System.out.println(_cfg.getList("products"));
+ }
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+
+- 执行结果:
+
+
+ Apple Inc.
+ {abc=xzy, color=red, size=small, weight=120g, age=2015}
+ [itouch, imac, ipad, iphone]
+
+## 示例二:解析Properties配置
+
+- 基于Properties文件的基础配置格式如下, 同样请将该文件命名为configuration.properties并放置在`config_home`路径下的cfgs目录里:
+
+
+ #--------------------------------------------------------------------------
+ # 配置文件内容格式: properties..=[propertyValue]
+ #
+ # 注意: attributes将作为关键字使用, 用于表示分类, 属性, 集合和MAP的子属性集合
+ #--------------------------------------------------------------------------
+
+ # 举例1: 默认分类下表示公司名称, 默认分类名称为default
+ properties.default.company_name=Apple Inc.
+
+ #--------------------------------------------------------------------------
+ # 数组和集合数据类型的表示方法: 多个值之间用'|'分隔, 如: Value1|Value2|...|ValueN
+ #--------------------------------------------------------------------------
+ properties.default.products=iphone|ipad|imac|itouch
+
+ #--------------------------------------------------------------------------
+ # MAP数据类型的表示方法:
+ # 如:产品规格(product_spec)的K分别是color|weight|size|age, 对应的V分别是热red|120g|small|2015
+ #--------------------------------------------------------------------------
+ properties.default.product_spec.color=red
+ properties.default.product_spec.weight=120g
+ properties.default.product_spec.size=small
+ properties.default.product_spec.age=2015
+
+ # 每个MAP都有属于其自身的属性列表(深度仅为一级), 用attributes表示, abc代表属性key, xyz代表属性值
+ # 注: MAP数据类型的attributes和MAP本身的表示方法达到的效果是一样的
+ properties.default.product_spec.attributes.abc=xyz
+
+
+- 修改配置类DemoConfig如下, 通过`@ConfigurationProvider`注解指定配置文件内容解析器:
+
+
+ @Configuration("cfgs/configuration.properties")
+ @ConfigurationProvider(PropertyConfigurationProvider.class)
+ public class DemoConfig extends DefaultConfiguration {
+ }
+
+
+- 重新执行示例代码, 执行结果与示例一结果相同:
+
+
+ Apple Inc.
+ {abc=xzy, color=red, size=small, weight=120g, age=2015}
+ [itouch, imac, ipad, iphone]
+
+## 示例三:无需创建配置对象, 直接加载配置文件
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ IConfiguration _cfg = Cfgs.get().loadCfg("cfgs/configuration.properties");
+ if (_cfg != null) {
+ System.out.println(_cfg.getString("company_name"));
+ System.out.println(_cfg.getMap("product_spec"));
+ System.out.println(_cfg.getList("products"));
+ }
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+
+## 示例四:通过`@Configurable`注解并配合`IConfigurable`接口实现配置文件自动装配
+
+ public interface IDemoService {
+
+ String getCompanyName();
+ }
+
+ @Bean
+ @Configurable(type = DemoConfig.class)
+ public class DemoService extends AbstractConfigurable implements IDemoService {
+
+ @Override
+ public String getCompanyName() {
+ return getConfig().getString("company_name");
+ }
+ }
+
+## 配置体系模块更多操作
+
+### 获取路径信息
+
+下列方法的返回结果会根据配置体系模块配置的不同而不同:
+
+ // 返回配置体系根路径
+ Cfgs.get().getConfigHome();
+
+ // 返回项目根路径
+ Cfgs.get().getProjectHome();
+
+ // 返回项目模块根路径
+ Cfgs.get().getModuleHome();
+
+ // 返回user.dir所在路径
+ Cfgs.get().getUserDir();
+
+ // 返回user.home所在路径
+ Cfgs.get().getUserHome();
+
+### 搜索目标文件
+
+ // 在配置体系中搜索cfgs/configuration.xml文件并返回其File对象
+ Cfgs.get().searchFile("cfgs/configuration.xml");
+
+ // 在配置体系中搜索cfgs/configuration.properties文件并返回其绝对路径
+ Cfgs.get().searchPath("cfgs/configuration.properties");
diff --git a/misc/site/guide/core.md b/misc/site/guide/core.md
new file mode 100755
index 0000000000000000000000000000000000000000..a6f720dd9b56158c179f0ca48c7cac82225725ec
--- /dev/null
+++ b/misc/site/guide/core.md
@@ -0,0 +1,1119 @@
+---
+sidebarDepth: 2
+---
+
+# 核心 (Core)
+
+YMP框架主要是由核心(Core)和若干模块(Modules)组成,核心主要负责框架的初始化和模块的生命周期管理。
+
+## 核心功能
+
+- Beans:类对象管理器(微型的Spring容器),提供包类的自动扫描(AutoScan)以及Bean生命周期管理、依赖注入(IoC)和方法拦截(AOP)等特性。
+
+- Event:事件服务,通过事件注册和广播的方式触发和监听事件动作,并支持同步和异步两种模式执行事件队列。
+
+- Module:模块,是YMP框架所有功能特性封装的基础形式,负责模块的生命周期管理,模块将在框架初始化时自动加载并初始化,在框架销毁时自动销毁。
+
+- I18N:国际化资源管理器,提供统一的资源文件加载、销毁和内容读取,支持自定义资源加载和语言变化的事件监听。
+
+- Lang:提供了一组自定义的数据结构,它们在部分模块中起到了重要的作用,包括:
+ + BlurObject:用于解决常用数据类型间转换的模糊对象。
+ + PairObject:用于将两个独立的对象捆绑在一起的结对对象。
+ + TreeObject:使用级联方式存储各种数据类型,不限层级深度的树型对象。
+
+- Util:提供框架中需要的各种工具类。
+
+## Maven包依赖
+
+
+ net.ymate.platform
+ ymate-platform-core
+
+
+
+> **注**:若想单独使用YMP核心包时需要在pom.xml中添加上述配置,其它模块已经默认引入核心包依赖,无需重复配置。
+
+## 框架初始化
+
+### 方式一:基于配置文件初始化
+
+YMP框架的初始化默认是从加载`ymp-conf.properties`文件开始的,该文件必须被放置在`classpath`的根路径下;
+
+- 根据程序运行环境的不同,YMP框架初始化时将根据当前操作系统优先级加载配置:
+
+ + 优先加载`ymp-conf_DEV.properties`(若加载成功则强制设置`ymp.dev_mode=true`)
+ + Unix/Linux环境下,优先加载`ymp-conf_UNIX.properties`;
+ + Windows环境下,优先加载`ymp-conf_WIN.properties`;
+ + 若以上配置文件未找到,则加载默认配置`ymp-conf.properties`;
+
+- 同时,也可以通过JVM启动参数配置系统环境,框架将优先根据当前操作系统及运行环境加载匹配的配置文件:
+
+ + `-Dymp.run_env=test`:测试环境,将优先加载`ymp-conf_TEST.properties`
+ + `-Dymp.run_env=dev`:开发环境,将优先加载`ymp-conf_DEV.properties`
+ + `-Dymp.run_env=product`:生产环境,将优先加载`ymp-conf.properties`
+
+- 框架初始化基本配置参数:
+
+ #-------------------------------------
+ # 框架基本配置参数
+ #-------------------------------------
+
+ # 是否为开发模式,默认为false
+ ymp.dev_mode=
+
+ # 框架自动扫描的包路径集合,多个包名之间用'|'分隔,默认已包含net.ymate.platform包,其子包也将被扫描
+ ymp.autoscan_packages=
+
+ # 包排除列表,多个包名之间用'|'分隔,被包含在包路径下的类文件在扫描过程中将被忽略
+ ymp.excluded_packages=
+
+ # 包文件排除列表,多个文件名称之间用'|'分隔,被包含的JAR或ZIP文件在扫描过程中将被忽略
+ ymp.excluded_files=
+
+ # 模块排除列表,多个模块名称或类名之间用'|'分隔,被包含的模块在加载过程中将被忽略
+ ymp.excluded_modules=
+
+ # 框架对象加载器, 可选参数, 默认为net.ymate.platform.core.beans.impl.DefaultBeanLoader
+ ymp.bean_loader_class=
+
+ # 框架代理工厂, 可选参数, 默认为net.ymate.platform.core.beans.proxy.impl.DefaultProxyFactory
+ ymp.proxy_factory_class=
+
+ # 国际化资源默认语言设置,可选参数,默认采用系统环境语言
+ ymp.i18n_default_locale=zh_CN
+
+ # 国际化资源管理器事件监听处理器,可选参数,默认为空
+ ymp.i18n_event_handler_class=
+
+ # 默认密码处理器,可选参数,用于对已加密参数值进行解密,默认为net.ymate.platform.core.support.impl.DefaultPasswordProcessor
+ ymp.default_password_class=
+
+ # 框架全局自定义参数,xxx表示自定义参数名称,vvv表示参数值
+ ymp.params.xxx=vvv
+
+ # 本文测试使用的自定义参数
+ ymp.params.helloworld=Hello, YMP!
+
+
+- 测试代码,完成框架的启动和销毁:
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ // 输出自定义参数值:Hello, YMP!
+ System.out.println(YMP.get().getConfig().getParam("helloworld"));
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+
+### 方式二:通过代码初始化
+
+- 示例代码,采用自定代理和对象加载器完成框架初始化操作:
+
+ public static void main(String[] args) throws Exception {
+ // 创建YMP实例
+ YMP owner = new YMP(ConfigBuilder.create()
+ .proxyFactory(new JavassistProxyFactory())
+ .beanLoader(new AbstractBeanLoader() {
+ @Override
+ public void load(IBeanFactory beanFactory, IBeanFilter filter) throws Exception {
+ // 手动注册Bean到容器中
+ beanFactory.registerBean(DemoBean.class);
+ }
+ }).developMode(true).runEnv(IConfig.Environment.PRODUCT).build());
+ // 向容器注册模块
+ owner.registerModule(Cfgs.class);
+ owner.registerModule(Logs.class);
+ owner.registerModule(Servs.class);
+ // 执行框架初始化
+ owner.init();
+ //
+ owner.getBean(DemoBean.class).say();
+ // 销毁
+ owner.destroy();
+ }
+
+## Beans
+
+### 包类的自动扫描(AutoScan)
+
+YMP框架初始化时将自动扫描由`autoscan_packages`参数配置的包路径下所有声明了`@Bean`注解的类文件,首先分析被加载的类所有已实现接口并注册到Bean容器中,然后执行类成员的依赖注入和方法拦截代理的绑定;
+
+> 说明:
+>
+> - 相同接口的多个实现类被同时注册到Bean容器时,通过接口获取的实现类将是最后被注册到容器的那个,此时只能通过实例对象类型才能正确获取;
+>
+> - 若不希望某个类被自动扫描,只需在该类上声明`@Ignored`注解,自动扫描程序都忽略它;
+
+- 示例一:
+
+ // 业务接口
+ public interface IDemo {
+ String sayHi();
+ }
+
+ // 业务接口实现类,单例模式
+ @Bean
+ public class DemoBean implements IDemo {
+ public String sayHi() {
+ return "Hello, YMP!";
+ }
+ }
+
+- 示例二:
+
+ // 示例一中的业务接口实现类,非单例模式
+ @Bean(singleton = false)
+ public class DemoBean implements IDemo {
+ public String sayHi() {
+ return "Hello, YMP!";
+ }
+ }
+
+- 示例三:
+
+ public class DemoBeanHandler implements IBeanHandler {
+
+ @Override
+ public Object handle(Class> targetClass) throws Exception {
+ // 自定义对象处理逻辑...
+ return BeanMeta.create(targetClass, true);
+ }
+ }
+
+ // 自定义对象处理器 (将取代原来的处理器)
+ @Bean(handler=DemoBeanHandler.class)
+ public class DemoBean implements IDemo {
+ public String sayHi() {
+ return "Hello, YMP!";
+ }
+ }
+
+- 示例四:
+
+ // 自定义Bean实例初始化后处理逻辑
+ @Bean
+ public class DemoBean implements IDemo, IBeanInitializer {
+ public String sayHi() {
+ return "Hello, YMP!";
+ }
+
+ public void afterInitialized() throws Exception {
+ System.out.println(sayHi() + " ---- afterInitialized.");
+ }
+ }
+
+- 测试代码:
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ // 1. 通过接口获取实例对象
+ IDemo _demo = YMP.get().getBean(IDemo.class);
+ System.out.println(_demo.sayHi());
+
+ // 2. 直接获取实例对象
+ _demo = YMP.get().getBean(DemoBean.class);
+ System.out.println(_demo.sayHi());
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+
+## 依赖注入(IoC)
+
+通过在类成员属性上声明`@Inject`和`@By`注解来完成依赖注入的设置,且只有被Bean容器管理的类对象才支持依赖注入,下面举例说明:
+
+- 示例:
+
+ // 业务接口
+ public interface IDemo {
+ String sayHi();
+ }
+
+ // 业务接口实现类1
+ @Bean
+ public class DemoOne implements IDemo {
+ public String sayHi() {
+ return "Hello, YMP! I'm DemoOne.";
+ }
+ }
+
+ // 业务接口实现类2
+ @Bean
+ public class DemoTwo implements IDemo {
+ public String sayHi() {
+ return "Hello, YMP! I'm DemoTwo.";
+ }
+ }
+
+- 测试代码:
+
+ @Bean
+ public class TestDemo {
+
+ @Inject
+ private IDemo __demo1;
+
+ @Inject
+ @By(DemoOne.class)
+ private IDemo __demo2;
+
+ public void sayHi() {
+ // _demo1注入的将是最后被注册到容器的IDemo接口实现类
+ System.out.println(__demo1.sayHi());
+ // _demo2注入的是由@By注解指定的DemoOne类
+ System.out.println(__demo2.sayHi());
+ }
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ TestDemo _demo = YMP.get().getBean(TestDemo.class);
+ _demo.sayHi();
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+ }
+
+也可以通过`@Injector`注解声明一个`IBeanInjector`接口实现类向框架注册自定义的注入处理逻辑,下面举例说明如何为注入对象添加包装器:
+
+- 示例:
+
+ // 定义一个业务接口
+
+ public interface IInjectBean {
+
+ String getName();
+
+ void setName(String name);
+ }
+
+ // 业务接口实现类
+
+ @Bean
+ public class InjectBeanImpl implements IInjectBean {
+
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ // 业务对象包装器类
+
+ public class InjectBeanWrapper implements IInjectBean {
+
+ private IInjectBean __targetBean;
+
+ public InjectBeanWrapper(IInjectBean targetBean) {
+ __targetBean = targetBean;
+ }
+
+ public String getName() {
+ return __targetBean.getName();
+ }
+
+ public void setName(String name) {
+ __targetBean.setName(name);
+ }
+ }
+
+ // 自定义一个注解
+
+ @Target({ElementType.FIELD})
+ @Retention(RetentionPolicy.RUNTIME)
+ @Documented
+ public @interface Demo {
+
+ String value();
+ }
+
+ // 为注解编写自定义注入逻辑
+
+ @Injector(Demo.class)
+ public class DemoBeanInjector implements IBeanInjector {
+
+ public Object inject(IBeanFactory beanFactory, Annotation annotation, Class> targetClass, Field field, Object originInject) {
+ // 为从自定义注解取值做准备
+ Demo _anno = (Demo) annotation;
+ if (originInject == null) {
+ // 若通过@Inject注入的对象不为空则为其赋值
+ IInjectBean _bean = new InjectBeanImpl();
+ _bean.setName(_anno.value());
+ // 创建包装器
+ originInject = new InjectBeanWrapper(_bean);
+ } else {
+ // 直接创建包装器并赋值
+ InjectBeanWrapper _wrapper = new InjectBeanWrapper((IInjectBean) originInject);
+ _wrapper.setName(_anno.value());
+ //
+ originInject = _wrapper;
+ }
+ return originInject;
+ }
+ }
+
+- 测试代码:
+
+ @Bean
+ public class App {
+
+ @Inject
+ @Demo("demo")
+ private IInjectBean __bean;
+
+ public IInjectBean getBean() {
+ return __bean;
+ }
+
+ public static void main(String[] args) throws Exception {
+ try {
+ YMP.get().init();
+ //
+ App _app = YMP.get().getBean(App.class);
+ IInjectBean _bean = _app.getBean();
+ System.out.println(_bean.getName());
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+ }
+
+> 说明:
+>
+> - 当使用自定义注解进行依赖注入操作时可以忽略`@Inject`注解,若存在则优先执行`@Inject`注入并将此对象当作`IBeanInjector`接口方法参数传入;
+> - 当成员变量被声明多个自定义注入规则注解时(不推荐),根据框架加载顺序,仅执行首个注入规则;
+
+## 方法拦截(AOP)
+
+YMP框架的AOP是基于CGLIB的MethodInterceptor实现的拦截,通过以下注解进行配置:
+
+- @Before:用于设置一个类或方法的前置拦截器,声明在类上的前置拦截器将被应用到该类所有方法上;
+
+- @After:用于设置一个类或方法的后置拦截器,声明在类上的后置拦截器将被应用到该类所有方法上;
+
+- @Around:用于同时配置一个类或方法的前置和后置拦截器;
+
+- @Clean:用于清理类上全部或指定的拦截器,被清理的拦截器将不会被执行;
+
+- @ContextParam:用于设置上下文参数,主要用于向拦截器传递参数配置;
+
+- @Ignored:声明一个方法将忽略一切拦截器配置;
+
+> 说明:
+>
+> - 声明`@Ignored`注解的方法、非公有方法和Object类方法及Object类重载方法将不被拦截器处理。
+> - 使用`@Interceptor`注解声明拦截器类,框架将自动扫描加载并支持IoC依赖注入特性。
+
+示例一:
+
+ // 创建自定义拦截器
+ public class DemoInterceptor implements IInterceptor {
+ public Object intercept(InterceptContext context) throws Exception {
+ // 判断当前拦截器执行方向
+ switch (context.getDirection()) {
+ // 前置
+ case BEFORE:
+ System.out.println("before intercept...");
+ // 获取拦截器参数
+ String _param = context.getContextParams().get("param");
+ if (StringUtils.isNotBlank(_param)) {
+ System.out.println(_param);
+ }
+ break;
+ // 后置
+ case AFTER:
+ System.out.println("after intercept...");
+ }
+ return null;
+ }
+ }
+
+ @Bean
+ public class TestDemo {
+
+ @Before(DemoInterceptor.class)
+ public String beforeTest() {
+ return "前置拦截测试";
+ }
+
+ @After(DemoInterceptor.class)
+ public String afterTest() {
+ return "后置拦截测试";
+ }
+
+ @Around(DemoInterceptor.class)
+ @ContextParam({
+ @ParamItem(key = "param", value = "helloworld")
+ })
+ public String allTest() {
+ return "拦截器参数传递";
+ }
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ TestDemo _demo = YMP.get().getBean(TestDemo.class);
+ _demo.beforeTest();
+ _demo.afterTest();
+ _demo.allTest();
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+ }
+
+示例二:
+
+ @Bean
+ @Before(DemoInterceptor.class)
+ @ContextParam({
+ @ParamItem(key = "param", value = "helloworld")
+ })
+ public class TestDemo {
+
+ public String beforeTest() {
+ return "默认前置拦截测试";
+ }
+
+ @After(DemoInterceptor.class)
+ public String afterTest() {
+ return "后置拦截测试";
+ }
+
+ @Clean
+ public String cleanTest() {
+ return "清理拦截器测试";
+ }
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ TestDemo _demo = YMP.get().getBean(TestDemo.class);
+ _demo.beforeTest();
+ _demo.afterTest();
+ _demo.cleanTest();
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+ }
+
+**注**:`@ContextParam`注解的value属性允许通过$xxx的格式支持从框架全局参数中获取xxx的值
+
+### 包拦截器配置
+
+YMP框架支持将`@Before`、`@After`、`@Around`和`@ContextParam`注解在`package-info.java`类中声明,声明后该拦截器配置将作用于其所在包下所有类(子包将继承父级包配置)。
+
+拦截器的执行顺序: `package` \> `class` \> `method`
+
+通过`@Packages`注解让框架自动扫描`package-info.java`类并完成配置注册。
+
+示例:
+
+> 本例将为`net.ymate.demo.controller`包指定拦截器配置,其`package-info.java`内容如下:
+
+ @Packages
+ @Before(DemoInterceptor.class)
+ @ContextParam(@ParamItem(key = "param", value = "helloworld"))
+ package net.ymate.demo.controller;
+
+ import net.ymate.demo.intercept.DemoInterceptor;
+ import net.ymate.platform.core.beans.annotation.Before;
+ import net.ymate.platform.core.beans.annotation.ContextParam;
+ import net.ymate.platform.core.beans.annotation.Packages;
+ import net.ymate.platform.core.beans.annotation.ParamItem;
+
+### 拦截器全局规则设置
+
+有些时候,我们需要对指定的拦截器或某些类和方法的拦截器配置进行调整,往往我们要修改代码、编译打包并重新部署,这样做显然很麻烦!
+
+现在我们可以通过配置文件来完成此项工作,配置格式及说明如下:
+
+ #-------------------------------------
+ # 框架拦截器全局规则设置参数
+ #-------------------------------------
+
+ # 是否开启拦截器全局规则设置, 默认为false
+ ymp.intercept_settings_enabled=true
+
+ # 为指定包配置拦截器, 格式: ymp.intercept.packages.<包名>=<[before:|after:]拦截器类名> (通过'|'分隔多个拦截器)
+ ymp.intercept.packages.net.ymate.demo.controller=before:net.ymate.demo.intercept.UserSessionInterceptor
+
+ # 全局设置指定的拦截器状态为禁止执行, 仅当取值为disabled时生效, 格式: ymp.intercept.globals.<拦截器类名>=disabled
+ ymp.intercept.globals.net.ymate.framework.webmvc.intercept.UserSessionAlreadyInterceptor=disabled
+
+ # 为目标类配置拦截器执行规则:
+ #
+ # -- 格式: ymp.intercept.settings.<目标类名>#[方法名称]=<[*|before:*|after:*]或[before:|after:]interceptor_class_name[+|-]]>
+ # -- 假设目标类名称为: net.ymate.demo.controller.DemoController
+ #
+ # -- 方式一: 指定目标类所有方法禁止所有拦截器(*表示全部, 即包括前置和后置拦截器)
+ ymp.intercept.settings.net.ymate.demo.controller.DemoController#=*
+
+ # -- 方式二: 指定目标类的doLogin方法禁止所有前置拦截器(before:表示规则限定为前置拦截器, after:表示规则限定为后置拦截器)
+ ymp.intercept.settings.net.ymate.demo.controller.DemoController#doLogin=before:*
+
+ # -- 方式三: 指定目标类的doLogout方法禁止某个前置拦截器并增加一个新的后置拦截器(多个执行规则通过'|'分隔, 增加拦截器的'+'可以省略)
+ ymp.intercept.settings.net.ymate.demo.controller.DemoController#__doLogout=before:net.ymate.demo.intercept.UserSessionInterceptor-|after:net.ymate.demo.intercept.UserStatusUpdateInterceptor+
+
+## 记录类属性状态 (PropertyState)
+
+通过在类成员变量上声明`@PropertyState`注解,并使用`PropertyStateSupport`工具类配合,便可以轻松实现对类成员属性的变化情况进行监控。
+
+- @PropertyState注解:声明记录类成员属性值的变化;
+
+ > propertyName:成员属性名称,默认为空则采用当前成员名称;
+ >
+ > aliasName:自定义别名,默认为空;
+ >
+ > setterName:成员属性SET方法名称,默认为空;
+
+- 示例代码:
+
+ public class PropertyStateTest {
+
+ @PropertyState(propertyName = "user_name")
+ private String username;
+
+ @PropertyState(aliasName = "年龄")
+ private int age;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public static void main(String[] args) throws Exception {
+ PropertyStateTest _original = new PropertyStateTest();
+ _original.setUsername("123456");
+ _original.setAge(20);
+ //
+ PropertyStateSupport _support = PropertyStateSupport.create(_original);
+ PropertyStateTest _new = _support.bind();
+ _new.setUsername("YMPer");
+ _new.setAge(30);
+ //
+ System.out.println("发生变更的字段名集合: " + Arrays.asList(_support.getChangedPropertyNames()));
+ for (PropertyStateSupport.PropertyStateMeta _meta : _support.getChangedProperties()) {
+ System.out.println("已将" + StringUtils.defaultIfBlank(_meta.getAliasName(), _meta.getPropertyName()) + "由" + _meta.getOriginalValue() + "变更为" + _meta.getNewValue());
+ }
+ }
+ }
+
+- 执行结果:
+
+ 发生变更的字段名集合: [user_name, age]
+ 已将user_name由123456变更为YMPer
+ 已将年龄由20变更为30
+
+## Event
+
+事件服务,通过事件的注册、订阅和广播完成事件消息的处理,目的是为了减少代码侵入,降低模块之间的业务耦合度,事件消息采用队列存储,采用多线程接口回调实现消息及消息上下文对象的传输,支持同步和异步两种处理模式;
+
+### 框架事件初始化配置参数
+
+ #-------------------------------------
+ # 框架事件初始化参数
+ #-------------------------------------
+
+ # 默认事件触发模式(不区分大小写),取值范围:NORMAL-同步执行,ASYNC-异步执行,默认为ASYNC
+ ymp.event.default_mode=
+
+ # 事件管理提供者接口实现,默认为net.ymate.platform.core.event.impl.DefaultEventProvider
+ ymp.event.provider_class=
+
+ # 事件线程池初始化大小,默认为Runtime.getRuntime().availableProcessors()
+ ymp.event.thread_pool_size=
+
+ # 最大线程池大小,默认为 200
+ ymp.event.thread_max_pool_size=
+
+ # 线程队列大小,默认为 1024
+ ymp.event.thread_queue_size=
+
+### YMP核心事件对象
+
+- ApplicationEvent:框架事件
+
+ APPLICATION_INITED - 框架初始化
+ APPLICATION_DESTROYED - 框架销毁
+
+- ModuleEvent:模块事件
+
+ MODULE_INITED - 模块初始化
+ MODULE_DESTROYED - 模块销毁
+
+**注**:以上只是YMP框架核心中包含的事件对象,其它模块中包含的事件对象将在其相应的文档描述中阐述;
+
+### 事件的订阅
+
+- 方式一:通过代码手动完成事件的订阅
+
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ // 订阅模块事件
+ YMP.get().getEvents().registerListener(ModuleEvent.class, new IEventListener() {
+ @Override
+ public boolean handle(ModuleEvent context) {
+ switch (context.getEventName()) {
+ case MODULE_INITED:
+ // 注意:这段代码是不会被执行的,因为在我们进行事件订阅时,模块的初始化动作已经完成
+ System.out.println("Inited :" + context.getSource().getName());
+ break;
+ case MODULE_DESTROYED:
+ System.out.println("Destroyed :" + context.getSource().getName());
+ break;
+ }
+ return false;
+ }
+ });
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+
+- 方式二:通过`@EventRegister`注解和IEventRegister接口实现事件的订阅
+
+ // 首先创建事件注册类,通过实现IEventRegister接口完成事件的订阅
+ // 通过@EventRegister注解,该类将在YMP框架初始化时被自动加载
+ @EventRegister
+ public class DemoEventRegister implements IEventRegister {
+ public void register(Events events) throws Exception {
+ // 订阅模块事件
+ events.registerListener(ModuleEvent.class, new IEventListener() {
+ @Override
+ public boolean handle(ModuleEvent context) {
+ switch (context.getEventName()) {
+ case MODULE_INITED:
+ System.out.println("Inited :" + context.getSource().getName());
+ break;
+ case MODULE_DESTROYED:
+ System.out.println("Destroyed :" + context.getSource().getName());
+ break;
+ }
+ return false;
+ }
+ });
+ //
+ // ... 还可以添加更多的事件订阅代码
+ }
+ }
+
+ // 框架启动测试
+ public static void main(String[] args) throws Exception {
+ YMP.get().init();
+ try {
+ // Do Nothing...
+ } finally {
+ YMP.get().destroy();
+ }
+ }
+
+### 自定义事件
+
+YMP的事件对象必须实现IEvent接口的同时需要继承EventContext对象,下面的代码就是一个自定义事件对象:
+
+- 创建自定义事件对象
+
+ public class DemoEvent extends EventContext