@AspectJ相關文章html
《spring AOP 之二:@AspectJ註解的3種配置》java
《spring AOP 之三:使用@AspectJ定義切入點》spring
《spring AOP 之四:@AspectJ切入點標識符語法詳解》express
與 AspectJ 相同的是,Spring AOP 一樣須要對目標類進行加強,也就是生成新的 AOP 代理類;與 AspectJ 不一樣的是,Spring AOP 無需使用任何特殊命令對 Java 源代碼進行編譯,它採用運行時動態地、在內存中臨時生成「代理類」的方式來生成 AOP 代理。編程
Spring 容許使用 AspectJ Annotation 用於定義方面(Aspect)、切入點(Pointcut)和加強處理(Advice),Spring 框架則可識別並根據這些 Annotation 來生成 AOP 代理。Spring 只是使用了和 AspectJ 5 同樣的註解,但並無使用 AspectJ 的編譯器或者織入器(Weaver),底層依然使用的是 Spring AOP,依然是在運行時動態生成 AOP 代理,並不依賴於 AspectJ 的編譯器或者織入器。安全
簡單地說,Spring 依然採用運行時生成動態代理的方式來加強目標對象,因此它不須要增長額外的編譯,也不須要 AspectJ 的織入器支持;而 AspectJ 在採用編譯時加強,因此 AspectJ 須要使用本身的編譯器來編譯 Java 文件,還須要織入器。app
爲了啓用 Spring 對 @AspectJ 方面配置的支持,並保證 Spring 容器中的目標 Bean 被一個或多個方面自動加強,必須在 Spring 配置文件中配置以下片斷,框架
AOP的做用這裏就再也不做說明了,下面開始講解一個很簡單的入門級例子。
引用一個猴子偷桃,守護者守護果園抓住猴子的小情節。
一、猴子偷桃類(普通類): 函數
二、守護者類(聲明爲Aspect): 源碼分析
三、XML配置文件:
四、測試類:
五、控制檯輸出:
解說:
1寫了一個猴子正在偷桃的方法。
2寫了一個標誌爲@Aspect的類,它是守護者。它會在猴子偷桃以前發現猴子,並在猴子偷桃以後抓住猴子。
原理:
A、@Aspect的聲明表示這是一個切面類。
B、@Pointcut使用這個方法能夠將com.samter.common.Monkey.stealPeaches(..)方法聲明爲poincut即切入點。做用,在stealPeaches方法被調用的時候執行2的foundMonkey方法。其中execution是匹配方法執行的切入點,也就是spring最經常使用的切入點定義方式。
C、@Before(value="foundMonkey()"):@Before聲明爲在切入點方法執行以前執行,然後面沒有直接聲明切入點,而是value="foundMonkey()",是由於若是@afterReturning等都有所改動的時候都必須所有改動,因此統一用Pointcut的foundMonkey代替,這樣子有改動的時候僅需改動一個地方。其餘@AfterReturning類同。
3是xml配置文件,裏面有具體的註釋。
特別說明:Guardian類裏面的@Pointcut("execution(* com.samter.common.Monkey.stealPeaches(..))"),若是stealPeaches有參數則..表示全部參數,@AfterReturning("foundMonkey() && args(name,..)")的&& args(name,..)能夠獲取切入點方法stealPeaches的參數。
總結:這裏列舉了一個簡單的例子,可是不難引伸到應用中,當你寫一個登錄系統的時候,你或許要記錄誰成功登錄了系統,誰登錄系統密碼錯誤等等的信息,這樣子你用切面是再合適不過的了,總之當你的事務邏輯都設計到日誌、安全檢查、事務管理等等共同的內容的時候,用切面是要比你沒有一個事務邏輯類都有相關代碼或者相關引用好得多。
一、經過 Spring 的 XML Schema 配置方式:
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" <!--schema方式配置--> xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!-- 啓動 @AspectJ 支持 --> <aop:aspectj-autoproxy /> <bean id="horseman" class="com.dxz.aop.demo4.Horseman" /> <bean id="swordman" class="com.dxz.aop.demo4.Swordman" /> <bean class="com.dxz.aop.demo4.StorageAdvisor" /> </beans>
固然,若是咱們但願徹底啓動 Spring 的「零配置」功能,則還須要啓用 Spring 的「零配置」支持,讓 Spring 自動搜索指定路徑下 Bean 類。
所謂自動加強,指的是 Spring 會判斷一個或多個方面是否須要對指定 Bean 進行加強,並據此自動生成相應的代理,從而使得加強處理在合適的時候被調用。
若是不打算使用 Spring 的 XML Schema 配置方式,則應該在 Spring 配置文件中增長以下片斷來啓用 @AspectJ 支持。
二、經過<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>啓動@AspectJ(此時schema中不須要相關的AOP配置)
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 啓動@AspectJ支持 --> <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/> <!-- 啓動@AspectJ支持 效果同上,若兩個同時添加即會執行兩次before方法--> <!-- <aop:aspectj-autoproxy/> --> <bean id="horseman" class="com.dxz.aop.demo4.Horseman" /> <bean id="swordman" class="com.dxz.aop.demo4.Swordman" /> <bean class="com.dxz.aop.demo4.StorageAdvisor" /> </beans>
上面配置文件中的 AnnotationAwareAspectJAutoProxyCreator 是一個 Bean 後處理器(BeanPostProcessor),該 Bean 後處理器將會爲容器中 Bean 生成 AOP 代理,當啓動了 @AspectJ 支持後,只要咱們在 Spring 容器中配置一個帶 @Aspect 註釋的 Bean,Spring 將會自動識別該 Bean,並將該 Bean 做爲方面 Bean 處理。
在 Spring 容器中配置方面 Bean(即帶 @Aspect 註釋的 Bean),與配置普通 Bean 沒有任何區別,同樣使用 <bean.../> 元素進行配置,同樣支持使用依賴注入來配置屬性值;若是咱們啓動了 Spring 的「零配置」特性,同樣可讓 Spring 自動搜索,並裝載指定路徑下的方面 Bean。
不用xml配置文件的狀況下,經過@Configuration來裝配Spring bean,@EnableAspectJAutoProxy來啓動spring AOP功能。見《Spring 3.1新特性之二:@Enable*註解的源碼,spring源碼分析之定時任務Scheduled註解》
使用 @Aspect 標註一個 Java 類,該 Java 類將會做爲方面 Bean,以下面代碼片斷所示:
package com.dxz.aop.demo6; import org.aspectj.lang.annotation.Aspect; // 使用 @Aspect 定義一個方面類 @Aspect public class LogAspect { // 定義該類的其餘內容 //... }
方面類(用 @Aspect 修飾的類)和其餘類同樣能夠有方法、屬性定義,還可能包括切入點、加強處理定義。
當咱們使用 @Aspect 來修飾一個 Java 類以後,Spring 將不會把該 Bean 當成組件 Bean 處理,所以負責自動加強的後處理 Bean 將會略過該 Bean,不會對該 Bean 進行任何加強處理。
開發時無須擔憂使用 @Aspect 定義的方面類被加強處理,當 Spring 容器檢測到某個 Bean 類使用了 @Aspect 標註以後,Spring 容器不會對該 Bean 類進行加強。
下面將會考慮採用 Spring AOP 來改寫前面介紹的例子:
下面例子使用一個簡單的 Chinese 類來模擬業務邏輯組件:
Chinese.java
package com.dxz.aop.demo6; import org.springframework.stereotype.Component; @Component public class Chinese { // 實現 Person 接口的 sayHello() 方法 public String sayHello(String name) { String ret = name + " Hello , Spring AOP"; System.out.println(ret); return ret; } // 定義一個 eat() 方法 public void eat(String food) { System.out.println("我正在吃 :" + food); } }
提供了上面 Chinese 類以後,接下來假設一樣須要爲上面 Chinese 類的每一個方法增長事務控制、日誌記錄,此時能夠考慮使用 Around、AfterReturning 兩種加強處理。
先看 AfterReturning 加強處理代碼。
AfterReturningAdviceTest.java
package com.dxz.aop.demo6; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; // 定義一個方面 @Aspect public class AfterReturningAdviceTest { // 匹配 com.dxz.aop.demo6 包下全部類的下的全部方法的執行做爲切入點 @AfterReturning(returning = "rvt", pointcut = "execution(* com.dxz.aop.demo6.*.*(..))") public void log(Object rvt) { System.out.println("AfterReturningAdviceTest==獲取目標方法返回值 :" + rvt); } }
上面 Aspect 類使用了 @Aspect 修飾,這樣 Spring 會將它當成一個方面 Bean 進行處理。其中程序中粗體字代碼指定將會在調用 org.crazyit.app.service.impl 包下的全部類的全部方法以後織入 log(Object rvt) 方法。
再看 Around 加強處理代碼:
package com.dxz.aop.demo6; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; // 定義一個方面 @Aspect public class AroundAdviceTest { // 匹配 com.dxz.aop.demo6 包下全部類的下的全部方法的執行做爲切入點 @Around("execution(* com.dxz.aop.demo6.*.*(..))") public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable { System.out.println("AroundAdviceTest==執行目標方法以前,模擬開始事務 ..."); // 執行目標方法,並保存目標方法執行後的返回值 Object rvt = jp.proceed(new String[] { "被改變的參數" }); System.out.println("AroundAdviceTest==執行目標方法以後,模擬結束事務 ..."); return rvt + " 新增的內容"; } }
與前面的 AfterReturning 加強處理相似的,此處一樣使用了 @Aspect 來修飾前面 Bean,其中粗體字代碼指定在調用com.dxz.aop.demo6 包下的全部類的全部方法的「先後(Around)」 織入 processTx(ProceedingJoinPoint jp) 方法須要指出的是,雖然此處只介紹了 Spring AOP 的 AfterReturning、Around 兩種加強處理,但實際上 Spring 還支持 Before、After、AfterThrowing 等加強處理,關於 Spring AOP 編程更多、更細緻的編程細節,能夠參考《輕量級 Java EE 企業應用實戰》一書。
本示例採用了 Spring 的零配置來開啓 Spring AOP,所以上面 Chinese 類使用了 @Component 修飾,而方面 Bean 則使用了 @Aspect 修飾,方面 Bean 中的 Advice 則分別使用了 @AfterReturning、@Around 修飾。接下來只要爲 Spring 提供以下配置文件便可:
applicationContext-aop6.xml
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 指定自動搜索 Bean 組件、自動搜索方面類 --> <context:component-scan base-package="com.dxz.aop.demo6"> <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect" /> </context:component-scan> <!-- 啓動 @AspectJ 支持 --> <aop:aspectj-autoproxy /> </beans>
接下來按傳統方式來獲取 Spring 容器中 chinese Bean、並調用該 Bean 的兩個方法,程序代碼以下:
BeanTest.java
package com.dxz.aop.demo6; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BeanTest { public static void main(String[] args) { // 建立 Spring 容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-aop6.xml"); Chinese p = ctx.getBean("chinese", Chinese.class); System.out.println(p.sayHello("張三")); p.eat("西瓜"); } }
從上面開發過程能夠看出,對於 Spring AOP 而言,開發者提供的業務組件、方面 Bean 並無任何特別的地方。只是方面 Bean 須要使用 @Aspect 修飾便可。程序不須要使用特別的編譯器、織入器進行處理。
運行上面程序,將能夠看到以下執行結果:
雖然程序是在調用 Chinese 對象的 sayHello、eat 兩個方法,但從上面運行結果不難看出:實際執行的絕對不是 Chinese 對象的方法,而是 AOP 代理的方法。也就是說,Spring AOP 一樣爲 Chinese 類生成了 AOP 代理類。這一點可經過在程序中增長以下代碼看出:
System.out.println(p.getClass());
上面代碼能夠輸出 p 變量所引用對象的實現類,再次執行程序將能夠看到上面代碼產生class com.dxz.aop.demo6.Chinese$$EnhancerBySpringCGLIB$$7d0b6d20的輸出,這纔是 p 變量所引用的對象的實現類,這個類也就是 Spring AOP 動態生成的 AOP 代理類。從 AOP 代理類的類名能夠看出,AOP 代理類是由 CGLIB 來生成的。
若是將上面程序程序稍做修改:只要讓上面業務邏輯類 Chinese 類實現一個任意接口——這種作法更符合 Spring 所倡導的「面向接口編程」的原則。假設程序爲 Chinese 類提供以下 Person 接口,並讓 Chinese 類實現該接口:
Person.java
package com.dxz.aop.demo6; public interface Person { String sayHello(String name); void eat(String food); }
Chinese修改實現Person接口:
@Component public class Chinese implements Person {
接下來讓 BeanTest 類面向 Person 接口、而不是 Chinese 類編程。即將 BeanTest 類改成以下形式:
BeanTest.java
package com.dxz.aop.demo6; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BeanTest { public static void main(String[] args) { // 建立 Spring 容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-aop6.xml"); Person p = ctx.getBean("chinese", Person.class); System.out.println(p.sayHello("張三")); p.eat("西瓜"); System.out.println(p.getClass()); } }
原來的程序是將面向 Chinese 類編程,如今將該程序改成面向 Person 接口編程,再次運行該程序,程序運行結果沒有發生改變。只是 System.out.println(p.getClass()); 將會輸出 class com.sun.proxy.$Proxy10,這說明此時的 AOP 代理並非由 CGLIB 生成的,而是由 JDK 動態代理生成的。
Spring AOP 框架對 AOP 代理類的處理原則是:若是目標對象的實現類實現了接口,Spring AOP 將會採用 JDK 動態代理來生成 AOP 代理類;若是目標對象的實現類沒有實現接口,Spring AOP 將會採用 CGLIB 來生成 AOP 代理類——不過這個選擇過程對開發者徹底透明、開發者也無需關心。
Spring AOP 會動態選擇使用 JDK 動態代理、CGLIB 來生成 AOP 代理,若是目標類實現了接口,Spring AOP 則無需 CGLIB 的支持,直接使用 JDK 提供的 Proxy 和 InvocationHandler 來生成 AOP 代理便可。關於如何 Proxy 和 InvocationHandler 來生成動態代理不在本文介紹範圍以內,若是讀者對 Proxy 和 InvocationHandler 的用法感興趣則可自行參考 Java API 文檔或《瘋狂 Java 講義》。
在spring3.1及以上,spring能夠不用xml配置裝載bean了,在@Configuration註解的環境裏,能夠經過@EnableAspectJAutoProxy啓動spring AOP功能。微調上面的示例以下:
在@Aspect中
package com.dxz.aop.demo6; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.springframework.context.annotation.Configuration; // 定義一個方面 @Aspect @Configuration public class AfterReturningAdviceTest { // 匹配 com.dxz.aop.demo6 包下全部類的下的全部方法的執行做爲切入點 @AfterReturning(returning = "rvt", pointcut = "execution(* com.dxz.aop.demo6.*.*(..))") public void log(Object rvt) { System.out.println("AfterReturningAdviceTest==獲取目標方法返回值 :" + rvt); } }
增長spring配置類
package com.dxz.aop.demo6; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; @Configuration @ComponentScan @EnableAspectJAutoProxy @Import({AfterReturningAdviceTest.class})/*@Aspect能夠生效,至關於Configuration類做用,都是配置類*/ public class AppConfig { @Bean(name = "chinese") public Chinese chinese() { return new Chinese(); } }
啓動類:
package com.dxz.aop.demo6; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test6 { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Person outPut = (Person) context.getBean("chinese"); outPut.sayHello("duan"); } }
結果:
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@c4437c4: startup date [Tue Dec 26 17:03:32 CST 2017]; root of context hierarchy AfterReturningAdviceTest==獲取目標方法返回值 :com.dxz.aop.demo6.Chinese@72967906 duan Hello , Spring AOP AfterReturningAdviceTest==獲取目標方法返回值 :duan Hello , Spring AOP
3、註解
2.1 @Aspect
做用是把當前類標識爲一個切面供容器讀取
2.2 @Before
標識一個前置加強方法,至關於BeforeAdvice的功能,類似功能的還有
2.3 @AfterReturning
後置加強,至關於AfterReturningAdvice,方法正常退出時執行
2.4 @AfterThrowing
異常拋出加強,至關於ThrowsAdvice
2.5 @After
final加強,不論是拋出異常或者正常退出都會執行
2.6 @Around
環繞加強,至關於MethodInterceptor
2.7 @DeclareParents
引介加強,至關於IntroductionInterceptor
execution函數用於匹配方法執行的鏈接點,語法爲:
execution(方法修飾符(可選) 返回類型 方法名 參數 異常模式(可選))
參數部分容許使用通配符:
* 匹配任意字符,但只能匹配一個元素
.. 匹配任意字符,能夠匹配任意多個元素,表示類時,必須和*聯合使用
+ 必須跟在類名後面,如Horseman+,表示類自己和繼承或擴展指定類的全部類
示例中的* chop(..)解讀爲:
方法修飾符 無
返回類型 *匹配任意數量字符,表示返回類型不限
方法名 chop表示匹配名稱爲chop的方法
參數 (..)表示匹配任意數量和類型的輸入參數
異常模式 不限
更多示例:
void chop(String,int)
匹配目標類任意修飾符方法、返回void、方法名chop、帶有一個String和一個int型參數的方法
public void chop(*)
匹配目標類public修飾、返回void、方法名chop、帶有一個任意類型參數的方法
public String *o*(..)
匹配目標類public修飾、返回String類型、方法名中帶有一個o字符、帶有任意數量任意類型參數的方法
public void *o*(String,..)
匹配目標類public修飾、返回void、方法名中帶有一個o字符、帶有任意數量任意類型參數,但第一個參數必須有且爲String型的方法
也能夠指定類:
public void examples.chap03.Horseman.*(..)
匹配Horseman的public修飾、返回void、不限方法名、帶有任意數量任意類型參數的方法
public void examples.chap03.*man.*(..)
匹配以man結尾的類中public修飾、返回void、不限方法名、帶有任意數量任意類型參數的方法
指定包:
public void examples.chap03.*.chop(..)
匹配examples.chap03包下全部類中public修飾、返回void、方法名chop、帶有任意數量任意類型參數的方法
public void examples..*.chop(..)
匹配examples.包下和全部子包中的類中public修飾、返回void、方法名chop、帶有任意數量任意類型參數的方法
能夠用這些表達式替換StorageAdvisor中的代碼並觀察效果
除了execution(),Spring中還支持其餘多個函數,這裏列出名稱和簡單介紹,以方便根據須要進行更詳細的查詢
4.1 @annotation()
表示標註了指定註解的目標類方法
例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示標註了@Transactional的方法
4.2 args()
經過目標類方法的參數類型指定切點
例如 args(String) 表示有且僅有一個String型參數的方法
4.3 @args()
經過目標類參數的對象類型是否標註了指定註解指定切點
如 @args(org.springframework.stereotype.Service) 表示有且僅有一個標註了@Service的類參數的方法
4.4 within()
經過類名指定切點
如 with(examples.chap03.Horseman) 表示Horseman的全部方法
4.5 target()
經過類名指定,同時包含全部子類
如 target(examples.chap03.Horseman) 且Elephantman extends Horseman,則兩個類的全部方法都匹配
4.6 @within()
匹配標註了指定註解的類及其全部子類
如 @within(org.springframework.stereotype.Service) 給Horseman加上@Service標註,則Horseman和Elephantman 的全部方法都匹配
4.7 @target()
全部標註了指定註解的類
如 @target(org.springframework.stereotype.Service) 表示全部標註了@Service的類的全部方法
4.8 this()
大部分時候和target()相同,區別是this是在運行時生成代理類後,才判斷代理類與指定的對象類型是否匹配
表達式可由多個切點函數經過邏輯運算組成
5.1 &&
與操做,求交集,也能夠寫成and
例如 execution(* chop(..)) && target(Horseman) 表示Horseman及其子類的chop方法
5.2 ||
或操做,求並集,也能夠寫成or
例如 execution(* chop(..)) || args(String) 表示名稱爲chop的方法或者有一個String型參數的方法
5.3 !
非操做,求反集,也能夠寫成not
例如 execution(* chop(..)) and !args(String) 表示名稱爲chop的方法可是不能是隻有一個String型參數的方法