在Spring AOP中,需要使用AspectJ的切点表达式语言来定义切点。下表列出了Spring AOP所支持的AspectJ切点指示器:
AspectJ指示器 | 描述 |
---|---|
arg() | 限制连接点匹配参数为指定类型的执行方法 |
@args() | 限制连接点匹配参数由指定注解标注的执行方法 |
execution() | 用于匹配是连接点的执行方法 |
this() | 限制连接点匹配AOP代理的Bean引用的指定类型的类 |
target() | 限制连接点匹配目标对象为执行类型的类 |
@target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解 |
within() | 限制连接点匹配指定的类型 |
@within() | 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的 注解所标注的类里) |
@annotation() | 限制匹配带有指定注解连接点 |
编写切点
如下所示的切点表达式表示当Instrument的play()
方法执行时会触发通知。
1 | execution(* com.spring.entity.Instrument.play(..)) |
使用execution()
指示器选择Instrument的play()
方法。方法表达式以*
开始,表示返回任意类型的返回值。然后指定了全限定类名和方法名。对于参数列表(..)
标识切点选择任意的pay()
方法,无论方法的入参是什么。
使用Spring的Bean()指示器
Spring2.5引入了新的bean()指示器。如:
1 | execution(* com.spring.entity.Instrument.play()) and bean(eddie) |
表示,执行Instrument的play()
方法时应用通知,并且Bean的ID为eddie。
XML中配置AOP
在Spring XML中配置AOP使用
AOP配置元素 | 描述 |
---|---|
<aop:advisor> | 定义AOP通知器 |
<aop:after> | 定义AOP后置通知(不管被通知的方法是否执行成功) |
<aop:after-returning> | 定义AOP after-returning通知 |
<aop:after-throwing> | 定义AOP after-throwing通知 |
<aop:around> | 定义AOP环绕通知 |
<aop:aspect> | 定义切面 |
<aop:aspectj-autoproxy> | 启用@AspectJ注解驱动切面 |
<aop:before> | 定义AOP前置通知 |
<aop:config> | 顶层的AOP配置元素 |
<aop:declare-parents> | 为被通知的对象引入额外的接口,并透明的实现 |
<aop:pointcut> | 定义切点 |
为了演示Spring AOP,现在定义一个观众类 Audience:
1 | public class Audience { |
在Spring XML中配置该Bean:
1 | <bean id="audience" class="com.spring.entity.Audience"/> |
声明前置和后置通知
将audience Bean变成一个切面:
1 | <aop:config> |
四个切点的表达式完全一样,我们可以简化上述写法:
1 | <aop:config> |
再次实例化kenny:
1 | public class Play { |
结果发现报错:
1 | Exception in thread "main" java.lang.ClassCastException: |
原因暂时还不晓得…😢解决办法,在<aop:config>
元素添加proxy-target-class="true"
:
1 | <aop:config proxy-target-class="true"> |
输出:
1 | 观众入座 |
现在在Instrumentalist的perform()
方法里制造一个异常:
1 | public void perform() { |
实例化kenny输出:
1 | 观众入座 |
声明环绕通知
如果不使用成员变量,那么在前置通知和后置通知之间共享信息是非常麻烦的。可以使用环绕通知代替前置通知和后置通知,现在在Audience类里添加一个新的方法:
1 | public void watch(ProceedingJoinPoint joinpoint){ |
对于新的方法,我们使用了ProceedingJoinPoint作为参数,这个对象可以在通知里调用被通知的方法!!我们要把控制转给被通知的方法时,必须调用ProceedingJoinPoint的proceed()
方法。
修改<aop:config>
元素:
1 | <aop:config proxy-target-class="true"> |
实例化kenny输出:
1 | 观众入座 |
假如不调用ProceedingJoinPoint的proceed()
方法发现输出为:
1 | 观众入座 |
这样我们使用AOP就没啥意义了。
我们甚至可以重复调用ProceedingJoinPoint的proceed()
方法,重复执行perform()
方法,输出:
1 | 观众入座 |
为通知传递参数
定义一个新的参赛者,他是一个读心者,由MindReader接口所定义:
1 | public interface MindReader { |
魔术师Magician实现该接口:
1 | public class Magician implements MindReader{ |
再定义一个Magician所要侦听的志愿者,首先定义一个思考者接口:
1 | public interface Thinker { |
志愿者Volunteer实现该接口:
1 | public class Volunteer implements Thinker{ |
接下来使用Spring AOP传递Volunteer的thoughts参数,以此实现Magician的侦听。。。:
1 | <bean id="magician" class="com.spring.entity.Magician"/> |
arg-names
属性传递了参数给interceptThoughts()
方法。
测试:
1 | public class TestIntercept { |
输出:
1 | 侦听志愿者的心声 |
通过切面引入新的方法
现在假设要给Performer派生类添加一个新的方法,传统做法是找到所有派生类,让后逐个增加新的方法或者实现。这不但很累而且假设第三方实现没有源码的话,这个过程会变得很困难。幸好,通过Spring AOP可以不必入侵性地改变原有地实现。比如,现在要给所有演出者添加一个receiveAward()
方法:
新增一个接口Contestant:
1 | public interface Contestant { |
由OutstandingContestant实现:
1 | public class OutstandingContestant implements Contestant{ |
XML:
1 | <aop:config proxy-target-class="true"> |
或者:
1 | <bean id="contestantDelegate" class="com.spring.entity.OutstandingContestant"/> |
types-matching
指定所要添加新方法的派生类实现的接口,implement-interface
指定要实现新的接口,default-impl
指定这个接口的实现类。
测试:
1 | public class Play { |
输出:
1 | 唱:May Rain |