上节我们学习了,在Java类中使用注解@Aspect注解将该类声明为一个切面,那么问题来了,如果你想声明为切面的Java类是别人封装好的jar包时要肿么办,总不能该别人的源码吧!强大的Spring会容许有这样的Bug出现么,难道没有解决办法么,当然有解决方案,Spring除了可以在Java类中将其配置为切面,还可以在XML中将一个Java类配置成一个切面:
AOP元素 | 用途 |
---|---|
<aop:advisor> | 定义AOP通知器 |
<aop:after> | 定义一个后置通知(不管目标方法是否执行成功) |
<aop:after-returning> | 定义AOP返回通知 |
<aop:after-throwing> | 定义AOP异常通知 |
<aop:around> | 定义环绕通知 |
<aop:aspect> | 定义一个切面 |
<aop:aspectj-autoproxy> | 启动@AspectJ注解驱动的切面 |
<aop:before> | 定义一个AOP前置通知 |
<aop:config> | 顶层AOP配置元素。大多数的<aop:*>元素都必须包含在<aop:config>元素内 |
<aop:declare-parents> | 以透明的方式为被通知的对象引入额外的接口 |
<aop:pointcut> | 定义一个切点 |
我们之前已经看过了<aop:adpectj-autoproxy>元素,他能够自动代理AspectJ注解的通知类。aop的其他元素可以让我们直接在XML中配置切面,而不使用注解,下面我们定义一个不使用任何注解的Audience类:
package com.spring.aop.service.aop;
/**
* <dl>
* <dd>Description:观看演出的切面</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月3日 下午9:58:09</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Aspect
public class Audience {
/**
* 目标方法执行之前调用
*/
public void silenceCellPhone() {
System.out.println("Silencing cell phones");
}
/**
* 目标方法执行之前调用
*/
public void takeSeats() {
System.out.println("Taking seats");
}
/**
* 目标方法执行完后调用
*/
public void applause() {
System.out.println("CLAP CLAP CLAP");
}
/**
* 目标方法发生异常时调用
*/
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
现在看来Audience类和普通的Java类没有任何的区别,但是我们只需要在Xml中稍作配置,他就可以成为一个切面。
1.声明前置和后置通知
<aop:config>
<aop:aspect ref="audience">
<aop:before pointcut="execution(** com.spring.aop.service.Perfomance.perform(..)"
method="silenceCellPhone"/>
<aop:before pointcut="execution(** com.spring.aop.service.Perfomance.perform(..)"
method="takeSeats"/>
<aop:after-returning pointcut="execution(** com.spring.aop.service.Perfomance.perform(..)"
method="applause"/>
<aop:after-throwing pointcut="execution(** com.spring.aop.service.Perfomance.perform(..)"
method="demandRefund"/>
</aop:aspect>
</aop:config>
聪明的小伙伴一定又发现了,相同的切点我们写了四次,这是不科学的,强大的Spring不会允许有这样的Bug出现,你猜对了,可以使用<aop:pointcut>元素定义一个公共的切点,而且这个切点还可以定义在切面类的外边,供其他的切面使用:
<aop:config>
<aop:pointcut id="performance"
expression="execution(** com.spring.aop.service.Perfomance.perform(..)" />
<aop:aspect ref="audience">
<aop:before pointcut-ref="performance" method="silenceCellPhone"/>
<aop:before pointcut-ref="performance" method="takeSeats"/>
<aop:after-returning pointcut-ref="performance" method="applause"/>
<aop:after-throwing pointcut-ref="performance"method="demandRefund"/>
</aop:aspect>
</aop:config>
2.在Xml中配置环绕通知
上面我们介绍了在Xml中配置前置通知、后置通知等四种通知,现在我们看一下怎么在Xml中配置环绕通知,先看一下要声明为切面的Java类中的方法:
/**
* 环绕通知
* @param jp 通过它调用目标方法
*/
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
在Xml中配置环绕通知:
<aop:config>
<aop:aspect ref="audience">
<!-- 也可以放在<aop:aspect>外,<sop:config>内 ,可以供多个切面使用-->
<aop:around pointcut-ref="performance" method="watchPerformance"/>
</aop:aspect>
</aop:config>
上面我们可以看到,我们使用了<aop:around>元素声明环绕通知。
3.为通知传递参数
与在使用注解配置一样,在Xml中也可以为通知传递参数,先看看统计歌曲播放次数的TrackCounter类:
package com.spring.aop.service.aop;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* <dl>
* <dd>Description:统计每首歌曲播放次数的AOP</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月4日 上午8:19:24</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
public class TrackCounter {
private Map<Integer,Integer> trackCounts = new HashMap<Integer,Integer>();
/**
* 将统计播放次数的方法声明为前置通知
* @param trackNumber 歌曲id
*/
public void countTrack(int trackNumber){
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount+1);
}
public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber)?trackCounts.get(trackNumber):0;
}
}
在Xml中我们可以使用aop元素,将TrackCounter配置成一个切面:
<bean id="trackCounter" class="com.spring.aop.service.aop.TrackCounter" />
<bean id="cd" class="com.spring.aop.service.impl.BlankDisc">
<property name="title" value="Sgt. Pepper's Lonely Hearts Club Band"/>
<property name="artist" value="The Beatles"/>
<property name="tracks">
<list>
<value>Sgn. Pepper's Lonelu Hears Club Band</value>
<value>Wiith a Litter Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
</list>
</property>
</bean>
<aop:config>
<aop:aspect ref="trackCounter">
<aop:pointcut id="trackPlayed"
expression="execution(* com.spring.aop.service.CompactDisc.playTrack(int)) and args(trackNumber)"/>
<aop:after pointcut-ref="trackPlayed" method="countTrack"/>
</aop:aspect>
</aop:config>
我们注意到在定义切点的时候,我们使用的是 and
进行连接限定符,而不是&&
,是因为&
在Xml中要特殊的含义。
4.通过切面引入新功能
在Xml中我们可以使用<aop:declare-parents>元素为被通知的类引入新方,在Xml中的配置是这样的:
<aop:config>
<!--default-impl="com.spring.aop.service.impl.DefaultEncoreable":通过类名指定添加功能的实现 -->
<!-- delegate-ref="encoreableDelegate":通过bean ID指定添加功能的实现 -->
<aop:aspect>
<aop:declare-parents
types-matching="com.spring.aop.service.Perforance+"
implement-interface="com.spring.aop.service.Encoreable"
delegate-ref="encoreableDelegate"
/>
</aop:aspect>
</aop:config>
<bean id="encoreableDelegate" class="com.spring.aop.service.impl.DefauleEncoreable"/>
可以看出<aop:declare-parents>元素包含三个属性:
- types-matching:指定添加方法的接口
- mplement-interface:指定添加的新功能的接口
- default-impl/delegate-ref:指定添加新功能接口的实现类,其中default-impl直接表示委托,delegate-ref指定一个bean,个人觉得后者较灵活。