从.NET转投Java阵营之后,鄙人也算是花费了大量的时间和精力对Java世界里的明星开源框架诸如Spring,Tomcat,Mybatis,Druid,Log4j2等进行了比较深入的了解。
在这个研究过程中,除了不断感慨这些框架的设计精妙以及广泛的普适性外,渐渐地也对它们的复杂度开始感到些许厌倦。
1. 概述
首先还是强调下,本人不敢说Spring或Mybatis这些明星框架有什么不好。只是任何工具都有其适用场景,Spring等框架有着极为广泛的受众,带来强大的社区活跃性和生命力的同时,也不可避免得为了满足层出不穷和千奇百怪的需求,不断得增加中间层,这样在增加了灵活性和包容性的同时,其框架的复杂性也不可避免得膨胀起来。
但是,我们真的需要这么多功能。Spring必须做到你可以不要,但我必须要有。但作为使用者的我们,却只是在乎有限的几个功能点。 本文将要讨论的jfinal正是以此为立足点。贴一段jfinal的宣传语,相信也是最终让很多人选择它的理由: ”JFinal 是基于 Java 语言的极速 WEB + ORM 框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展、Restful。在拥有Java语言所有优势的同时再拥有ruby、python、php等动态语言的开发效率!为您节约更多时间,去陪恋人、家人和朋友 :)“。
2. 配置
jfinal的配置极其简单,只需要在web.xml
中配置如下一段XML:
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<!-- 以下这个类就是我们添加自定义配置的位置; 除此之外就没有其他XML配置需求-->
<param-name>configClass</param-name>
<param-value>com.sp.wechat.conf.WechatConfig</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3. 细节
正如标题所示,本文我们关注的是jfinal框架是如何启动的。在看到上面的web.xml配置之后,相信各位基本都能猜到核心启动代码应该就在Servlet规范中的com.jfinal.core.JFinalFilter.init
方法中。
public void init(FilterConfig filterConfig) throws ServletException {
// 确保用户配置了自定义的jfinal配置类
if (jfinalConfig == null) {
createJFinalConfig(filterConfig.getInitParameter("configClass"));
}
// 初始化jfinal框架的核心组件; 也是本文的重心所在
jfinal.init(jfinalConfig, filterConfig.getServletContext());
// 获取contextPath的实际长度; 用于后期接收请求时, 快速截取出实际的请求参数
String contextPath = filterConfig.getServletContext().getContextPath();
contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());
// 保留一份Constants配置
constants = Config.getConstants();
// 获取配置的字符编码
encoding = constants.getEncoding();
// 回调启动成功的响应函数, 并入用户的自定义逻辑
jfinalConfig.afterJFinalStart();
// 获取配置完毕的核心组件handler, 开始接受请求
handler = jfinal.getHandler(); // 开始接受请求
}
4. JFinal.init
这个方法的职责是在JFinal启动时,初始化jfinal框架的核心组件。
// JFinal.init
void init(JFinalConfig jfinalConfig, ServletContext servletContext) {
this.servletContext = servletContext;
this.contextPath = servletContext.getContextPath();
// 初始化工具类PathKit, 以便之后的使用;
// 截止3.4版本, 本方法主要是为PathKit设置WebRootPath
initPathKit();
Config.configJFinal(jfinalConfig); // start plugin, init log factory and init engine in this method
// 保留一份Constants配置
constants = Config.getConstants();
// ActionMapping是个全局单例模式, 其中存放着用户所有自定义配置的Route——即客户端请求的url路径与处理该请求的Action之间的匹配关系
// 其中的关键字段 mapping (Map<String,Action>类型, key就是actionKey, 客户端请求的url路径与该actionKey匹配的, 对应的Value就是处理该请求的Action)
initActionMapping();
// Handler, 在这里将Handler组装成链式结构, 并且ActionHandler为链条的最后一环, 即除非链条被主动断开, 那么在handler链条中, 最后处理请求的肯定是这个ActionHandler
// 用户配置的Handler, 在链条中, 先配置的先执行, 后配置的后执行
// jfinal框架中, 也正是ActionHandler在调取相应的自定义Action的
initHandler();
// Render
initRender();
// OreillyCos 文件上传功能
initOreillyCos();
// TokenManager 页面token功能,防止安全漏洞, 确保页面是传递给客户端的那个
initTokenManager();
}
4.1 Config.configJFinal
- 本方法按照一定的顺序回调开发者的自定义配置项constant, plugin, route, engine, interceptor, handler。将用户的自定义配置加载到JFinal内部的相应配置实体Constants,Routes,Plugins,Interceptors,Handlers中。
- 所以访问修饰符为package的
Config
类即可JFinal配置项的集散地,在经过Config.configJFinal
调用时机之后,我们就可以通过Config里的各种静态getXxxx方法获取用户的各种自定义配置。
//----------------- Config.configJFinal
/*
* Config order: constant, plugin, route, engine, interceptor, handler
*/
static void configJFinal(JFinalConfig jfinalConfig) {
// 回调用户自定义的配置Constant
jfinalConfig.configConstant(constants);
// 初始化日志系统
initLogFactory();
// 初始化Engine, 截止3.4版本, 本方法做了两件事:
// 1. 设置devMode
// 2. 设置default base template path
initEngine();
// ----------- 以下会连续调用五次configPluginWithOrder, 确保plugin能在合适的时机初始化且只初始化一次。
// 开发者可以通过在自定义配置constans.setConfigPluginOrder来自定义Plugin的启动时机。
// 默认configPlugin(..) 将在 configRoute(...) 调用之后被调用
configPluginWithOrder(1, jfinalConfig);
// 回调用户自定义的配置Route
jfinalConfig.configRoute(routes);
configPluginWithOrder(2, jfinalConfig);
// 回调用户自定义的配置Engine
jfinalConfig.configEngine(engine);
configPluginWithOrder(3, jfinalConfig);
// 回调用户自定义的配置Interceptor
jfinalConfig.configInterceptor(interceptors);
configPluginWithOrder(4, jfinalConfig);
// 回调用户自定义的配置Handler
jfinalConfig.configHandler(handlers);
configPluginWithOrder(5, jfinalConfig);
}
4.2 JFinal.initActionMapping
// JFinal.initActionMapping
private void initActionMapping() {
// 构建全局的单例ActionMapping
actionMapping = new ActionMapping(Config.getRoutes());
// jFinal正是在下面的这个方法中将用户自定义的Route信息解析为Action级别的URL-Action的键值对的。
// 注意这里还会一并考虑Interceptor的分配。
// jFinal最终还是以Action为主体,而非我之前预想的Controller。
actionMapping.buildActionMapping();
Config.getRoutes().clear();
}
4.3 JFinal.initHandler
// JFinal.initHandler
private void initHandler() {
// 确保一个回调用户自定义Action的ActionHandler的存在。
ActionHandler actionHandler = Config.getHandlers().getActionHandler();
if (actionHandler == null) {
actionHandler = new ActionHandler();
}
actionHandler.init(actionMapping, constants);
handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler);
}
5. 最后
随着阅历的增加,相比较所谓的聪明和智商,现在的我只会相信一件事:那就是”坚持“。哪怕一开始你做得惨不忍睹,但愿意持续不断地改进,愿意为之不断付出时间和精力的坚持,才是唯一让我相信它能成功的理由。正如鄙人在博客的标题栏里的那句“人们真正注意到你的时候,不是第一眼看到你站在那里,而是发现过了这么久你居然还在那里。”。jfinal的作者这么多年的勤勉和坚持,才是造就jfinal如今成就的唯一原因。