在Spring 2.0中集成AspectJ
副标题#e#
在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
#p#副标题#e#
在Tomcat下的配置
打开<Tomcat_Home>\bin\catalina.bat,在该批处理惩罚文件头部添加以下的配置:
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>
在①中,通过<aspect>指定LTW织入器需要处理惩罚的切面类,这些切面类是用AspectJ语法编写的。②处通过通配符指定需要举办织入操纵的方针类。通过..*将需要处理惩罚的方针类限制在项目类包下是一个较量好的要领,不然织入器将对所有类举办操纵,而这并不是我们期望的行为。
AspectJ织入切面团结Spring IoC容器打点切面实例
让AspectJ为Java类提供切面织入处事,同时让方针类和切面类享受Spring IoC依赖注入成果,这样,两者是细密地集成在一起了。
首先,我们来看一下需要AspectJ举办切面织入的方针类:
package com.baobaotao;
public class Waitress ...{
private String name;
public void serveTo(String client) ...{
System.out.println(name + " serves to " + client+"...");
}
public String getName() ...{
return name;
}
public void setName(String name) ...{
this.name = name;
}
}
Waitress拥有一个name属性和一个serveTo()要领。此刻我们需要通过AspectJ为Waitress举办切面织入,以便在侍者提供处事之前强制利用规矩用语:
#p#分页标题#e#
package com.baobaotao;
public aspect TestAspectj ...{
private pointcut traceServeTo() :execution(* serveTo(..));①切点
before(): traceServeTo() ...{②前置加强
System.out.println(message);
}
private String message; ③规矩用语
public void setMessage(String message)...{
this.message = message;
}
}
(注:为了可以或许编写AspectJ的切面,你首先需要从http://www.eclipse.org/aspectj/downloads.php下载AspectJ开拓插件,以支持AspectJ语法。今朝AspectJ别离为Eclipse、JBuilder、NetBeans、JDeveloper IDE.以及Emacs and JDEE提供了插件。)
TestAspectj切面类将对Waitress的serveTo()要领举办前置加强,在①处界说了切点,在②处界说了前置加强要领。另外,该切面类还拥有一个message属性,用于提供类型的处事前规矩用语,我们但愿通过设置,在Spring IoC容器中注入该属性。
在Spring设置文件中,我们可以按设置一般Bean相似的方法设置AspectJ切面类(TestApectj)和织入AspectJ的方针类(Waitress):
<bean id="aspectj" class="com.baobaotao.ThreadAspectj" factory-method="aspectOf">
<property name="message" value="How are you!"/>
</bean>
<bean id="waitress" class="com.baobaotao.Waitress">
<property name="name" value="Katty"/>
</bean>
留意,设置AspectJ切面类里,需要指定factory-method="aspectOf"属性,以便确保Spring从AspectJ获取切面实例,而非本身建设该实例。
为了让ThreadAspectj起浸染,虽然我们需要调解aop.xml的设置:
<aspectj>
<aspects>
<aspect name="com.baobaotao.ThreadAspectj "/>
</aspects>
<weaver options="-showWeaveInfo
-XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
<include within="com.baobaotao..*" />
</weaver>
</aspectj>
运行以下的测试代码(同样的,你需要为该类配置JVM javaagent参数):package com.baobaotao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class WaitressAspectjTest ...{
public static void main(String[] args) ...{
String configPath = "com/baobaotao /beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waitress waitress = (Waitress)ctx.getBean("waitress");
waitress.serveTo("Johnson");
}
}
节制台输出以下的信息:
①说明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打点容器外工具的成果让我们拥有了这个本领。
下面,我们将通过一个实例揭示这一神秘的成果。首先,来看一下我们但愿打点的两个规模工具:
#p#分页标题#e#
package com.baobaotao.configure;
import java.io.Serializable;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable ①
public class Thread implements Serializable...{
private String title;
private Topic topic;
//get/setter
public String toString()...{
return "title:"+title+";\ntopic:"+topic;
}
}
Thread是论坛主题的规模工具,一个论坛主题对应一个主帖,并拥有多个跟帖,为了简朴,这里仅保存主贴工具topic,Topic规模工具类如下所示:package com.baobaotao.configure;
import java.io.Serializable;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable ①
public class Topic implements Serializable...{
private String title;
private String content;
//get/setter
public String toString()...{
return "title:"+title+";content:"+content;
}
}
Thread和Topic在①处,都标注了@Configurable注解。仅仅标注了注解并没有任何用途,我们需要操作AspectJ LTW在加载这些规模工具类时为标注@Configurable注解的类织入切面。
首先,我们得将匹配@Configurable注解类的切面类AnnotationBeanConfigurerAspect地址的spring-aspects.jar类包添加到类路径上。spring-aspects.jar类包自己拥有一个aop.xml设置文件,其内容如下所示:<aspectj>
<aspects>
<aspect name="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
</aspects>
</aspectj>
该设置文件会将AnnotationBeanConfigurerAspect和AnnotationTransactionAspect切面类应用到所有类中。
AnnotationTransactionAspect用于处理惩罚@Transaction注解,这里我们没有用到。由于,我们但愿限制举办AspectJ切面织入方针类的范畴,所以我们需要再界说一个aop.xml文件:
<?xml version="1.0"?>
<aspectj>
<weaver options="-showWeaveInfo
-XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
<include within="com.baobaotao.configure..*" /> ① 使AspectJ织入器仅对该包下类举办操纵
</weaver>
</aspectj>
通过<weaver>的options属性的配置,指定在日志中显示织入操纵的信息,通过<include>元素指定需要举办AspectJ织入的方针类。可以简朴地将这个设置文件放到src/META-INF/目次下。
前面,我们提到过切面类AnnotationBeanConfigurerAspect实现了BeanFactoryAware接口,所以需要在Spring设置文件中设置它,以便其可以感知Spring IoC容器,另外我们还需要设置Thread和Topic规模工具Bean,为标注了@Configurable的规模工具提供依赖注入的成果:<bean class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"
factory-method="aspectOf"/>①
<bean class="com.baobaotao.configure.Topic" scope="prototype"> ② 设置规模工具
<property name="title" value="测试帖子"/>
<property name="content" value="测试内容"/>
</bean>
<bean class="com.baobaotao.configure.Thread" scope="prototype"> ③ 设置规模工具
<property name="title" value="测试的主题"/>
<property name="topic" ref="com.baobaotao.configure.Topic"/>
</bean>
在①处我们声明白一个AnnotationBeanConfigurerAspect Bean,而且界说了factory-method="aspectOf"属性,确保Spring从AspectJ获取切面实例,而不是实验本身去建设该实例。
Spring在aop定名空间中为设置AntationBeanConfigurerAspect提供了专门的设置元素:<aop:spring-configured/>,可以用这种简捷的设置替代①处的设置。在②和③处,我们在Spring IoC中设置了规模工具Bean。
至此,一切已经停当,我们可以编写一个测试类测试Spring打点容器外工具的成果:
package com.baobaotao.configure;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ConfigureAnnoAspectTest ...{
public static void main(String[] args) ...{
String configPath = "com/baobaotao/configure/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Thread thread = new Thread(); ① 象传统一样通过new结构规模工具
System.out.println(thread.toString()); ② 查察规模工具的信息
}
}
#p#分页标题#e#
在①处利用传统建设规模工具的方法结构一个Thread规模工具,在②处打印出该规模工具的信息。为ConfigureAnnoAspectTest类配置好JVM的javaagent参数,启用AspectJ LTW织入器,配置完成后,运行该测试类,节制台将输出以下的信息: …
① 以下两行暗示织入器注册切面类
INFO [main] (AspectJWeaverMessageHandler.java:55)
- [AspectJ] register aspect
org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect
INFO [main] (AspectJWeaverMessageHandler.java:55)
- [AspectJ] register aspect
org.springframework.transaction.aspectj.AnnotationTransactionAspect
…
②以下几行暗示织入器对匹配方针类举办织入操纵INFO [main] (AspectJWeaverMessageHandler.java:55)
- [AspectJ] weaving 'com/baobaotao/configure/Topic'
INFO [main] (AspectJWeaverMessageHandler.java:55) -
[AspectJ] Join point 'initialization(void com.baobaotao.configure.Topic.<init>())'
in Type 'com.baobaotao.configure.Topic' (Topic.java:8) advised by
afterReturning advice from
'org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect'
(AbstractBeanConfigurerAspect.aj:43)
INFO [main] (AspectJWeaverMessageHandler.java:55)
- [AspectJ] weaving 'com/baobaotao/configure/Thread'
INFO [main] (AspectJWeaverMessageHandler.java:55) -
[AspectJ] Join point 'initialization(void
com.baobaotao.configure.Thread.<init>())' in Type
'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
'org/springframework/context/support/ClassPathXmlApplicationContext'
④规模工具的信息
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的方法选择个中最简朴最适合的方法。