在Java语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入、类加载期织入和运行期织入。编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中;而类加载期织入则指通过特殊的类加载器,在类字节码加载到JVM时,织入切面;运行期织入则是采用CGLib工具或JDK动态代理进行切面的织入。
AspectJ采用编译期织入和类加载期织入的方式织入切面,是语言级的AOP实现,提供了完备的AOP支持。它用AspectJ语言定义切面,在编译期或类加载期将切面织入到Java类中。
在低版本的Spring中,你只能通过接口定义切面,在Spring 2.0中你可以通过AspectJ的切点表达式语法定义切点,Spring 2.0采用AspectJ的解析包解析切点织入切面。但这并不是我们这篇文章要讲的内容。在这篇文章里,我们希望从更高的层面上集成Spring和AspectJ,直接采用AspectJ织入切面,并让Spring IoC容器管理切面实例。
Spring AOP提供了有限的AOP支持,在一般情况下,这些支持已经能够满足我们的开发要求,但如果对AOP有更高的要求(如实例化切面、属性访问切面等),则需要使用AspectJ的支持,而AspectJ又可以利用Spring IoC的依赖注入能力,两者相得益彰,琴瑟合鸣。
如何使用AspectJ LTW
我们前面提到过,AspectJ提供了两种切面织入方式,第一种通过特殊编译器,在编译期,将AspectJ语言编写的切面类织入到Java类中,可以通过一个Ant或Maven任务来完成这个操作;第二种方式是类加载期织入,也简称为LTW(Load Time Weaving)。这里,我们只介绍LTW的织入,编译期织入请参看:http://www.eclipse.org/aspectj/doc/released/devguide/antTasks.html。
使用AspectJ LTW有两个主要步骤,第一,通过JVM的-javaagent参数设置LTW的织入器类包,以代理JVM默认的类加载器;第二,LTW织入器需要一个aop.xml文件,在该文件中指定切面类和需要进行切面织入的目标类。下面,我们来了解一下具体的做法:
1.一般情况下,我们不会直接在DOS窗口中,通过Java命令启动应用或进行测试。这就要求我们在IDE环境下,或应用部署的环境下,设置JVM的参数。我们以Eclipse和Tomcat为例,分别讲述IDE和Web应用服务器中设置-javaapent JVM参数的方法。
在Eclipse下的设置
在Eclipse中,如果我们要改变JVM参数,可以在项目类导航树中选中某个可运行类->右键单击->Run As->Run...,可以在弹出的Run设置窗口设置该类的各项运行属性,切换到Arguments Tab页,在VM arguments中通过-javaagent指定AspectJ 织入器类包,如下图所示:
这里,我们设置为:-javaagent:D:/masterSpring/resources/aspectj-1.5.3/lib/aspectjweaver.jar
在Tomcat下的设置
打开
set JAVA_OPTS=-javaagent:D:/masterSpring/resources/aspectj-1.5.3/lib/aspectjweaver.jar
这样,Tomcat服务启动时,JVM就会使用这个参数了。
2.配置LTW织入器的aop.xml织入配置文件
LTW织入器在工作时,首先会查找类路径下META-INF /aop.xml的配置文件,并根据配置文件的设置进行织入的操作。下面是一个简单的aop.xml文件:
<aspectj> <aspects> <aspect name="com.baobaotao.aspectj.TestAspectJ"/> ①切面类 aspects> <weaver> <include within="com.baobaotao..*"/> ② 指定需要进行织入操作的目标类范围 weaver> aspectj> |
AspectJ织入切面结合Spring IoC容器管理切面实例
让AspectJ为Java类提供切面织入服务,同时让目标类和切面类享受Spring IoC依赖注入功能,这样,两者是紧密地集成在一起了。
首先,我们来看一下需要AspectJ进行切面织入的目标类:
package com.baobaotao; |
Waitress拥有一个name属性和一个serveTo()方法。现在我们需要通过AspectJ为Waitress进行切面织入,以便在侍者提供服务之前强制使用礼貌用语:
package com.baobaotao; |
TestAspectj切面类将对Waitress的serveTo()方法进行前置增强,在①处定义了切点,在②处定义了前置增强方法。此外,该切面类还拥有一个message属性,用于提供规范的服务前礼貌用语,我们希望通过配置,在Spring IoC容器中注入该属性。
在Spring配置文件中,我们可以按配置一般Bean相似的方式配置AspectJ切面类(TestApectj)和织入AspectJ的目标类(Waitress):
|
为了让ThreadAspectj起作用,当然我们需要调整aop.xml的配置:
|
package com.baobaotao; |
①说明AspectJ切面织入到Waitress..serveTo()中,且礼貌用语从Spring IoC中注入
From AspectJ:How are you!
Katty serves to Johnson...
从输出信息中,我们可以知道,Spring成功地管理了AspectJ的切面,AspectJ的切面类也成功地织入到目标类中。
让Spring管理容器外的对象
Spring为管理容器外创建的对象提供了一个AspectJ语法编写的切面类:
org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect,它位于spring-aspects.jar包中。spring-aspects.jar类包没有随Spring标准版一起发布,但你可以在完整版中找到它,位于Spring项目的dist目录下。该切面类匹配所有标注@Configurable的类,该注解类org.springframework.beans.factory.annotation.Configurable则位于spring.jar中。
AspectJ在类加载时,将AnnotationBeanConfigurerAspect切面将织入到标注有@Configurable注解的类中。
AnnotationBeanConfigurerAspect将这些类和Spring IoC容器进行了关联,AnnotationBeanConfigurerAspect本身实现了BeanFactoryAware的接口。
这样,标注了@Configurable的类通过AspectJ LTW织入器织入AnnotationBeanConfigurerAspect切面后,就和Spring IoC容器间接关联起来了,实现了Spring管理容器外对象的功能。
展现该功能的一个比较好的实例是管理Spring IoC容器外的领域对象。回想一下我们通常如何进行Dao类的单元测试:比如测试一个论坛主题ThreadDao。首先,我们需要在单元测试类中手工创建论坛主题Thread领域对象、帖子Topic领域对象、附件Attachment领域对象并设置好属性值,然后手工设置这些领域对象的关联关系。
对于习惯了使用Spring IoC依赖注入功能的开发者而言,可能更希望让Spring IoC容器来做这样工作——当然,原来我们就可以做这样的工作,在Spring配置文件中配置好领域对象,然后通过ctx.getBean(beanName)获取领域对象。但很多开发者可能并不喜欢这种方式。他们既希望以传统的new Thread()方式创建领域对象,但又能够享受Spring IoC所提供的依赖注入的好处。Spring管理容器外对象的功能让我们拥有了这个能力。
下面,我们将通过一个实例展现这一神秘的功能。首先,来看一下我们希望管理的两个领域对象:
package com.baobaotao.configure; |
package com.baobaotao.configure; |
|
该配置文件会将AnnotationBeanConfigurerAspect和AnnotationTransactionAspect切面类应用到所有类中。
AnnotationTransactionAspect用于处理@Transaction注解,这里我们没有用到。由于,我们希望限制进行AspectJ切面织入目标类的范围,所以我们需要再定义一个aop.xml文件:
|
|
在①处我们声明了一个AnnotationBeanConfigurerAspect Bean,并且定义了factory-method="aspectOf"属性,确保Spring从AspectJ获取切面实例,而不是尝试自己去创建该实例。
Spring在aop命名空间中为配置AntationBeanConfigurerAspect提供了专门的配置元素:
至此,一切已经就绪,我们可以编写一个测试类测试Spring管理容器外对象的功能:
package com.baobaotao.configure; |
在①处使用传统创建领域对象的方式构造一个Thread领域对象,在②处打印出该领域对象的信息。为ConfigureAnnoAspectTest类设置好JVM的javaagent参数,启用AspectJ LTW织入器,设置完成后,运行该测试类,控制台将输出以下的信息: …
① 以下两行表示织入器注册切面类
INFO [main] (AspectJWeaverMessageHandler.java:55) - [AspectJ] register aspect org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect - [AspectJ] register aspect org.springframework.transaction.aspectj.AnnotationTransactionAspect |
INFO [main] (AspectJWeaverMessageHandler.java:55) - [AspectJ] weaving 'com/baobaotao/configure/Topic' [AspectJ] Join point 'initialization(void com.baobaotao.configure.Topic. in Type 'com.baobaotao.configure.Topic' (Topic.java:8) advised by afterReturning advice from 'org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect' (AbstractBeanConfigurerAspect.aj:43) - [AspectJ] weaving 'com/baobaotao/configure/Thread' [AspectJ] Join point 'initialization(void com.baobaotao.configure.Thread. 'com.baobaotao.configure.Thread' (Thread.java:7) advised by afterReturning advice from 'org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect' (AbstractBeanConfigurerAspect.aj:43)' |
③以下表示织入器略过不在目标范围内的类
INFO [main] (AspectJWeaverMessageHandler.java:55) - [AspectJ] not weaving |
④领域对象的信息
title:测试的主题;
topic:title:测试帖子;content:测试内容
查看以上的信息,我们发现④处输出的领域信息是我们在Spring IoC容器中配置的信息,可见我们通过new Thread()创建的领域对象,其实已经从Spring IoC容器中获取到对应的Bean了。
这个过程参与的角色比较多,关系错踪复杂,我们有必须对这一过程重新进行梳理,找出角色间的关系和参与的操作,请看下图:
AspectJ LTW织入器(aspectjweaver.jar)根据aop.xml中配置信息,在类加载期将切面类(AnnotationBeanConfigurerAspect)织入到标注@Configurable的类(Thread和Topic)中。
Spring IoC容器中配置了AnnotationBeanConfigurerAspect,使其可以感知Spring IoC容器,此外,Spring还为标注了@Configurable的类配置了对应的Bean。这样,Thread和Topic通过new实例化对象时,其实是通过AnnotationBeanConfigurerAspect从容器中获取实例。
在这一过程中,我们有两个问题需要进一步说明:第一,AnnotationBeanConfigurerAspect是静态的类,也即一个ClassLoader对应一个实例;第二,AnnotationBeanConfigurerAspect通过类反射机制获取Thread和Topic的类全限定名:com.baobaotao.configure.Thread和com.baobaotao.configure.Topic,并用这个名称到Spring IoC容器中获取对应的Bean,因为如果配置时未指定Bean的名字,Spring使用类的全限定类作为Bean的名字。如果你希望采用命名的Bean,则需要在@Configurable中指定Bean的命名,如@Configurable(“thread”)。
小结
Spring 2.0对AOP进行了很大的改善,除了提供基于@ApsectJ和Schema的切面定义外,还允许集成AspectJ,即使用AspectJ切面织入功能,又可以通过Spring IoC管理切面类和目标类。所以,只要你愿意,完全可以使用AspectJ进行切面定义,而使用Spring 2.0进行Bean的管理。究竟如何选择,最好从实际项目的需要出发,以最Progaramtic的方式选择其中最简单最适合的方式。