[Spring实战系列](17)编写切点与声明切面
2016-02-12 20:41
465 查看
切点用于准确定位应该在什么地方应用切面的通知。切点和通知是切面的最基本元素。
在SpringAOP中,需要使用AspectJ的切点表达式语言来定义切点。关于SpringAOP的AspectJ切点,最重要的一点是Spring仅支持AspectJ切点指示器的一个子集。
在Spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。
注意:
如下图所示的切点表达式表示当Singer的perform()方法执行时会触发通知。我们使用execution()指示器选择Singer的perform()方法。方法表达式以*号开始,标示了我们不关心返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用(..)标示切点选择任意的perform()方法,无论该方法的参数是什么。
除此之外,我们还可以对上面的匹配进行限制,可以使用within()指示器来限制匹配。
我们使用&&操作符把execution()和within()指示器连接在一起形成and关系(切点必须匹配所有的指示器)。
Spring的AOP配置元素简化了基于POJO切面的声明:
为了阐述SpringAOP,我们创建一个观众类(Audience)类:
Audience类并没有任何特别之处,她就是一个有几个方法的简单Java类。我们可以像其他类一样,利用XML把它注册为Spring应用上下文中的一个Bean:
我们需要SpringAOP就能把它成为一个切面。
2.1前置声明和后置声明
在SpringAOP中,需要使用AspectJ的切点表达式语言来定义切点。关于SpringAOP的AspectJ切点,最重要的一点是Spring仅支持AspectJ切点指示器的一个子集。
类型 | 说明 |
---|---|
arg() | 限制连接点匹配参数为指定类型的执行方法。 |
@args() | 限制连接点匹配参数由指定注解标注的执行方法。 |
execution() | 用于匹配的是连接点的执行方法。 |
this() | 限制连接点匹配AOP代理的Bean引用为指定类型的类。 |
target() | 限制连接点匹配目标对象为指定类型的类。 |
@target() | 限制连接点匹配特定的执行对象,这些对象对应的类具备指定类型的注解。 |
within() | 限制连接点匹配指定的类型。 |
@within() | 限制连接点匹配指定注解所标注的类型(当使用SpringAOP时,方法定义再由指定的注解所标注的类里)。 |
@annotation | 限制匹配带有指定注解连接点。 |
注意:
只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要的指示器。在此基础上,我们使用其他指示器来限制所匹配的切点。 |
1.编写切点
如下图所示的切点表达式表示当Singer的perform()方法执行时会触发通知。我们使用execution()指示器选择Singer的perform()方法。方法表达式以*号开始,标示了我们不关心返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用(..)标示切点选择任意的perform()方法,无论该方法的参数是什么。除此之外,我们还可以对上面的匹配进行限制,可以使用within()指示器来限制匹配。
我们使用&&操作符把execution()和within()指示器连接在一起形成and关系(切点必须匹配所有的指示器)。
2.在XML中声明切面
Spring的AOP配置元素简化了基于POJO切面的声明:类型 | 说明 |
---|---|
<aop:advisor> | 定义AOP通知器。 |
<aop:after> | 定义AOP后置通知(不管被通知的方法是否执行成功) |
<aop:after-returning> | 定义AOPafter-returning通知。 |
<aop:after-throwing> | 定义AOPafter-throwing通知。 |
<aop:around> | 定义AOP环绕通知。 |
<aop:aspect> | 定义切面。 |
<aop:aspectj-autoproxy> | 启用@AspectJ注解驱动的切面。 |
<aop:before> | 定义AOP前置通知。 |
<aop:config> | 顶层的AOP配置元素。大多数的<aop:*>元素必须包含在<aop:config>元素内。 |
<aop:declare-parents> | 为被通知的对象引入额外的接口,并透明的实现。 |
<aop:pointcut> | 定义切点。 |
[code]packagecom.sjf.bean;
/**
*观众类
*@authorsjf0115
*
*/
publicclassAudience{
publicvoidtakeSeats(){
System.out.println("theaudienceistakingtheirseats...");
}
publicvoidapplaud(){
System.out.println("verygood,clapclapclap...");
}
publicvoiddemandRefund(){
System.out.println("verybad,Wewantourmoneyback...");
}
}
Audience类并没有任何特别之处,她就是一个有几个方法的简单Java类。我们可以像其他类一样,利用XML把它注册为Spring应用上下文中的一个Bean:
[code]<beanid="audience"class="com.sjf.bean.Audience">
</bean>
我们需要SpringAOP就能把它成为一个切面。
2.1前置声明和后置声明
[code]<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop 'target='_blank'>http://www.springframework.org/schema/aop/spring-aop-4.2.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"> <beanid="singer"class="com.sjf.bean.Singer"></bean><beanid="audience"class="com.sjf.bean.Audience"></bean><aop:configproxy-target-class="true"><!--声明定义一个切面--><aop:aspectref="audience"><!--表演之前--><aop:beforemethod="takeSeats"pointcut="execution(*com.sjf.bean.Singer.perform(..))"/><!--表演之后--><aop:after-returningmethod="applaud"pointcut="execution(*com.sjf.bean.Singer.perform(..))"/><!--表演失败之后--><aop:after-throwingmethod="demandRefund"pointcut="execution(*com.sjf.bean.Singer.perform(..))"/></aop:aspect></aop:config></beans>
大多数的AOP配置元素必须在<aop:config>元素的上下文内使用。这条规则有几种例外场景,但是把Bean声明为一个切面时,我们总是从<aop:config>元素开始配置。
在<aop:config>元素内,我们可以声明一个或者多个通知器,切面或者切点。上述例子中,我们使用<aop:aspect>元素声明了一个简单的切面。ref元素引用了一个Bean(Audience),该Bean实现了切面的功能。ref元素应用的Bean提供了在切面上通知所调用的方法。
该切面应用了3个不同的通知。<aop:before>元素定义了匹配切点的方法执行之前调用前置通知方法,audienceBean的takeSeats()方法。<aop:after-returning>元素定义了一个返回后(after-returning)通知,在切点所匹配的方法调用之后在执行applaud()方法。<aop:after-throwing>元素定义了抛出异常后通知,如果所有匹配的方法执行时抛出任何异常,都将调用demandRefund()方法。
下面展示了通知逻辑如何嵌入到业务逻辑中:
在所有的通知元素中,pointcut属性定义了通知所应用的切点。pointcut属性的值是使用AspectJ切点表达式语法所定义的切点。
你或许注意到所有通知元素中的pointcut属性的值都是一样的,这是因为所有的通知都是应用到相同的切点上。这似乎违反了DRY(不要重复你自己)原则。我们做一下修改,可以使用<aop:pointcut>元素定义一个命名切点。[code]<aop:configproxy-target-class="true"><!--声明定义一个切面--><aop:aspectref="audience"><aop:pointcutid="singerPerfom"expression="execution(*com.sjf.bean.Singer.perform(..))"/><!--表演之前--><aop:beforemethod="takeSeats"pointcut-ref="singerPerfom"/><!--表演之后--><aop:after-returningmethod="applaud"pointcut-ref="singerPerfom"/><!--表演失败之后--><aop:after-throwingmethod="demandRefund"pointcut-ref="singerPerfom"/></aop:aspect></aop:config>
<aop:pointcut>元素定义了一个id为singerPerfom的切点,同时修改所有的通知元素,用pointcut-ref属性来引用这个命名切点。
2.2声明环绕通知
如果不适用成员变量存储信息,那么在前置通知和后置通知之间共享信息是非常麻烦的。我们希望实现这样一个功能:希望观众一直关注演出,并报告演出的演出时长。使用前置通知和后置通知实现该功能的唯一方式是:在前置通知中记录开始时间,并在某个后置通知中报告演出的时长。但这样的话,我们必须在一个成员变量中保存开始时间。因此我们可以使用环绕通知,只需在一个方法中实现即可。[code]publicvoidPerformTime(ProceedingJoinPointjoinPoint){//演出之前System.out.println("theaudienceistakingtheirseats...");try{longstart=System.currentTimeMillis();//执行演出操作joinPoint.proceed();longend=System.currentTimeMillis();//演出成功System.out.println("verygood,clapclapclap...");System.out.println("该演出共需要"+(end-start)+"milliseconds");}catch(Throwablee){//演出失败System.out.println("verybad,Wewantourmoneyback...");e.printStackTrace();}}
对于这个新的通知方法,我们会注意到它使用了ProceedingJoinPoint作为方法的入参。这个对象非常重要,因为它能让我们在通知里调用被通知的方法。如果希望把控制转给被通知的方法时,我们可以调用ProceedingJoinPoint的proceed()方法。如果忘记调用proceed()方法,通知将会阻止被通知方法的调用。我们还可以在通知里多次调用被通知方法,这样做的一个目的是实现重试逻辑,在被通知的方法执行失败时反复重试。
PerformTime()方法包含了之前3个通知方法的所有逻辑,并且该方法还会负责自身的异常处理。声明环绕通知与声明其他类型的通知并没有太大的区别,只需要<aop:around>元素。[code]<aop:configproxy-target-class="true"><!--声明定义一个切面--><aop:aspectref="audience"><aop:pointcutid="singerPerfom"expression="execution(*com.sjf.bean.Singer.perform(..))"/><!--声明环绕通知--><aop:aroundmethod="performTime"pointcut-ref="singerPerfom"/></aop:aspect></aop:config>
运行结果:像其他通知的XML一样,<aop:around>指定了一个切点和一个通知方法的名字。
theaudienceistakingtheirseats...
正在上演个人演唱会...
verygood,clapclapclap...
该演出共需要37milliseconds
2.3为通知传递参数
到目前为止,我们的切面很简单,没有任何的参数。但是有时候通知并不是仅仅是对方法进行简单包装,还需要校验传递给方法的参数值,这时候为通知传递参数就非常有用了。
下面是歌手的实体类:[code]packagecom.sjf.bean;/***歌手实体类*@authorsjf0115**/publicclassSinger{publicvoidperform(StringsongName){System.out.println("正在上演个人演唱会,演唱曲目为"+songName);}}
在这我们提供了一个Organizers(主办方)实体类,在歌手演唱之前截获歌手演唱的曲目,然后通知给大家:[code]packagecom.sjf.bean;/***主办方实体类*@authorsjf0115**/publicclassOrganizers{publicvoidBeforeSong(StringsongName){System.out.println("演唱会马上就开始了,演唱歌曲为"+songName);}publicvoidAfterSong(){System.out.println("演唱曲目结束,谢谢大家...");}}
我们像以前一样使用<aop:aspect>,<aop:before>和<aop:after>元素。但是这次我们通过配置实现将被通知方法的参数传递给通知。[code]<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/aop 'target='_blank'>http://www.springframework.org/schema/aop/spring-aop-4.2.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"> <beanid="singer"class="com.sjf.bean.Singer"></bean><beanid="Organizers"class="com.sjf.bean.Organizers"></bean><aop:config><!--声明定义一个切面--><aop:aspectref="Organizers"><aop:pointcutid="singerPerform"expression="execution(*com.sjf.bean.Singer.perform(String))andargs(song)"/><aop:pointcutid="singerPerform2"expression="execution(*com.sjf.bean.Singer.perform(..))"/><aop:beforemethod="BeforeSong"pointcut-ref="singerPerform"arg-names="song"/><aop:after-returningmethod="AfterSong"pointcut-ref="singerPerform2"/></aop:aspect></aop:config></beans>
关键之处在于切点定义和<aop:before>的arg-names属性。切点标示了Singer的perform()方法,指定了String参数。然后在args参数中标示了song作为参数。同样,<aop:before>元素引用了切点中song参数,标示该参数必须传递给Organizers的BeforeSong()方法。
运行结果:来源于:《Spring实战》
演唱会马上就开始了,演唱歌曲为你是我的眼泪
正在上演个人演唱会,演唱曲目为你是我的眼泪
演唱曲目结束,谢谢大家...
相关文章推荐
- Java反序列化漏洞之Weblogic、Jboss利用之payload生成工具
- java环境变量设置以及常见问题解决方法
- 新装myeclipse后的一系列设置汇总
- 35 个 Java 代码性能优化总结
- Java中怎么简单的使用正则表达式?
- Java中怎么简单的使用正则表达式?
- Notepad++自动格式化Java代码
- Java的多线程之同步篇三:同步阻塞、监视器、volatile、final、原子性、线程局部变量、锁测试与超时、读写锁
- 20个非常有用的Java程序片段
- java基础15集合List
- Java序列化示例
- 我就是试试Java中的Socket
- Spring
- Struts 2
- 华为机试题: 求n!中含有0的个数(JAVA)
- Java回调机制
- java注释模板
- Notepad++直接编译运行Java
- 深入理解Java虚拟机----(十一)线程安全与锁优化
- The Java™ Tutorials — Concurrency :Synchronized Methods 同步方法