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!");
- }
- }
通知類型 | 接口 |
---|---|
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 {}
與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;
- }
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!");
- }
- }
- <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>