jfinal AOP 实现原理和整体架构
语雀链接:https://www.yuque.com/miaoshuo/ko4syg/ny150b
(博客排版有问题,代码&表格请见原文)
序言
jfinal 是一个极简的 webmvc 框架,并且用极少的代码实现了 AOP,本文介绍 jfinal 中 AOP 实现原理和整体架构。
一、概念理解&使用方式
AOP(Aspect-oriented programming,面向切面编程),是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
传统AOP实现需要引入大量繁杂而多余的概念,例如:Aspect、Advice、Joinpoint、Poincut、Introduction、Weaving、Around等等,并且需要引入IOC容器并配合大量的XML或者annotation来进行组件装配。
传统AOP不但学习成本极高,开发效率极低,开发体验极差,而且还影响系统性能,尤其是在开发阶段造成项目启动缓慢,极大影响开发效率。
JFinal采用极速化的AOP设计,专注AOP最核心的目标,将概念减少到极致,仅有三个概念:Interceptor、Before、Clear,并且无需引入IOC也无需使用啰嗦的XML。
jfinal 实现的 AOP 只有三个概念,理解和使用相对简单。
概念 | 概述 |
Interceptor | 对方法进行拦截,并提供机会在方法的前后添加切面代码,实现 AOP 的核心目标 |
Before | 用来对拦截器进行配置,该注解可配置Class、Method级别的拦截器 |
Clear | 拦截器从上到下依次分为Global、Routes、Class、Method四个层次,Clear用于清除自身所处层次以上层的拦截器。 |
使用方式
使用 jfinal 的 AOP只需要三步:
在方法上使用 @Before(OtherInterceptor.class)
在属性上使用 @Inject
使用 Aop.get() 获取增强实例
代码如下:
//主 Service@Slf4jpublic class Service { @Inject private OtherService otherService; @Before(ServiceInterceptor.class) public void doIt() { log.info("doIt"); } public static class ServiceInterceptor implements Interceptor { @Override public void intercept(Invocation inv) { log.info("before ServiceInterceptor"); inv.invoke(); log.info("after ServiceInterceptor"); } }}
//被注入的属性@Slf4jpublic class OtherService { @Before(OtherInterceptor.class) public void doOther() { log.info("doOther"); } public static class OtherInterceptor implements Interceptor { @Override public void intercept(Invocation inv) { log.info("before otherInterceptor"); inv.invoke(); log.info("after otherInterceptor"); } }}
//获取增强实例public class AopDemo { public static void main(String[] args) { ProxyManager.me().setPrintGeneratedClassToConsole(true); Service service= Aop.get(Service.class); service.doIt(); }}
二、实现原理
jfinal 原生实现
原生实现原理如下,见 ProxyFactory :
解析被增强类的 class,根据模板生成被增强类的 java 代码
编译该 java 代码
加载增强后的class 文件
反射生成该 class 的实例
根据 @Inject 注解做依赖注入
其中最重要的步骤就是更新模板生成被增强类的 java 代码。其生成的代码如下:
package com.example.demo.jfinal;import com.jfinal.aop.Invocation;public class Service$$EnhancerByJFinal extends Service { @Override public void doIt() { Invocation inv = new Invocation(this, 1L, args -> { Service$$EnhancerByJFinal.super.doIt( ); return null; } ); inv.invoke(); }}
可以看到,代码中构建了 Invocation 并调用 invoke,其实就相当于把所有的执行逻辑放到 Invocation ,自动生成的增强类只是一层皮。接下来我们看下 Invocation 是如何实现执行逻辑的。
cglib 实现
cglib 实现原理 很简单,调用 cglib 即可
public <T> T get(Class<T> target) { return (T)net.sf.cglib.proxy.Enhancer.create(target, new CglibCallback()); }
CglibProxyFactory 就一行代码,就是调用 cglib 对 target 进行增强。切入逻辑在 CglibCallback 中
/** * CglibCallback. */class CglibCallback implements MethodInterceptor { public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //1. 构造拦截器链 MethodKey key = IntersCache.getMethodKey(targetClass, method); Interceptor[] inters = IntersCache.get(key); if (inters == null) { inters = interMan.buildServiceMethodInterceptor(targetClass, method); IntersCache.put(key, inters); } //2. 封装 Invocation 并执行 Invocation invocation = new Invocation(target, method, inters, x -> { return methodProxy.invokeSuper(target, x); } , args); invocation.invoke(); return invocation.getReturnValue(); }}
可以看出 cglib 实现的增强逻辑和原生实现的增强逻辑一致。都是构建 Invocation 并执行,只不过原生实现中通过 ProxyMethod 获取拦截器链,cglib 实现中使用InterceptorManager 构建拦截器链。
Invocation 实现织入逻辑
初始化
//构造方法 public Invocation(Object target, Long proxyMethodKey, Callback callback) { this(target, proxyMethodKey, callback, NULL_ARGS); } public Invocation(Object target, Long proxyMethodKey, Callback callback, Object... args) { this.action = null; this.target = target; ProxyMethod proxyMethod = ProxyMethodCache.get(proxyMethodKey); this.method = proxyMethod.getMethod(); this.inters = proxyMethod.getInterceptors(); this.callback = callback; this.args = args; }
构造方法如上,通过ProxyMethod 获取了拦截器列表,其思路是根据类和方法上的 @Before 构建拦截器。
执行逻辑
public void invoke() { if (index < inters.length) { inters[index++].intercept(this); } else if (index++ == inters.length) { // index++ ensure invoke action only one time try { // Invoke the action if (action != null) { returnValue = action.getMethod().invoke(target, args); } // Invoke the callback else { returnValue = callback.call(args); } } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t == null) {t = e;} throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t); } catch (RuntimeException e) { throw e; } catch (Throwable t) { throw new RuntimeException(t); } } }
执行逻辑也很简单,有拦截器则先执行拦截器,拦截器执行完毕则执行 callback
三、时序图
四、架构
整体架构
整体分为两层,Aop 和 Proxy 。Proxy 用于获取增强后的class 对象,包括生成增强的 java 代码、编译、加载 class;Aop 使用proxy增强后的代码并组装拦截器链以实现拦截逻辑
UML 类图
Proxy
Proxy 类图如下:
含义 | 类名 | 备注 |
对外接口 | Proxy | 提供 get 接口,根据 class 获取增强后实例 |
对外接口内部实现 | ProxyFactory | 虽然命名为 Factory ,但并不是工厂模式,而是 Proxy 的实现类 |
配置类 | ProxyManager | Proxy模块对外的配置类 |
功能实现类 |
| 代码生成器、代码编译器和class 加载器 |
实体 |
|
|
Aop
Aop 类图如下
含义 | 类名 | 备注 |
对外接口 | Aop |
|
对外接口内部实现 | AopFactory | Aop 接口的实现类 |
配置类 | AopManager | Aop 模块对外的配置类 |
功能实现类 |
| 使用 proxy 模块的能力完成切面能力 |
实体 |
|
|
五、总结
jfinal 的 Aop 实现本质上是构建 Invocation。原生实现通过生成 java 代码并编译的方式生成增强类,cglib 实现通过 cglib 增强目标类。(生成 java 代码并编译生成增强的类思路牛逼!!!)
按 对外接口、对外配置类、接口实现类、功能实现类、实体 对jfinal 的类进行分类非常清晰。
jfinal AOP 与 spring AOP 最大不同在于 jfinal 是极简设计,用户只需要学习三个核心概念:Interceptor、Before、Clear,这让学习成本低到极致
而 spring AOP 的概念一大堆,例如:Aspect、Advice、Joinpoint、Poincut、Introduction、Weaving、Around等等,并且需要引入IOC容器并配合大量的XML或者annotation来进行组件装配。概念太多让学习成本急剧上升,开发时头脑的负荷加重,认知成本很高,相应的开发效率也会降低
除了极简设计、学习成本低以外,jfinal AOP 的另一特色是采用动态编译方式实现 proxy, 在 java 界绝无仅有
同时还支持 cglib 进行增强模式实现,目的是为了支持 JRE 环境没有动态编译支持的场景