jfinal源码研究之核心组件Interceptor

在上一篇文章中我们研究了jfinal中的五大核心组件之一的Handler,而在本文中我们将目光转移到另外一个核心组件——Interceptor上来。

1. 概述

既然提到Interceptor,就不得不先提及下其在jfinal中的应用。jfinal中正是利用Interceptor来完成AOP功能。

相比较于传统AOP不但学习成本极高,开发效率极低,开发体验极差,而且还影响系统性能,尤其是在开发阶段造成项目启动缓慢,极大影响开发效率。

JFinal采用极速化的AOP设计,专注AOP最核心的目标,将概念减少到极致,仅有三个概念:Interceptor、Before、Clear,并且无需引入IOC也无需使用啰嗦的XML。

2. 定义

还是按照之前的研究方法,让我们先来看看Interceptor的相关定义。

/**
 * Interceptor.
 */
public interface Interceptor {
    void intercept(Invocation inv);
}

这个接口的定义相当清晰,只有一个方法声明。在这一点上jfinal依然是彻底贯彻了简洁的设计理念。

而要搞清楚执行逻辑,我们就需要对另外一个类,也就是方法参数Invocation进行一定的了解。jfinal的AOP实现主要就是借助这两个类来合作完成的。

// ------------------------------- Invocation.invoke
public void invoke() {
    // 这里将迭代完所有相关的Interceptor
    if (index < inters.length) {
        inters[index++].intercept(this);
    }
    else if (index++ == inters.length) {    // index++ ensure invoke action only one time
        // Invoke the action
        // 因为在之前的文章中已经探究过这个方法, 所以这里我们将无关细节省略; 
        // 只保留了Invocation和Interceptor的配合逻辑部分,避免干扰
    }
}

以上是Invocation中最关键性的方法invoke实现中的主要逻辑。我们可以大致将其分了两个部分:if判断部分,以及else if判断部分。

在我们一般的印象中,以上的else if部分的代码应该是在所有的Interceptor中的逻辑执行完毕之后才执行的,但是正是因为Invocation实例在回调相关 Interceptor时,会将自身作为方法参数传递给Interceptor.intercept方法,这就使得Interceptor里的逻辑可以在else if部分的逻辑执行完毕之后才执行。这就使得Interceptor可以实现AOP中的前后切面功能。

为了更加清晰地论述以上结论,特意截取了如下调用堆栈图。
执行堆栈
上图中我们一共启用了两个Interceptor —— 记录系统访问日志的SysLogInterceptor和仅作测试的LqInterceptor。而通过上述堆栈截图以及相应的注释,我们可以很清晰地感受到其中的执行先后顺序。

以上也解释了为什么在jfinal的官方文档中,一再强调在Interceptor的实现类中必须调用 inv.invoke() 方法;因为只有这样才能将当前调用传递到后续的 Interceptor 与 Action,让请求处理链条继续下去。

3. 继承链

在探究完接口定义以及相关的Invocation类之后,接下来就让我们看看Interceptor在jfinal中的相关继承体系,看看jfinal是如何发扬建立在Interceptor之上的奇思妙想的。
Interceptor继承链

由以上截图可以看出,相比较于Handler,jfinal在Interceptor上的实现要多出了好几倍,这当然和Interceptor在实际的项目中有着更加广泛的应用场景所分不开的;当然这也说明在Handler和Interceptor的这两大满足自定义需求的扩展利器中,Interceptor离开发人员要更接近一些。

因为以上的继承体系中成员比较多,所以这里我只是挑选出有代表性的几个进行探讨:

3.1 CacheInterceptor / EvictInterceptor

首先这两个拦截器在jfinal的官方文档中作了专门的论述,有兴趣的读者可以点击以下链接 : CacheInterceptor - Office Site

CacheInterceptor可以将action所需数据全部缓存起来,下次请求到来时如果cache存在则直接使用数据并render,而不会去调用action。此用法可使action完全不受cache相关代码所污染,即插即用。

EvictInterceptor可以根据CacheName注解自动清除缓存。这里有一点需要注意一下的是 EvictInterceptor的实现中,是在回调完毕自定义的Action方法之后才进行的缓存清除操作。

3.2 GET / POST

这两个Interceptor的作用挺简单的,作用类似于SpringMVC中的@GetMapping@PostMapping。就是约束当前请求必须以GET或POST请求发送。

3.3 Tx

既然讨论的是Interceptor,那么作为子类的Tx不被提及实在就有些说不过去,毕竟数据库的事务功能是任意一个再小的项目都绕不过去的。

Tx及其子类,和功能类似的TxByActionKeyRegex,其内部实现也是相当简单的。
更多细节可以参见如下官方内容事务

3.4 Validator

最后让我们来看看JFinal利用Interceptor来实现的校验组件。这也是Interceptor的一大应用场景,将校验和实际的业务代码完全隔离开,并且做到了对校验逻辑的复用。官方文档 - Validator

4. 补充

  1. 有关jfinal提供的AOP功能, jfinal中专门提供了顶级packagecom.jfinal.aop来实现,Interceptor以及相关的辅助类和工具类都在此package中。
  2. 除了Class与Method级别的拦截器以外,JFinal还支持全局拦截器以及Inject拦截器,全局拦截器分为控制层全局拦截器与业务层全局拦截器,前者拦截控制层所有Action方法,后者拦截业务层所有方法。而业务层全局拦截器的应用,感兴趣的读者可以看看jfinal中的com.jfinal.aop.Callback中的源码。
  3. Routes 拦截器在功能上通过一行代码,同时为多个 Controller 配置好相同的拦截器,减少了代码冗余。Routes 级别拦截器将在 Class 级别拦截器之前被调用。感兴趣的读者可以查阅下ActionMapping.buildActionMapping方法中对该类拦截器的应用。他们最终在InterceptorManager.doBuild被按照既定的顺序进行组装。
  4. JFinal的AOP可以应用于非web项目。只需要引入jfinal.jar包,然后使用Enhancer.enhance()或Duang.duang()即可极速使用JFinal的AOP功能。可以实现对任意目标在任何地方的增强。另外我非常想吐槽这个Duang是个什么鬼?真是任性的设计,让人好生羡慕。
  5. 最后关于Interceptor的触发,读者可以查阅下官方文档——Interceptor的触发,我就不再赘述了。
  6. 最后捎带提一下的是,jfinal的AOP增强依赖于CGLIB,所以我们在引入jfinal时,默认会引入一个名为cglib-nodep-3.2.5.jar的JAR包,这也是jfinal的唯一显式依赖。

5. 总结

  1. 相比较于前文的Handler的扩展,本篇所讨论的Interceptor最终还是围绕着自定义Action来运转的。其他诸如Class和全局层级的Interceptor,最终都是被分配到了Action级别。关于这一点,有兴趣的读者可以看看jfinal中的ActionMapping.buildActionMapping方法。
  2. 也正是因为Interceptor围绕Action的特性,也使得其应用场景相比较于Handler要多得多,这一点从其子类的数量就可见一斑。
  1. Interceptor - Office Site

猜你喜欢

转载自blog.csdn.net/lqzkcx3/article/details/81051558