1FWhat is AspectJ?
说到AOP框架我们就不得不探讨一下AspectJ了,在Spring 开始支持AOP之初,也是大量借鉴了AspectJ的思想与编写习惯,在语法方面则完全借鉴了AspectJ,所以AspectJ对于Spring AOP的影响可以说非常巨大。但实际上,Spring AOP与AspectJ使用了完全不同的技术来实现切面框架。Spring AOP通过代理(JDK,CGLIB)模型,采用动态织入技术,在内存中临时生成AOP代理类,因此也被称为运行时增强;而AspectJ则通过编译器在class字节码级别实现了静态织入的技术,也称为编译时增强。采用不同的技术,当然会有各自的利弊,采用动态织入技术不需要特殊编译器的支持,更符合项目级别的模块解耦,但是Spring AOP的切面必须基于Spring Beans进行切面,也就是说普通Java对象(POJO),无法通过Spring AOP建立切面;而AspecJ却没有这样的限制,可以在任意Java对象上建立切面。本节我们主要探讨一下AspectJ静态织入技术与原理。
2F静态织入
静态织入,即指Java代码在编译时期就将切面代码织入的技术,需要特殊编译器的支持。这里我们实践出真知,来看看究竟ApectJ的静态织入的到底是什么东东。为了方便,作者这里直接使用AspectJ项目支持的Eclipse扩展插件AJDT(Eclipse官网下载)来构建工程(AJDT只是AspectJ为了方便项目开发,将AspectJ编译器、语法提示集成在了插件中,读者也可以直接基于AspectJ命令行ajc进行编译,或者基于maven的工程项目可以使用aspectj-maven-plugin插件编译)。安装好AJDT插件后,就可以在Eclipse中建立一个AJDT类型的项目了,这里我们建立一个AspecJ示例工程:
新建项目之后,我们直接对main函数进行增强处理,AspectJ的开发与Spring AOP的advice开发方法稍有不同,但基本上还是java代码,只是代码文件的后缀名需要以 .aj作为后缀名(否则AspectJ编译器将不会进行增强处理):
增强代码(advice)就可以写在 *.aj 文件中进行编写了,下图是对于main函数的before与after增强的逻辑:
首先,需要注意增强处理类并没有使用class关键字,而是用了aspect关键字作为类的声明,并且切面表达式语法与Spring AOP中的使用语法也完全相同(Spring AOP与AspectJ切面语法保持了一致),其他的语法想必小伙伴们扫一眼就看明白了,这里就不累述了,我们运行工程,结果如下:
不出所料,对于main函数切点的增强处理正确的执行了。细心的小伙伴在这里应该会有疑问,Spring AOP切面框架不是无法对于static与final类型函数进行切面吗?的确如此,但是AspectJ却没有这方面限制,这也是AspectJ基于字节码技术的优势。
3F静态织入原理
关于AOP框架,相信很多小伙伴,都听过很多遍静态织入与动态织入了,但是却不明白到底什么是静态织入,或者说AspectJ是如何静态织入的?其实实现很复杂,但是原理很简单,静态织入就是将advice代码编译入切点处。这里,我们通过反编译工具JD-GUI来看一下AspectJ在上面示例是如何将before与after增强处理静态织入的,反编译.class结果如下:
通过反编译Main.class就可以看到,在main函数入口处与函数返回时,分别调用了before与after的增强逻辑,也就是说ApectJ将我们的增强处理以调用的方式编译进了切入点出,这就是静态织入实质,我们再来看一下增强代码的编译结果:
可以发现,实际上AspectJ实际上将源码中aspect关键字声明的类编译成了一个正常的java类,并且在增强声明中,采用了AspectJ注解方式来进行增强声明。AspectJ基于字节码级别的织入技术,使得其切面几乎可以关注任何Java对象生名周期的任何时期,因此非常强大。
4F动态代理原理
AspectJ虽然强大,但是Spring AOP的切面却是基于JDK或CGLIB动态代理技术实现的,所以想要了解Spring AOP的核心原理还是需要先研究一下动态代理技术,关于JDK与CGLIB的具体原理作者将会在下篇Spring AOP之代理之争一文中深挖,这里我们意在动态代理与静态织入的比较。这里,我们来看JDK是如何实现对象代理。
这里我们通过JDK代理对Spring AOP之初探黄龙一文中的WorkService类进行代理,并且添加before与after增强,使用JDK 代理需要我们实现InvocationHandler类来进行代理行为的控制,如下所示:
此时,我们就能使用ServiceProxyInvocationHandler来对Java对象进行代理了,并且在ServiceProxyInvocationHandler中,我们添加了类似切面的Before与After增强处理。OK,现在我们使用ServiceProxyInvocationHandler来对WorkerSerivce对象进行代理:
运行结果可以发现,通过JDK代理技术的增强处理也被正确的执行了。其实,Spring AOP框架的核心原理也是通过使用InvocationHandler与Proxy这两个类来实现对Spring Beans代理的。JDK代理使用起来尽然如此简单,但实际上JDK代理框架在背后做了很多手脚,会将根据代理的对象生成专门的代理类,为了便于深挖,我们将JDK代理生成的代理类保存到磁盘上来分析(默认动态代理直接在内存中生成,并通过Java类加载机制加载):
运行示例程序后会在项目的com.sum.proxy目录下会生成$Proxy0.class,二话不说,反编译它!
通过反编译,可以看到JDK代理生成的$Proxy0.class实际上是一个实现了IWorkService接口的子类,也就是说动态代理实际是通过接口生成增强过后的子类(CGLIB直接对代理对象继承)来实现的,因此代理是基于继承机制的,这也是Spring AOP框架无法对static与final进行切面的原因,由于不支持重写。
5F小结
面对AspectJ与Spring AOP项目中到底应该选择哪个框架?这要看项目中具体需要的增强需求了,如果本项目是基于Spring构建的项目,并且要增强的对象也都是Spring Beans,那么Spring AOP无疑是最好的选择。但如果项目比较老,并且需要对任意POJO都添加切面支持,那就只有AspectJ框架了。Java生态发展到今天,可以说已经很难离开Spring框架的依赖了(这句话没毛病吧?),所以Spring AOP也被广泛的应用在了很多Java组件中。本文并没有详细深挖动态织入技术,意在向小伙伴们分析静态织入的原理和展示动态与静态织入的具体区别,至于动态代理的详细分析,将会在Spring AOP系列的下一篇文章“Spirng AOP之代理之争”中深入讨论,敬请期待。