AspectJ的织入方式及其原理概要
对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程。对于这个过程,一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术,这点另一篇博文已经有分析了,这里主要重点分析一下静态织入。
ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
关于ajc编译器,是一种能够识别aspect语法的编译器,它是采用java语言编写的,由于javac并不能识别aspect语法,便有了ajc编译器,注意ajc编译器也可编译java文件。为了更直观了解aspect的织入方式,我们打开前面案例中已编译完成的HelloWord.class文件,反编译后的java代码如下:
//编译后织入aspect类的HelloWord字节码反编译类 public class HelloWord { public HelloWord() { } public void sayHello() { System.out.println("hello world !"); } public static void main(String[] args) { HelloWord helloWord = new HelloWord(); HelloWord var10000 = helloWord; try { //MyAspectJDemo 切面类的前置通知织入 MyAspectJDemo.aspectOf().ajc$before$com_zejian_demo_MyAspectJDemo$1$22c5541(); //目标类函数的调用 var10000.sayHello(); } catch (Throwable var3) { MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574(); throw var3; } //MyAspectJDemo 切面类的后置通知织入 MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574(); } }
显然AspectJ的织入原理已很明朗了,当然除了编译期织入,还存在链接期(编译后)织入,即将aspect类和java目标类同时编译成字节码文件后,再进行织入处理,这种方式比较有助于已编译好的第三方jar和Class文件进行织入操作,由于这不是本篇的重点,暂且不过多分析,掌握以上AspectJ知识点就足以协助理解Spring AOP了
Spring AOP的优点
Spring AOP 与ApectJ 的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP 并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势
同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。
在AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解。请注意,Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器
再说区别和联系
AspectJ
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件(只是我们Spring一般只用到它的注解,其余都是Spring自己实现的)。
Spring AOP
Spring提供了四种类型(说三种也对)的Aop支持:
1.基于经典的SpringAOP
1.使用ProxyFactoryBean创建Bean。显然已经过时了,因为每个Bean你都使用它包装一下,麻烦
2.引入自动代理创建器。如:AbstractAutoProxyCreator的所有子类,实现有多种。(EnableAspectJAutoProxy注解驱动原理就是它)
前置通知:org.springframework.aop.MethodBeforeAdvice
后置通知:org.springframework.aop.AfterReturningAdvice
异常通知:org.springframework.aop.ThrowsAdvice
环绕通知:org.aopalliance.intercept.MethodInterceptor (注意这个是aopalliance包下的接口,Spring提供实现类非常之多,比如:MethodBeforeAdviceInterceptor(包装了MethodBeforeAdvice),CacheInterceptor等等非常多)
2.纯POJO切面
这个方式也就比较古老了,纯基于xml,简单例子如下:
<-- Spring Aop的命名空间可以将纯POJO转换为切面,实际上这些POJO只是提供了满足切点的条件时所需要调用的方法,但是,这种技术需要XML进行配置,不能支持注解 --> <bean id="pojoAdvice" class="com.njust.learning.spring.pojoaop.PojoAdvice"></bean> <aop:config> <aop:pointcut id="p" expression="execution (* *.add(..))"/> <aop:aspect ref="pojoAdvice"> <aop:before method="before" pointcut-ref="p"></aop:before> <!--通过设置returning来将返回值传递给通知--> <aop:after-returning method="after" pointcut-ref="p" returning="returnval"/> <aop:around method="around" pointcut-ref="p"/> <!--通过设置returning来将异常对象传递给通知--> <aop:after-throwing method="afterThrowing" pointcut-ref="p" throwing="e"/> </aop:aspect> </aop:config>
3.@ASpectJ注解驱动的切面
- 需要注意的是,Spring只使用到了它的注解功能,并不使用它的核心功能。核心解析功能由Spring自己实现的
@Aspect public class HelloAspect { @Pointcut("execution(* com.fsx.service.*.*(..)) ") public void point() { } @Before("point()") public void before() { System.out.println("this is from HelloAspect#before..."); } @After("point()") public void after() { System.out.println("this is from HelloAspect#after..."); } @AfterReturning("point()") public void afterReturning() { System.out.println("this is from HelloAspect#afterReturning..."); } @AfterThrowing("point()") public void afterThrowing() { System.out.println("this is from HelloAspect#afterThrowing..."); } // 此处需要注意:若写了@Around方法,那么最后只会执行@Around和@AfterReturning 其它的都不会执行 //@Around("point()") //public void around() { // System.out.println("this is from HelloAspect#around..."); //} }
4.注入式AspectJ切面(其实与Spring并无多大的关系,这个就是使用AspectJ这个框架实现Aop编程) 需用用到AspectJ
自己的编译器,因此其实也可以说这个不属于Spring的
- 这种方式上面已经做了案例了,此处忽略(使用较少,但Spring也给与了支持)
联系
我们借助于Spring Aop的命名空间可以将纯POJO转换为切面,实际上这些POJO只是提供了满足切点的条件时所需要调用的方法,但是,这种技术需要XML进行配置,不能支持注解
所以spring借鉴了AspectJ的切面,以提供注解驱动的AOP,本质上它依然是Spring基于代理的AOP,只是编程模型与AspectJ完全一致,这种风格的好处就是不需要使用XML进行配置(XML方式已经淘汰嘛,哈哈)
最后
博主希望通过本文,让读者能够了解到Spring AOP和AspectJ的区别与联系。