Spring核心系列之AOP(一)
Hello,大家好,今天來給大家講一講Spring中的AOP,面向切面程式設計,它在Spring的整個體系中佔有著重要地位。本文還是以實踐為主,註解切入注入,OK,文章結構:
- @AspectJ 詳解
- Spring AOP – AspectJ註解
1. @AspectJ 的由來
提到AspectJ,其實很多人是有誤解的,很多人只知道在Spring中使用Aspect那一套註解,以為是Spring開發的這一套註解,這裡我覺得有責任和大家澄清一下。AspectJ是一個AOP框架,它能夠對java程式碼進行AOP編譯(一般在編譯期進行),讓java程式碼具有AspectJ的AOP功能(當然需要特殊的編譯器),可以這樣說AspectJ是目前實現AOP框架中最成熟,功能最豐富的語言,更幸運的是,AspectJ與java程式完全相容,幾乎是無縫關聯,因此對於有java程式設計基礎的工程師,上手和使用都非常容易. 其實AspectJ單獨就是一門語言,它需要專門的編譯器(ajc編譯器).Spring AOP 與ApectJ的目的一致,都是為了統一處理橫切業務,但與AspectJ不同的是,Spring AOP並不嘗試提供完整的AOP功能(即使它完全可以實現),Spring AOP 更注重的是與Spring IOC容器的結合,並結合該優勢來解決橫切業務的問題,因此在AOP的功能完善方面,相對來說AspectJ具有更大的優勢,同時,Spring注意到AspectJ在AOP的實現方式上依賴於特殊編譯器(ajc編譯器),因此Spring很機智迴避了這點,轉向採用動態代理技術的實現原理來構建Spring AOP的內部機制(動態織入),這是與AspectJ(靜態織入)最根本的區別。在AspectJ 1.5後,引入@Aspect形式的註解風格的開發,Spring也非常快地跟進了這種方式,因此Spring 2.0後便使用了與AspectJ一樣的註解。請注意,Spring 只是使用了與 AspectJ 5 一樣的註解,但仍然沒有使用 AspectJ 的編譯器,底層依是動態代理技術的實現,因此並不依賴於 AspectJ 的編譯器。所以,大家要明白,Spring AOP雖然是使用了那一套註解,其實實現AOP的底層是使用了動態代理(JDK或者CGLib)來動態植入。至於AspectJ的靜態植入,不是本文重點,所以只提一提。
2. Spring AOP – AspectJ註解
談到Spring AOP,老程式設計師應該比較清楚,之前的Spring AOP沒有使用@Aspect這一套註解和aop:config這一套XML解決方案,而是開發者自己定義一些類實現一些介面,而且配置賊噁心。本文就不再演示了,後來Spring痛下決心,把AspectJ”整合”進了Spring當中,並開啟了aop名稱空間。現在的Sping AOP可以算是朗朗乾坤。上例子之前,還是把AOP的概念都提一提:
- 切點:定位到具體方法的一個表示式
- 切面: 切點+建言
- 建言(增強):定位到方法後幹什麼事
網上有很多概念,什麼連線點,織入,目標物件,引入什麼的。我個人覺得,在Java的Spring AOP領域,完全不用管。別把自己繞暈了。就按照我說的這三個概念就完事了,好了,先來搞個例子:maven依賴:
<
dependency>
<
groupId>
org.springframework<
/groupId>
<
artifactId>
spring-core<
/artifactId>
<
version>
4.2.3.RELEASE<
/version>
<
/dependency>
<
dependency>
<
groupId>
org.springframework<
/groupId>
<
artifactId>
spring-context<
/artifactId>
<
version>
4.2.3.RELEASE<
/version>
<
/dependency>
<
dependency>
<
groupId>
org.springframework<
/groupId>
<
artifactId>
spring-aop<
/artifactId>
<
version>
4.2.3.RELEASE<
/version>
<
/dependency>
//下面這兩個aspectj的依賴是為了引入AspectJ的註解 <
dependency>
<
groupId>
org.aspectj<
/groupId>
<
artifactId>
aspectjrt<
/artifactId>
<
version>
1.6.12<
/version>
<
/dependency>
<
dependency>
<
groupId>
org.aspectj<
/groupId>
<
artifactId>
aspectjweaver<
/artifactId>
<
version>
1.6.12<
/version>
<
/dependency>
//Spring AOP底層會使用CGLib來做動態代理 <
dependency>
<
groupId>
cglib<
/groupId>
<
artifactId>
cglib<
/artifactId>
<
version>
2.2<
/version>
<
/dependency>
複製程式碼
小狗類,會說話:
public class Dog {
private String name;
public void say(){
System.out.println(name + "在汪汪叫!...");
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}複製程式碼
切面類:
@Aspect //宣告自己是一個切面類public class MyAspect {
/** * 前置通知 */ //@Before是增強中的方位 // @Before括號中的就是切入點了 //before()就是傳說的增強(建言):說白了,就是要幹啥事. @Before("execution(* com.zdy..*(..))") public void before(){
System.out.println("前置通知....");
}
}複製程式碼
這個類是重點,先用@Aspect宣告自己是切面類,然後before()為增強,@Before(方位)+切入點可以具體定位到具體某個類的某個方法的方位.Spring配置檔案:
//開啟AspectJ功能. <
aop:aspectj-autoproxy />
<
bean id="dog" class="com.zdy.Dog" />
<
!-- 定義aspect類 -->
<
bean name="myAspect" class="com.zdy.MyAspect"/>
複製程式碼
然後Main方法:
ApplicationContext ac =new ClassPathXmlApplicationContext("applicationContext.xml");
Dog dog =(Dog) ac.getBean("dog");
System.out.println(dog.getClass());
dog.say();
複製程式碼
輸出結果:
class com.zdy.Dog$$EnhancerBySpringCGLIB$$80a9ee5f前置通知....null在汪汪叫!...複製程式碼
說白了,就是把切面類丟到容器,開啟一個AdpectJ的功能,Spring AOP就會根據切面類中的(@Before+切入點)定位好具體的類的某個方法(我這裡定義的是com.zdy包下的所有類的所有方法),然後把增強before()切入進去.
然後說下Spring AOP支援的幾種類似於@Before的AspectJ註解:
- 前置通知@Before: 前置通知通過@Before註解進行標註,並可直接傳入切點表示式的值,該通知在目標函式執行前執行,注意JoinPoint,是Spring提供的靜態變數,通過joinPoint 引數,可以獲取目標物件的資訊,如類名稱,方法引數,方法名稱等,該引數是可選的。
@Before("execution(...)")public void before(JoinPoint joinPoint){
System.out.println("...");
}複製程式碼
- 後置通知@AfterReturning:通過@AfterReturning註解進行標註,該函式在目標函式執行完成後執行,並可以獲取到目標函式最終的返回值returnVal,當目標函式沒有返回值時,returnVal將返回null,必須通過returning = “returnVal”註明引數的名稱而且必須與通知函式的引數名稱相同。請注意,在任何通知中這些引數都是可選的,需要使用時直接填寫即可,不需要使用時,可以完成不用宣告出來。
@AfterReturning(value="execution(...)",returning = "returnVal")public void AfterReturning(JoinPoint joinPoint,Object returnVal){
System.out.println("我是後置通知...returnVal+"+returnVal);
}複製程式碼
- 異常通知 @AfterThrowing:該通知只有在異常時才會被觸發,並由throwing來宣告一個接收異常資訊的變數,同樣異常通知也用於Joinpoint引數,需要時加上即可.
@AfterThrowing(value="execution(....)",throwing = "e")public void afterThrowable(Throwable e){
System.out.println("出現異常:msg="+e.getMessage());
}複製程式碼
- 最終通知 @After:該通知有點類似於finally程式碼塊,只要應用了無論什麼情況下都會執行.
@After("execution(...)")public void after(JoinPoint joinPoint) {
System.out.println("最終通知....");
}複製程式碼
- 環繞通知 @Around: 環繞通知既可以在目標方法前執行也可在目標方法之後執行,更重要的是環繞通知可以控制目標方法是否指向執行,但即使如此,我們應該儘量以最簡單的方式滿足需求,在僅需在目標方法前執行時,應該採用前置通知而非環繞通知。案例程式碼如下第一個引數必須是ProceedingJoinPoint,通過該物件的proceed()方法來執行目標函式,proceed()的返回值就是環繞通知的返回值。同樣的,ProceedingJoinPoint物件也是可以獲取目標物件的資訊,如類名稱,方法引數,方法名稱等等
@Around("execution(...)")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("我是環繞通知前....");
//執行目標函式 Object obj= (Object) joinPoint.proceed();
System.out.println("我是環繞通知後....");
return obj;
}複製程式碼
然後說下一直用”…”忽略掉的切入點表示式,這個表示式可以不是exection(..),還有其他的一些,我就不說了,說最常用的execution:
//scope :方法作用域,如public,private,protect//returnt-type:方法返回值型別//fully-qualified-class-name:方法所在類的完全限定名稱//parameters 方法引數execution(<
scope>
<
return-type>
<
fully-qualified-class-name>
.*(parameters))複製程式碼
<
fully-qualified-class-name>
.*(parameters)複製程式碼
注意這一塊,如果沒有精確到class-name,而是到包名就停止了,要用兩個”..”來表示包下的任意類:
- execution(* com.zdy..*(..)):com.zdy包下所有類的所有方法.
- execution(* com.zdy.Dog.*(..)): Dog類下的所有方法.
具體詳細語法,大家如果有需求自行google了,我最常用的就是這倆了。要麼按照包來定位,要麼按照具體類來定位.
在使用切入點時,還可以抽出來一個@Pointcut來供使用:
/** * 使用Pointcut定義切點 */@Pointcut("execution(...)")private void myPointcut(){
}/** * 應用切入點函式 */@After(value="myPointcut()")public void afterDemo(){
System.out.println("最終通知....");
}複製程式碼
可以避免重複的execution在不同的註解裡寫很多遍…
結語
好了,Spring AOP 基於AspectJ註解如何實現AOP給大家分享完了,其實不是很難,因為博主比較懶,主說了主要內容,像切入點表示式要說其實有很多東西可以說,還有增強級別的排序問題,比如一個Aspect類中定義了多個@Before,誰先呼叫,因為博主覺得這些知識點不是很重要,用的非常少,所以就沒提了。這一期把基於註解的分享完了,下一次準備把Spring AOP基於XML的配置講一講,然後說一些實際的運用場景。包括多個切面命中一個切點時,切面級別的呼叫順序等都會提一提。然後把Spring AOP底層使用的JDK動態代理和CGLib動態代理都稍微說一說。好了,這一期就到這裡了..Over,Have a good day .