mica 云母,寓意为云服务的核心,使得云服务开发更加方便快捷。mica 的前身是 lutool,lutool在内部孵化了小两年,已经被多个朋友运用到企业。由于 lutool 对微服务不够友好,故重塑了mica。mica 中的部分大部分组件进行了持续性打磨,增强易用性和性能。
mica 基于 java 8,没有历史包袱,支持传统 Servlet 和 Reactive(webflux)。采用 mica-auto 自动生成 spring.factories 和 spring-devtools.properties 配置,仅依赖 Spring boot、Spring cloud 全家桶,无第三方依赖。市面上鲜有的微服务核心组件。
[1.0.1] - 2019年04月03日
5.3.1。本次版本主要是进行了一些工具的压力测试:
| Benchmark | Score | Error | Units |
|---|---|---|---|
| hutool | 1939.092 | 26.747 | ops/ms |
| spring | 3569.035 | 39.607 | ops/ms |
| cglib | 9112.785 | 560.503 | ops/ms |
| mica | 17753.409 | 393.245 | ops/ms |
结论:mica 在非编译期 Bean copy 性能强劲,功能强大。
| Benchmark | Score | Error | Units |
|---|---|---|---|
| jdk8UUId | 734.595 | 17.220 | ops/ms |
| jdk8ThreadLocalRandomUUId | 3224.759 | 32.107 | ops/ms |
| hutoolFastSimpleUUID | 3619.748 | 67.195 | ops/ms |
| micaUUId(java9 方式) | 12375.405 | 241.879 | ops/ms |
结论:mica 在使用了 java9 的算法,性能卓越。
| Benchmark | Score | Error | Units |
|---|---|---|---|
| java8Date | 2405.924 | 44.912 | ops/ms |
| micaDateUtil | 2541.753 | 48.321 | ops/ms |
| hutoolDateUtil | 2775.531 | 13.526 | ops/ms |
结论:hutool 使用的 common lang3 的 FastDateFormat 占用优势。mica 使用 tomcat8 中的 ConcurrentDateFormat 简单的规避了 SimpleDateFormat 的线程问题。
后期 mica 会进行更多的测试,保证稳定的同时,尽可能的提高性能。
mica 工具集:https://gitee.com/596392912/mica Avue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avue pig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pig SpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBlade IJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay 如梦技术-公众号.jpg
扫描上面二维码,更多精彩内容每天推荐!
mica 云母,寓意为云服务的核心,使得云服务开发更加方便快捷。mica 的前身是 lutool,lutool在内部孵化了小两年,已经被多个朋友运用到企业。由于 lutool 对微服务不够友好,故重塑了mica。mica 中的部分大部分组件进行了持续性打磨,增强易用性和性能。
mica 基于 java 8,没有历史包袱,支持传统 Servlet 和 Reactive(webflux)。采用 mica-auto 自动生成 spring.factories 和 spring-devtools.properties 配置,仅依赖 Spring boot、Spring cloud 全家桶,无第三方依赖。市面上鲜有的微服务核心组件。
webflux 下不支持的配置。requestUrl 拼接 queryString,添加 requestMethod 参数。base64 验证码为直接返回 Captcha 对象,方便二次处理。swagger 服务名不使用大写,webflux swagger 仅仅自动配置 Docket。lutool 中的资源读取工具。Spring-core 扩展增强,无其他依赖。$ 工具类快捷方法,不用再记忆到底有哪些工具类。url 版本号和 header版本处理。webflux 和 serlvet允许以引入不改源码的形式免费用于学习、毕设、公司项目、私活等。
特殊情况修改代码,但仍然想闭源需经过作者同意。
参考请注明:参考自 mica:https://gitee.com/596392912/mica
mica 工具集:https://gitee.com/596392912/mica Avue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avue pig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pig SpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBlade IJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay 如梦技术-公众号.jpg
扫描上面二维码,更多精彩内容每天推荐!
spring-boot-starter-weixin 是 jfinal weixin 的 spring boot starter,这个 starter 是为了方便 Spring boot 用户使用。具体demo请查看:spring-boot-weixin-demo 和 JFinal-weixin文档。
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>spring-boot-starter-weixin</artifactId>
<version>1.3.4</version>
</dependency>
DreamMsgControllerAdapter,实现需要重写的消息。@WxMsgController,注解value为你的消息地址,使用/weixin/wx,已经组合[@RequestMapping和@Controller]DreamWxaMsgController,实现需要重写的消息。@WxMsgController,注解value为你的消息地址,使用/weixin/wxa,已经组合[@RequestMapping和@Controller]@WxApi,注解value为你的消息地址,使用/weixin/api,已经组合[@RequestMapping和@Controller]| 配置项 | 默认值 | 说明 |
|---|---|---|
| dream.weixin.access-token-cache | dreamWeixinCache | 缓存名,需要开启spring cache |
| dream.weixin.app-id-key | appId | 多公众号参数名,如:/weixin/wx?appId=xxx |
| dream.weixin.dev-mode | false | 开发模式 |
| dream.weixin.url-patterns | /weixin/* | weixin 消息处理spring拦截器url前缀 |
| dream.weixin.wx-configs | 公众号的配置 | 多公众号配置 |
| dream.weixin.wxa-config | 小程序配置 | 小程序配置 |
demo 项目中的 application.yml 配置文件。
dream:
weixin:
dev-mode: true
wx-configs:
- appId: wx9803d1188fa5fbda
appSecret: db859c968763c582794e7c3d003c3d87
- appId: wxc03edcd008ad1e70
appSecret: 11ed9e2b8e3e3c131e7be320a42b2b5a
token: 123456
wxa-config:
app-id: wx4f53594f9a6b3dcb
app-secret: eec6482ba3804df05bd10895bace0579
@EnableCaching开启。access-token-cache 建议配置有效时间7100秒。2019年03月17日 v1.3.4
解决小程序,启用并设置消息推送配置校验不通过的问题。
升级到
gradle 5.2.1。
升级JFinal到3.6。
升级JFinal Weixin到2.3。
使用mica-auto生成spring.factories、devtools配置。InMsg消息对象采用request存储,去掉@WxMsgController中的 Scope 配置,将消息控制器还原为单例。
修复
SpringAccessTokenCache没有配置的问题,感谢 qq:A梦的小C反馈。
WeixinAppConfig改为实现SmartInitializingSingleton。
弃用
@EnableDreamWeixin,导入jar包即可享用。
将消息路由改为spring接管。
mica 工具集:https://gitee.com/596392912/mica Avue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avue pig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pig SpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBlade IJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay 如梦技术
精彩内容每日推荐!!!
]]>模仿的Spring中的消息事件:详解Spring事件驱动模
源码连接:http://git.oschina.net/596392912/JFinal-event
手册WIKI地址:http://git.oschina.net/596392912/JFinal-event/wikis/home
代码简单无任何第三方依赖,具体使用如下:
// 初始化插件
EventPlugin plugin = new EventPlugin();
// 开启异步,默认同步。或者使用`threadPool(ExecutorService executorService)`自定义线程池。
plugin.async();
// 设置扫描jar包,默认不扫描
plugin.scanJar();
// 设置监听器默认包,默认全扫描
plugin.scanPackage("net.dreamlu");
// 启动插件,用于main方法启动,jfinal中不需要,添加插件即可。
plugin.start();
// 发送第一个消息
EventKit.post(new Test1Event("hello1"));
// 发送带tag的消息
EventKit.post("save", new Test2Event(123123));
Thread.sleep(1000);
// 停止插件,用于main方法测试。
plugin.stop();
// 1. 开启异步plugin.async();
// 2. @Listener(order = 1, enableAsync = true, tag="save")注解中,enableAsync = true开启异步。
// 服务端:
plugin.setRmiServer(int port);
//客户端:
plugin.setRmiClient(String host, int port);
//发送消息:
EventKit.postRemote(final ApplicationEvent event);
// 或者
EventKit.postRemote(final String tag, final ApplicationEvent event);
// 建议:将服务端和客户端通用的event类文件打成maven模块。
http://central.maven.org/maven2/net/dreamlu/JFinal-event/
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>JFinal-event</artifactId>
<version>1.5.1</version>
</dependency>
2018年10月09日 v2.2.2
升级到jfinal 3.5 (不兼容老版本)支持新版本inject,升级到java 8优化部分代码。
DefaultBeanFactory改为jfinal 3.5 aop 创建,删除 DuangBeanFactory。
优化 ObjenesisBeanFactory 支持jfinal 3.5 inject
2018年08月14日 v2.2.1
ApplicationEvent 添加泛型,方便类型转换。
2018年04月15日 v2.2.0
插件添加手动注册监听类
plugin.register(Class<?> clazz), 多个类,多次调用register方法即可。
2018年03月02日 v2.1.0
添加
CtrlHolderEvent处理同步、异步中request、session、attr、header参数传递。
使用:
需先在Config中添加me.add(new CtrlHolderInterceptor());拦截器。
然后继承CtrlHolderEvent编写自己的事件类
CtrlHolder holder = event.getCtrlHolder();
holder.getPara("p");
holder.getAttr("x");
holder.getHeader("x");
holder.getSessionAttr("x");
2017年11月29日 v2.0.4
添加
ObjenesisBeanFactory处理不含有默认构造器的Bean
依赖objenesisjar包,下载地址:http://mvnrepository.com/artifact/org.objenesis/objenesis/2.6
2017年10月11日 v2.0.3
用户反馈的问题 #IFX3Z
支持多包名,用;分割,如:net.dreamlu.a;net.dreamlu.b。
插件初始化时,没有扫描到监听时依然初始化成功。
2017年10月11日 v2.0.2
2.x bug修复版
2017年10月11日 v2.0.1
插件添加Bean工厂,方便IOC容器和自定义扩展。
默认为DefaultBeanFactory,可实现IBeanFactory自定义扩展。plugin.beanFactory(new DuangBeanFactory());
2017年10月10日 v2.0.0
基于注解和方法的兼听,简化使用,不兼容1.x
支持JFinal 3.1和3.1以上版本
2017年04月28日
v1.5.1基于rmi的远程Event
2017年03月22日
v1.4.2
更改默认线程池为SingleThreadExecutor,使异步执行有序化。
添加EventThreadFactory,处理异步时的异常避免影响服务请求。
建议:如果event需要发送大量的异步事件,建议使用自定义线程池。
如:eventPlugin.threadPool(Executors.newCachedThreadPool(new EventThreadFactory()));
2017年02月15日
v1.4.1添加自定义线程池EventPlugin.threadPool(ExecutorService executorService)方法
2016年08月19日
v1.4.0升级到JFinal2.2,JFinal低版本用户请使用v1.2.0。
EventKit.postEvent(event)更改为EventKit.post(event),postEvent不再建议使用。
添加EventKit.post(tag, event)方法,@Listener(order = 2, tag = "save")添加tag
mica 工具集:https://gitee.com/596392912/mica Avue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avue pig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pig SpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBlade IJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay 如梦技术
精彩内容每日推荐!!!
]]>利用JFinal的代码生成可以为我们生成漂亮的数据字典。其格式类似Mysql的命令行,可见波总的用心。
Table: t_ad (广告表)
----------------+---------------------+------+-----+-------------------+---------
Field | Type | Null | Key | Default | Remarks
----------------+---------------------+------+-----+-------------------+---------
id | INT UNSIGNED(10) | NO | PRI | | 主键id
ad_position_id | INT UNSIGNED(10) | NO | | 0 | 广告位id
title | VARCHAR(64) | NO | | | 广告标题
content | VARCHAR(255) | NO | | | 广告内容
media_type | TINYINT UNSIGNED(2) | NO | | 0 | 媒体类型
link | VARCHAR(255) | NO | | | 链接地址
image_url | VARCHAR(255) | NO | | | 图片地址
end_time | DATETIME(19) | NO | | CURRENT_TIMESTAMP | 结束时间
seq | TINYINT(3) | NO | | 0 | 排序
enabled | TINYINT UNSIGNED(2) | NO | | 1 | 是否启用
creator | VARCHAR(32) | NO | | | 格式:创建者[id]
updator | VARCHAR(32) | NO | | | 格式:创建者[id]
create_time | DATETIME(19) | NO | | CURRENT_TIMESTAMP | 创建时间
update_time | DATETIME(19) | NO | | CURRENT_TIMESTAMP | 更新时间
----------------+---------------------+------+-----+-------------------+---------
在实际工作中现在我们更多的是采用markdown格式的文档,研究一番可以发现JFinal生成的数据字典和markdown的Table比较类型。下面来看看我的改造过程。
DruidPlugin druidPlugin = new DruidPlugin(jdbcUrl, user, password);
// remarks=true设置可以获取column remarks信息
// useInformationSchema=true设置可以获取tables remarks信息
druidPlugin.setConnectionProperties("useInformationSchema=true;remarks=true");
druidPlugin.start();
return druidPlugin.getDataSource();
import javax.sql.DataSource;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.generator.ColumnMeta;
import com.jfinal.plugin.activerecord.generator.TableMeta;
public class MyDataDictionaryGenerator extends com.jfinal.plugin.activerecord.generator.DataDictionaryGenerator {
public DataDictionaryGenerator(DataSource dataSource, String dataDictionaryOutputDir) {
super(dataSource, dataDictionaryOutputDir);
}
/**
* 重写掉生成换行
*/
@Override
protected String genSeparateLine(TableMeta tm) {
return "";
}
protected void generateTable(TableMeta tableMeta, StringBuilder ret) {
ret.append("Table: ").append(tableMeta.name);
if (StrKit.notBlank(tableMeta.remarks)) {
ret.append("(").append(clearBlank(tableMeta.remarks)).append(")");
}
ret.append("\n");
String sparateLine = genSeparateLine(tableMeta);
ret.append(sparateLine);
genTableHead(tableMeta, ret);
ret.append(sparateLine);
for (ColumnMeta columnMeta : tableMeta.columnMetas) {
genColumn(tableMeta, columnMeta, ret);
}
ret.append(sparateLine);
ret.append("\n");
}
/**
* 表头
*/
@Override
protected void genTableHead(TableMeta tm, StringBuilder ret) {
ret.append('\n').append('|');
genCell(tm.colNameMaxLen, " ", "Field", " ", "|", ret);
genCell(tm.colTypeMaxLen, " ", "Type", " ", "|", ret);
genCell("Null".length(), " ", "Null", " ","|", ret);
genCell("Key".length(), " ", "Key", " ","|", ret);
genCell(tm.colDefaultValueMaxLen, " ", "Default", " ", "|", ret);
genCell("Remarks".length(), " ", "Remarks", " ", "|", ret);
ret.append('\n').append('|');
genCell(tm.colNameMaxLen, " ", "-", "-", "|", ret);
ret.replace(ret.length() - 2, ret.length() - 1, " ");
genCell(tm.colTypeMaxLen, " ", "-", "-", "|", ret);
ret.replace(ret.length() - 2, ret.length() - 1, " ");
genCell("Null".length(), " ", "-", "-","|", ret);
ret.replace(ret.length() - 2, ret.length() - 1, " ");
genCell("Key".length(), " ", "-", "-","|", ret);
ret.replace(ret.length() - 2, ret.length() - 1, " ");
genCell(tm.colDefaultValueMaxLen, " ", "-", "-", "|", ret);
ret.replace(ret.length() - 2, ret.length() - 1, " ");
genCell("Remarks".length(), " ", "-", "-", "|", ret);
ret.replace(ret.length() - 2, ret.length() - 1, " ");
ret.append('\n');
}
/**
* 列
*/
@Override
protected void genColumn(TableMeta tableMeta, ColumnMeta columnMeta, StringBuilder ret) {
ret.append('|');
genCell(tableMeta.colNameMaxLen, " ", columnMeta.name, " ", "|", ret);
genCell(tableMeta.colTypeMaxLen, " ", columnMeta.type, " ", "|", ret);
genCell("Null".length(), " ", columnMeta.isNullable, " ", "|", ret);
genCell("Key".length(), " ", columnMeta.isPrimaryKey, " ", "|", ret);
genCell(tableMeta.colDefaultValueMaxLen, " ", columnMeta.defaultValue, " ", "|", ret);
genCell("Remarks".length(), " ", clearBlank(columnMeta.remarks), " ", "|", ret);
ret.append("\n");
}
/**
* 清除注释中的空白符
*/
private String clearBlank(String str) {
return str.replaceAll("[ |\t|\n]+", "");
}
}
// 创建生成器
Generator generator = new Generator(dataSource, baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir);
DataDictionaryGenerator dg = new MyDataDictionaryGenerator(dataSource, modelOutputDir);
dg.setDataDictionaryFileName("数据字典.md");
generator.setDataDictionaryGenerator(dg);
到此大工搞成,迫不及待的看看效果展示了~
Table: t_ad (广告表)
| Field | Type | Null | Key | Default | Remarks |
|---|---|---|---|---|---|
| id | INT UNSIGNED(10) | NO | PRI | 主键id | |
| ad_position_id | INT UNSIGNED(10) | NO | 0 | 广告位id | |
| title | VARCHAR(64) | NO | 广告标题 | ||
| content | VARCHAR(255) | NO | 广告内容 | ||
| media_type | TINYINT UNSIGNED(3) | NO | 0 | 媒体类型 | |
| link | VARCHAR(255) | NO | 链接地址 | ||
| image_url | VARCHAR(255) | NO | 图片地址 | ||
| end_time | DATETIME(19) | NO | CURRENT_TIMESTAMP | 结束时间 | |
| seq | TINYINT(3) | NO | 0 | 排序 | |
| enabled | TINYINT UNSIGNED(3) | NO | 1 | ||
| creator | VARCHAR(32) | NO | 格式:创建者[id] | ||
| updator | VARCHAR(32) | NO | 格式:创建者[id] | ||
| create_time | DATETIME(19) | NO | CURRENT_TIMESTAMP | 创建时间 | |
| update_time | DATETIME(19) | NO | CURRENT_TIMESTAMP | 更新时间 |
mica 工具集:https://gitee.com/596392912/mica Avue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avue pig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pig SpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBlade IJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay 如梦技术
精彩内容每日推荐!!!
]]>通常在java项目开发为了防止sql注入我们通常都采用的预编译的sql,采用"?"号挂参,如下:
SELECT * FROM blog WHERE id = ?
往往在开发测试阶段能获取到完整的可执行的sql能帮我们及时的发现和定位问题。就有了很多朋友使用log4jdbc来记录SQL信息。
如下图我们可以看到有一项statementExecutableSqlLogEnable默认为false
druid 打印可执行sql
这里我们以JFinal和Log4j最为演示的例子。
// 配置Druid数据库连接池插件
DruidPlugin druidPlugin = new DruidPlugin(jdbcUrl, user, password);
// 配置log插件
Log4jFilter logFilter = new Log4jFilter();
logFilter.setStatementLogEnabled(false);
logFilter.setStatementLogErrorEnabled(true);
logFilter.setStatementExecutableSqlLogEnable(true);
druidPlugin.addFilter(logFilter);
log4j.logger.druid.sql.Statement=DEBUG
druid中支持的日志Filter
Log4jFilter
Slf4jLogFilter
Log4j2Filter
CommonsLogFilter
mica 工具集:https://gitee.com/596392912/mica Avue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avue pig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pig SpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBlade IJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay 如梦技术
精彩内容每日推荐!!!
]]>