上節我們學習了,在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,個人覺得后者較靈活。