1.AOP術語
- 編譯時:切面在目標類編譯時被織入。這需要特殊的編譯器,AspectJ在織入編譯器就以這種方式織入切面。
- 類加載時:切面在目標類加載到JVM時被織入。這需要特殊的ClassLoader,它可以在目標類被加載到程序之前增強類的字節代碼。AspectJ5的“加載時織入(LTW)”就以這種方式支持織入切面。
- 運行時:切面在程序運行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會動態創建一個代理對象來委託給目標對象。這就是Spring AOP織入切面的方式。
2.Spring對AOP的支持
- 經典的基於代理的AOP(各版本Spring)
- @AspectJ註解驅動的切面(僅Spring 2.0);
- 純POJO切面(僅Spring2.0);
- 注入式AspectJ切面(各版本Spring)
- 使用前一種創建接口的代理方式比使用代理類更受人喜歡,因爲它能夠更好地實現程序的鬆耦合。
- 被標記爲final的方法不能被通知。Spring爲目標類創建一個子類,需要被通知的任務方法都會被覆蓋並被織入通知,這對於final類型的方法是不可能的。
3.典型的Spring切面
package com.sin90lzc.test;
//觀衆類
public class Audience {
public Audience() {
}
//表演前找座位
public void takeSeats() {
System.out.println("The audience is taking their seats.");
}
//找到座位後關掉手機
public void turnOffCellPhones() {
System.out.println("The audience is turning off their cellphones");
}
//表演精彩時鼓掌
public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP");
}
//表演槽糕時要求退票
public void demandRefund() {
System.out.println("Boo!We want our money back!");
}
}
由Spring容器管理Audience:<bean id="audience" class="com.springinaction.springido1.Audience" />
3.1創建通知
通知類型 | 接口 |
---|---|
Before | org.springframework.aop.MethodBeforeAdvice |
After-returning | org.springframework.aop.AfterReturningAdvice |
After-throwing | org.springframework.aop.ThrowsAdvice |
Around | org.aopalliance.intercept.MethodInterceptor |
Introduction | org.springframework.aop.IntroductionInterceptor |
package com.sin90lzc.test;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
public class AudienceAdvice implements MethodBeforeAdvice,
AfterReturningAdvice, ThrowsAdvice {
private Audience audience;
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
audience.applaud();
}
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
audience.takeSeats();
audience.turnOffCellPhones();
}
public void afterThrowing(Throwable throwable) {
}
public Audience getAudience() {
return audience;
}
public void setAudience(Audience audience) {
this.audience = audience;
}
}
這裏一個類中實現了3種不同類型的AOP通知。
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {}
第一個參數代表要使用這個通知的方法。第二個參數是方法被調用時要傳遞的參數。最後一個參數是方法調用的目標(也就是被調用方法所在的對象)。public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {}
aferReturning方法的參數與MethodBeforeAdvice的before()方法的參數區別不大,只是第一個參數是從被調用方法返回的值。與MethodBeforeAdvice和AfterReturningAdvice不同的是,ThrowsAdvice不需要實現任何方法,它只是一個標記接口,告訴Spring相應的通知要求處理被拋出的異常。
周圍通知相當於前通知、返回後通知、拋出後通知的結合。AudienceAdvice類可以用周圍通知來重寫。如:
package com.sin90lzc.test;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class AudienceAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
audience.takeSeats();
audience.turnOffCellPhones();
Object returnValue = invocation.proceed();
audience.applaud();
return returnValue;
} catch (Exception e) {
audience.demandRefund();
throw e;
}
}
public Audience getAudience() {
return audience;
}
public void setAudience(Audience audience) {
this.audience = audience;
}
private Audience audience;
}
使用周圍通知的好處之一是能以簡潔的方式在一個方法裏定義前通知和後通知。利用周圍通知還可以檢查和修改被通知方法的返回值,讓我們可以在把方法的返回值傳遞給調用者之前對其進行一些後處理。AfterReturningAdvice只能對返回值進行檢查,但不能修改它。3.2 定義切入點和通知者
3.2.1聲明正則表達式切點
<bean id="performancePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*perform" />
</bean>
<bean id="audienceAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="audienceAdvice" />
<property name="pointcut" ref="performancePointcut" />
</bean>
DefaultPointcutAdvisor是個通知者類,它只是把通知關聯到切點。
<bean id="audienceAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="audienceAdvice" />
<property name="pattern" value=".*perform" />
</bean>
3.2.2定義AspectJ切點
<bean id="performancePointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
<property name="expression" value="execution(* Performer+.perform(..))" />
</bean>
<bean id="audienceAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="audienceAdvice" />
<property name="expression" value="execution(* Performer+.perform(..))" />
</bean>
通知者把通知與切點關聯起來,從而完整地定義一個切面。但是,切面在Spring裏是以代理方式實現的,所以仍然需要代理目標Bean才能讓通知者發揮作用。
3.3 使用ProxyFactoryBean
<bean id="duke" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="dukeTarget" />
<property name="interceptorNames">
<list>
<value>audienceAdvisor</value>
</list>
</property>
<property name="proxyInterfaces">
<list>
<value>com.springinaction.springido1.Performer</value>
</list>
</property>
</bean>
4.自動代理
- “基於Spring上下文裏聲明的通知者Bean的基本自動代理”:通知者的切點表達式用於決定哪個Bean和哪個方法要被代理。
- “基於@AspectJ註解驅動切面的自動代理”:切面裏包含的通知裏指定的切點將用於選擇哪個Bean和哪個方法要被代理。
4.1 爲Spring切面創建自動代理
- 像上一節中定義通知者Bean。
- 添加DefaultAdvisorAutoProxyCreator的Bean定義。注意不需要id:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
- 聲明目標Bean(像普通的Bean一樣聲明),但實際上會以代理Bean取代:
<bean id="duke" class="com.springinaction.springido1.PoeticJuggler" autowire="constructor"> <constructor-arg ref="sonnet29" /> </bean>
4.2 自動代理@AspectJ切面
package com.sin90lzc.test;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
//聲明切面
@Aspect
public class Audience {
public Audience() {
}
// 定義切入點
@Pointcut("execution(* *.perform(...))")
public void performance() {
}
@Before("performance()")
public void takeSeats() {
System.out.println("The audience is taking their seats.");
}
@Before("performance()")
public void turnOffCellPhones() {
System.out.println("The audience is turning off their cellphones");
}
@AfterReturning("performance()")
public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP");
}
@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Boo!We want our money back!");
}
}
@Pointcut註解在@AspectJ切面裏定義一個可重用的切點。賦給@Pointcut註解的值是一個AspectJ切點表達式,表示這個切點應該匹配任意一個類的perform()方法。這個切點的名稱是源自注解所應用的方法的名稱,所以本例中切點的名稱就是performance()。實際的performance()方法與此並不相關,方法本身只是一個標記。<aop:aspectj-autoproxy />
這個元素會在Spring上下文創建一個AnnotaionAwareAspectJAutoProxyCreator,從而根據@Pointcut註解定義的切點來自動代理相匹配的Bean。要使用該配置元素,還需要添加aop命名空間:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
</beans>
AnnotaionAwareAspectJAutoProxyCreator也基於典型的Spring通知者來創建代理,也就是說,它也會完成與DefaultAdvisorAutoProxyCreator同樣的工作。因此,如果在Spring上下文裏聲明瞭通知者Bean,它們也會被自動用於通知被代理的Bean。
4.3 定義純粹的POJO切面
AOP配置元素 | 功能 |
---|---|
<aop:advisor> | 定義一個AOP通知者 |
<aop:after> | 定義一個AOP後通知(不考慮被通知的方法是否成功返回) |
<aop:after-returning> | 定義一個AOP返回後通知 |
<aop:after-throwing> | 定義一個AOP拋出後通知 |
<aop:around> | 定義一個AOP周圍通知 |
<aop:aspect> | 定義一個切面 |
<aop:before> | 定義一個AOP前通知 |
<aop:config> | 頂級AOP元素。大多數<aop:*>元素必須包含在<aop:config>裏 |
<aop:Pointcut> | 定義一個切點 |
下面示例把audience Bean轉化爲切面:
<bean id="audience" class="com.springinaction.springido1.Audience" />
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(* *.perform(..))" />
<aop:before method="takeSeats" pointcut-ref="performance" />
<aop:before method="turnOffCellPhones" pointcut="execution(* *.perform(..))" />
<aop:after-returning method="applaud"
pointcut-ref="performance" />
<aop:after-throwing method="demandRefund"
pointcut-ref="performance" />
</aop:aspect>
</aop:config>