###jFinal研究与见解
最幸福的事,莫过于看别人写的好框架,代码简单,功能齐全,设计思路清晰,可读性强。。。最痛苦的事,莫过于看别人写的烂代码,功能简单,代码复杂,可读性差。。。jFinal是很少见的国内写地很好的框架,该用别人造好轮子的地方,就不重复造轮子、别人写的不好的地方,就自己写个更好的。 *
网上有很多学习jFinal的资料,当然,任何资料都比不上源码和官方文档,查阅官方文档,请移步,我读的是jfinal-2.0-manual.pdf
jFinal的运行思路非常清晰。官方介绍的启动jFinal有两种方式,第一种,使用jFinal集成的jetty启动;第二种,使用tomcat启动,jetty相对tomcat比较小巧,jetty使用nio监听端口,而tomcat采用bio监听端口。下面,结合代码简单介绍下jFinal的运行。
-
容器启动时,读取配置文件web.xml,加载核心过滤器
jfinal com.jfinal.core.JFinalFilter configClass com.demo.common.DemoConfig jfinal /* -
过滤器初始化配置类,配置类中配置好路由、插件、拦截器、处理器。 com.demo.common.DemoConfig关键代码如下
public class DemoConfig extends JFinalConfig { public void configRoute(Routes me) { // 配置路由 } public void configPlugin(Plugins me) { // 配置插件 } public void configInterceptor(Interceptors me) { // 配置全局拦截器 } public void configHandler(Handlers me) { // 配置处理器 } }
-
com.jfinal.core.JFinal中对Handler进行初始化,关键代码如下
private void initHandler() { Handler actionHandler = new ActionHandler(actionMapping, constants); handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler); }
从代码中可以看出,初始化Handler的时候,先构造了一个ActionHandler对象,然后再将用户自定义的Handler加入,而ActionHandler中,将拦截器,controler和rander以及plugin都分别进行初始化
-
请求访问服务器,jFinalFilter拿到请求的控制权,将其交给handler链进行处理,handler链一层层处理,最终将结果返回给Filter com.jfinal.core.JFinalFilter关键代码如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; request.setCharacterEncoding(encoding); String target = request.getRequestURI(); if (contextPathLength != 0) target = target.substring(contextPathLength); boolean[] isHandled = {false}; try { //handler为链式的结构,此处交给多层handler进行处理 handler.handle(target, request, response, isHandled); } catch (Exception e) { if (log.isErrorEnabled()) { String qs = request.getQueryString(); log.error(qs == null ? target : target + "?" + qs, e); } } if (isHandled[0] == false) chain.doFilter(request, response); }
-
Handler抽象类接口源码如下
public abstract class Handler { //此处即说明Handler是链式结构 protected Handler nextHandler; public abstract void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled); }
读过框架源代码,即可很好的理解作者**@James Zhan**为框架所做的架构图,图如下:
####以下是关于此项目的一些个人的见解
- 关于JFinalConfig
此特性为jFinal引以为傲的特性之一,所有的Constant、Pluging、Interceptor、configHandler都在这一个类中配置,这样可以达到没有xml配置文件,不需要像ssh那样去维护繁琐的配置文件,之前接触php、nodejs这些语言的时候,也接触过这种处理方式,无配置文件,在类中利用route的方式转发请求。
但是我认为,小项目如此处理可以快速开发,但是项目如果大点,繁琐点,配置类将会变得很大,并且不易读懂。还有一种解决方案不将所有的配置都放在一个类中,而是根据不同的功能和业务,放在不同的包中,启动时将这些类动态加载进来。这样,开发看到包名即可知道mvc的构成,而不是需要一段一段读配置的代码,并且后期加入新的配置也不会对之前已存在配置造成影响。
jFinal配置的初始化也只支持一个类,如果想要使用多个,则不支持。同时routes的配置文档中说明支持配置多个,便于不同开发人员版本维护,constant、pluging等不使用这种方式,可能是考虑到这些是全局的,个性化的比较少吧。
com.jfinal.core.JFinalFilter初始化代码如下
public void init(FilterConfig filterConfig) throws ServletException { //本行代码读取配置类,只能配置一个类,可以优化为读取多个或者模糊匹配加载某系列包 createJFinalConfig(filterConfig.getInitParameter("configClass")); if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false) throw new RuntimeException("JFinal init error!"); handler = jfinal.getHandler(); constants = Config.getConstants(); encoding = constants.getEncoding(); jfinalConfig.afterJFinalStart(); String contextPath = filterConfig.getServletContext().getContextPath(); contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());}
- 关于Interceptor
jFinal使用非常灵活的方式实现了aop,学习spring的同学,都会被它的 Aspect、Advice、Joinpoint、Poincut...这些复杂的概念搞晕。 jFinal的拦截器根据级别可以划分为全局拦截器、Inject拦截器、Class拦截器以及Method拦截器,jFinal的aop是靠Iterceptor实现的吗?我们看下源码,Iterceptor只是个普通的接口,代码如下:
com.jfinal.aop.Interceptor
public interface Interceptor { void intercept(Invocation inv);}
可以看到,这个接口并没有任何特殊之处,那么aop到底是如何实现的呢?官方给出使用示例如下。
public class OrderService { // 配置事务拦截器 @Before(Tx.class) public void payment(int orderId, int userId) { // service code here }} // 定义控制器,控制器提供了enhance系列方法可对目标进行AOP增强public class OrderController extends Controller { public void payment() { // 使用 enhance方法对业务层进行增强,使其具有AOP能力 OrderService service = enhance(OrderService.class); // 调用payment方法时将会触发拦截器 service.payment(getParaToInt("orderId"), getParaToInt("userId")); }}
可以看到,特殊之处在于**@Before注解和enhance()**方法,那么它们究竟做了什么?我们继续深入研究。
com.jfinal.core.Controller的enhance方法源码如下:
publicT enhance(Class targetClass) { return (T)Enhancer.enhance(targetClass);}
调用了com.jfinal.aop.Enhancer.enhance(),再深入,看Enhancer..enhance()
public staticT enhance(Class targetClass) { return (T)net.sf.cglib.proxy.Enhancer.create(targetClass, new Callback());}
发现是调用了cglib的方法,动态创建了一个代理对象,并且新建了一个Callback(),进入**Callback()**看源码。
com.jfinal.aop.Callback实现了cglib的MethodInterceptor接口,intercept方法如下
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if (excludedMethodName.contains(method.getName())) { if (method.getName().equals("finalize")) return methodProxy.invokeSuper(target, args); return this.injectTarget != null ? methodProxy.invoke(this.injectTarget, args) : methodProxy.invokeSuper(target, args); } if (this.injectTarget != null) { target = this.injectTarget; Interceptor[] finalInters = InterceptorBuilder.build(injectInters, target.getClass(), method); Invocation invocation = new Invocation(target, method, args, methodProxy, finalInters); invocation.useInjectTarget = true; invocation.invoke(); return invocation.getReturnValue(); } else { Interceptor[] finalInters = InterceptorBuilder.build(injectInters, target.getClass(), method); Invocation invocation = new Invocation(target, method, args, methodProxy, finalInters); invocation.useInjectTarget = false; invocation.invoke(); return invocation.getReturnValue(); }}
发现中间有一行这样的代码:InterceptorBuilder.build(injectInters, target.getClass(), method);
进入com.jfinal.aop.InterceptorBuilder看build方法:
public static Interceptor[] build(Interceptor[] injectInters, Class targetClass, Method method) { Interceptor[] methodInters = createInterceptors(method.getAnnotation(Before.class)); 。。。}
此处获取了方法的**@Before的annotation**,并且根据annotation创建Interceptor。那么一切就明了了,我们再回到最开始的地方,加上注释
public class OrderService { // 配置事务拦截器 @Before(Tx.class) public void payment(int orderId, int userId) { // service code here }} // 定义控制器,控制器提供了enhance系列方法可对目标进行AOP增强public class OrderController extends Controller { public void payment() { // 使用 enhance方法对业务层进行增强,使其具有AOP能力 //此处,创建代理类,并且在回调方法中获取@before的配置,并根据配置构造拦截器 OrderService service = enhance(OrderService.class); // 调用payment方法时将会触发拦截器 // 此处使用的是OrderService的代理类,并且调用时候回调函数中调用拦截器方法执行拦截。 service.payment(getParaToInt("orderId"), getParaToInt("userId")); }}
对于Inject拦截器,是在Enhancer的enhance方法,提供了直接传入Interceptor.class的方法,所以可以提供Inject拦截器的功能 jFinal的拦截器用法非常简单,但是有一点,未提供动态代理的支持。但是jFinal提供的扩展方式如此简单,只需要在此基础上自己稍加改造即可实现。
- jFinal对事务的支持。
jFinal对事务的支持采用声明式事务,需要使用到事务的类,配置拦截器**@Before(Tx.class)**即可。
com.jfinal.plugin.activerecord.tx.Tx核心代码如下:
public void intercept(Invocation inv) { ... Boolean autoCommit = null; try { conn = config.getConnection(); autoCommit = conn.getAutoCommit(); config.setThreadLocalConnection(conn); conn.setTransactionIsolation(getTransactionLevel(config)); // conn.setTransactionIsolation(transactionLevel); conn.setAutoCommit(false); inv.invoke(); conn.commit(); } ...
}
实现代码非常简单易懂。
- jFinal缓存支持,
jFinal使用ehcache作为默认的缓存系统,提供了对sql的缓存和对业务层的缓存,两种缓存应该都属于二级缓存,范围都是在项目级别的,未提供session级别的缓存。
- validator,
jFinal的validate作为拦截器存在,建立在拦截器基础上,提供了string、double、date、等基本类型的校验,并提供了正则校验方式,可以动态配置,但是这里的问题是,如果一个请求有100个参数,那就必须写100行校验的代码,如果使用配置文件的方式,那可能会便于管理,当然,jFinal的高扩展性,也能让你自己轻易实现这个功能。
后记:jFinal是不可多得的好框架,源码简洁,没有过多的为了使用设计模式而使用设计模式,扩展性强。但是缺点也在于它太小了,对于大型项目的支持可能就有点吃不消;而这也算是语言的通病吧,功能强大,就会累赘臃肿,易上手,就难免扛不起大项目。祝jFinal越来越好。