AspectJ容許使用註解用於定義切面、切入點和加強處理,而Spring框架則能夠識別並根據這些註解來生成AOP代理。Spring只是使用了和AspectJ 5同樣的註解,但並無使用AspectJ的編譯器或者織入器,底層依然使用SpringAOP來實現,依然是在運行時動態生成AOP代理,所以不須要增長額外的編譯,也不須要AspectJ的織入器支持。而AspectJ採用編譯時加強,因此AspectJ須要使用本身的編譯器來編譯Java文件,還須要織入器。java
爲了啓用Spring對@AspectJ切面配置的支持,並保證Spring容器中的目標Bean被一個或多個切面自動加強,必須在Spring配置文件中配置以下內容(第四、九、十、15行):git
<?xml version="1.0" encoding="utf-8"?> <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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 啓動@AspectJ支持 --> <aop:aspectj-autoproxy/> </beans>
所謂自動加強,指的是Spring會判斷一個或多個切面是否須要對指定的Bean進行加強,並據此自動生成相應的代理,從而使得加強處理在合適的時候被調用。若是不打算使用XML Schema的配置方式,則應該在Spring配置文件中增長以下片斷來啓用@AspectJ支持(即上面的<aop:aspectj-autoproxy />和下面建立Bean的方式選擇一種便可啓用@AspectJ支持):github
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
上面配置的是一個Bean後處理器,該處理器將會爲容器中Bean生成AOP代理。spring
爲了在Spring應用中啓動@AspectJ支持,還須要在用用的類加載路徑下增長兩個AspectJ庫:aspectweaver.jar和aspectjrt.jar,直接使用AspectJ安裝路徑下的lib目錄下的這兩個Jar文件便可,固然,也能夠在Spring解壓縮文件夾的lib/aspectj路徑下找到它們。下面是項目內容的截圖:express
定義切面Beanapp
當啓用了@AspectJ支持後,只要咱們在Spring容器中配置一個帶@AspectJ註釋的Bean,Spring將會自動識別該Bean,並將該Bean做爲切面處理。下面是一個例子:框架
@Aspect public class LogAspect { }
切面類(用@Aspect修飾的類)和其餘類同樣能夠有方法和屬性的定義,還可能包括切入點、加強處理的定義。當咱們使用@Aspect來修飾一個Java類後,Spring將不會把該Bean當成組件Bean處理,所以當Spring容器檢測到某個Bean使用了@AspectJ標註以後,負責自動加強的後處理Bean將會忽略該Bean,不會對該Bean進行任何加強處理。測試
使用Before加強處理spa
當咱們在一個切面類裏使用@Before來標註一個方法時,該方法將做爲Before加強處理。使用@Before標註時,一般須要指定一個value屬性值,該屬性值指定一個切入點表達式(既能夠是一個已有的切入點,也能夠直接定義切入點表達式),用於指定該加強處理將被織入哪些切入點。看例子:代理
package com.abc.advice; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeAdviceTest { //匹配com.abc.service下的類中以before開始的方法 @Before("execution(* com.abc.service.*.before*(..))") public void permissionCheck() { System.out.println("模擬權限檢查"); } }
上面的程序使用@Aspect修飾了BeforeAdviceTest類,這代表該類是一個切面類,在該貼面裏定義了一個permissionCheck方法——這個方法原本沒有什麼特殊之處,但由於使用了@Before來標註該方法,這就將該方法轉換成一個Before加強處理。這個@Before註解中,直接指定了切入點表達式,指定com.abc.service包下的類中以before開始的方法的執行做爲切入點。現假設咱們在com.abc.service下有一個這樣一個類:
package com.abc.service; import org.springframework.stereotype.Component; @Component public class AdviceManager { //這個方法將被BeforeAdviceTest類的permissionCheck匹配到 public void beforeAdvice() { System.out.println("方法: beforeAdviceTest"); } }
從上面的代碼來看,這個AdviceManager是一個純淨的Java類,它絲絕不知道將被誰來加強,也不知道將被進行怎樣的加強——正式由於AdviceManager類的這種「無知」,纔是AOP的最大魅力:目標類能夠被無限的加強。
在Spring配置文件中配置自動搜索Bean組件,配置自動搜索切面類,SpringAOP自動對Bean組件進行加強,下面是Spring配置文件代碼:
<?xml version="1.0" encoding="utf-8"?> <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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 啓動@AspectJ支持 --> <aop:aspectj-autoproxy/> <!-- 指定自動搜索Bean組件,自動搜索切面類 --> <context:component-scan base-package="com.abc.service,com.abc.advice"> <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect" /> </context:component-scan> </beans>
主程序很是簡單,經過Spring容器獲取AdviceManager Bean,並調用Bean的beforeAdvice方法:
package com.abc.main; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.abc.service.AdviceManager; @SuppressWarnings("resource") public class AOPTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); AdviceManager manager = context.getBean(AdviceManager.class); manager.beforeAdvice(); } }
執行主程序,將看到如下結果:
使用Before加強處理只能在目標方法執行以前織入加強,使用Before加強處理無需理會目標方法的執行,因此Before處理沒法阻止目標方法的執行。Before加強處理執行時,目標方法還未得到執行機會,因此Before加強處理沒法訪問目標方法的返回值。
使用AfterReturning加強處理
和使用@Before註解的使用相似,使用@AfterReturning來標註一個AfterReturning加強處理,該處理將在目標方法正常完成後被織入。使用@AfterReturning時能夠指定兩個屬性:
pointcut/value:這兩個屬性的做用是同樣的,都用於指定該切入點對應的切入表達式。一樣的,既能夠是一個已有的切入點,也能夠是直接定義的切入點。當指定了pointcut屬性後,value的屬性值將會被覆蓋
returning:指定一個返回值形參名,加強處理定義的方法能夠經過該形參名來訪問目標方法的返回值。
在com.abc.advice包下面增長AfterReturningAdviceTest,這個類定義了一個AfterReturning加強處理:
package com.abc.advice; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; @Aspect public class AfterReturningAdviceTest { //匹配com.abc.service下的類中以afterReturning開始的方法 @AfterReturning(returning="returnValue", pointcut="execution(* com.abc.service.*.afterReturning(..))") public void log(Object returnValue){ System.out.println("目標方法返回值:" + returnValue); System.out.println("模擬日誌記錄功能..."); } }
並在AdviceManager類中增長如下內容:
//將被AfterReturningAdviceTest的log方法匹配 public String afterReturning() { System.out.println("方法:afterReturning"); return "afterReturning方法"; }
正如上面程序中看到的,程序中使用@AfterReturning註解時,指定了一個returning屬性,該屬性的返回值是returnValue,這代表容許在加強方法log中使用名爲returnValue的形參,該形參表明目標方法的返回值。在測試類AOPTest的main方法中增長調用本方法的語句,運行測試類,能夠看到如下結果:
@AfterReturning註解的returning屬性所指定的形參名必須對應加強處理中的一個形參名,當目標方法執行之後,返回值做爲相應的參數傳入給加強處理方法。
須要注意的是,使用@AfterReturning屬性還有一個額外的做用,它可用於限定切入點只匹配具備對應返回值類型的方法——假設上面的log方法的參數returnValue的類型爲String,那麼該切入點只匹配com.abc.service.impl包下的返回值爲String的全部方法。固然,上面的log方法返回值類型爲Object,代表該切入點可匹配任何返回值的方法。除此以外,雖然AfterReturning加強處理能夠訪問到目標方法的返回值,但它不可改變這個返回值。
使用AfterThrowing加強處理
使用@AfterThrowing註解可用於標註一個AfterThrowing加強處理,這個處理主要用於處理程序中未處理的異常。使用這個註解時能夠指定兩個屬性:
pointcut/value:這兩個屬性的做用是同樣的,都用於指定該切入點對應的切入表達式。一樣的,既能夠是一個已有的切入點,也能夠是直接定義的切入點。當指定了pointcut屬性後,value的屬性值將會被覆蓋
throwing:指定一個返回值形參名,加強處理定義的方法可經過該形參名來訪問目標方法中所拋出的異常對象。
在com.abc.advice包下面增長AfterThrowingAdviceTest,這個類定義了一個AfterThrowing加強處理:
package com.abc.advice; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; @Aspect public class AfterThrowingAdviceTest { @AfterThrowing(throwing="ex", pointcut="execution(* com.abc.service.*.afterThrow*(..))") public void handleException(Throwable ex) { System.out.println("目標方法拋出異常:" +ex); System.out.println("模擬異常處理"); } }
並在AdviceManager類中增長如下內容:
//將被AfterThrowingAdviceTest的handleException方法匹配 public void afterThrowing() { System.out.println("方法: afterThrowing"); try { int a = 10 / 0; } catch (ArithmeticException ae) { System.out.println("算術異常已被處理"); } String s = null; System.out.println(s.substring(0,3)); }
正如上面程序中看到的,程序中使用@AfterThrowing註解時,指定了一個throwing屬性,該屬性的值是ex,這代表容許在加強方法handleException中使用名爲ex的形參,該形參表明目標方法的拋出的異常對象。運行測試類,能夠看到如下結果:
須要注意的是:若是一個異常在程序內部已經處理,那麼Spring AOP將不會處理該異常。只有當目標方法拋出一個未處理的異常時,該異常將會做爲對應的形參傳給加強處理的方法。和AfterReturning相似的是,正確方法的參數類型能夠限定切點只匹配指定類型的異常——假如上面的handleException方法的參數類型爲NullPointerException,那麼若是目標方法只拋出了ArithmaticException,則Spring AOP將不會處理這個異常。固然,handleException的參數類型爲Throwable,則匹配了全部的Exception。
從測試結果中能夠看到,AfterThrowing處理雖然能夠對目標方法的異常進行處理,但這種處理與直接使用catch捕捉不一樣:catch捕捉意味着徹底處理該異常,若是catch塊中沒有從新拋出新異常,則該方法能夠正常結束;而AfterThrowing處理雖然處理了該異常,但它不能徹底處理該異常,這個異常依然會傳播到上一級調用者(本例中爲JVM,故會致使程序終止)。
《Spring中的AOP系列3、4、五》的代碼在這裏:點擊下載,歡迎留言提意見。
【未完,待續】