經過一個多月的 Spring AOP 的學習,掌握了 Spring AOP 的基本概念。AOP 是面向切面的編程(Aspect-Oriented Programming),是基於 OOP(面向對象的編程,Object-Oriented Programming)開發的一套程序架構。
微服務架構體系下,有時候要求一段通用性的事務使用在軟件中的許多模塊,好比日誌模塊,外部業務邏輯等。以日誌模塊舉例,根據 OOP 的思想,我能夠創建一個日誌類,而後在每個須要記錄日誌的類中初始化日誌類,達到日誌記錄的目的。缺點是,你必須在全部即將使用的業務代碼中,實例化日誌類,而後執行日誌類的方法。若是採用 AOP 的思想,能夠將日誌類做爲切面,而後使用代理的方式將這個日誌類切入到須要記錄日誌的類中。日誌類方法的執行只須要寫一次,代碼更容易維護。
在實踐的過程當中,使用 Spring AOP 的思想,爲 PressSystem 項目加入了日誌記錄模塊。日誌採用 MySQL 數據庫表進行記錄,數據庫鏈接採用 MyBatis。html
下面給出 AOP 中本身理解後的基本概念。若是想看原文,能夠點擊文末的 Spring AOP 官方文檔。java
JoinPoint(鏈接點):能夠理解爲一個方法,就是切面要切入的那個方法。spring
Advice(通知):通知就是切面中的方法,這個方法將從JoinPoint切入。數據庫
Pointcut(切點):切點就是表達式,通知將會切入符合切點表達式的JoinPoint 中。編程
Introduction(引入):引入機制能夠向一個能被切入的對象發明新的接口,並實現它。其實就是把能被切入的對象強制轉換成另外一個類,這樣就能夠執行另外一個類的方法了。架構
Target Object(目標對象):就是能被切入的對象。Spring AOP 中,目標對象永遠是可被代理的對象。框架
AOP Proxy(AOP 代理):AOP Proxy 就是 經過 AOP 框架生成出來的一個對象,這個對象實現了切面的功能,是目標類被切入之後的結果。Spring AOP 代理分兩種:ssh
Weaving(織入):織入就是把切面與目標類鏈接的過程。過程的產物就是一個被切入的目標。異步
簡單地來講,通知能夠有前置通知、後置通知、環繞通知等等。好比,前置通知就是通知在切入點執行以前執行;後置通知就是在切入點執行以後執行。在個人 Spring AOP 示例程序中,列舉了如下幾個通知的實現方式。jsp
Before advice(前置通知):在鏈接點以前執行。除非是前置通知拋出了異常,不然前置通知沒有能力影響執行流流向鏈接點。
After returning advice(返回後通知):在鏈接點的方法正常執行完以後再執行的通知。所謂的正常執行完,好比說鏈接點方法沒有拋出異常。
After throwing advice(拋出後通知):若是方法由於拋出了異常而終止,那麼就執行拋出後通知。
After (finally) advice(後置通知):無論鏈接點方法的退出狀況如何,是正常仍是有異常,後置通知都會執行。
Around advice(環繞通知):環繞通知是包圍住鏈接點的通知,能夠在鏈接點的前面或後面執行自定義的方法。環繞通知能夠決定是否執行鏈接點的方法,或者繞過鏈接點的方法。繞過的方式是返回環繞通知本身的值或者拋出一個異常。
如下提到的組件,是實現 Spring AOP 的組件最小子集。
XuanTiController.java
在 PressSystem 項目中,用戶操做的是 jsp 頁面,jsp 頁面會發送一個異步請求給 XuanTiController,在 XuanTiController 中,會調用切入點的方法。因此能夠把 XuanTiController 看做入口。
XuanTiService.java
採用接口與實現分離的設計原則,XuanTiService 就是接口,它的實現是 XuanTiServiceImpl,XuanTiServiceImpl 就是切入點。
XuanTiServiceImpl.java
XuanTiServiceImpl 就是切入點,其中的函數被通知切入了。
LogAspect.java
LogAspect 就是切面,其中有一些通知,這些通知將會切入到切入點中。
spring-common.xml
這是切入點和切面的配置文件。
總的來講,是 LogAspect 這個 bean 切入了 XuanTiServiceImpl 這個 bean
在 spring-common.xml 配置文件中,聲明瞭 XuanTiServiceImpl 和 LogAspect 2個 bean<bean class="com.tgb.service.impl.XuanTiServiceImpl" />
<bean id="logAspect" class="com.tgb.utils.LogAspect" />
在 LogAspect 中,有不少配置,看以下的3行。這些配置指定了
saveLogInsert
updateLogInsert
deleteLogInsert
這三個通知要切入 XuanTiServiceImpl@AfterReturning(pointcut="execution(* com.tgb.service.impl.*.save(..))", argNames="returnValue", returning="returnValue")
@AfterReturning(pointcut="execution(* com.tgb.service.impl.*.update(..))", argNames="returnValue", returning="returnValue")
@Around("execution(* com.tgb.service.impl.*.delete(..)) && args(id, table_name)")
注意,每一個通知,有不一樣類型的通知的 @AspectJ 方式的標記,例如 @AfterReturning, @Around
另外,每一個通知,能夠有 pointcut 配置,即每一個通知要切入到哪裏
AOP 的實現方式是經過代理實現的。因此在 spring-common.xml 中指定了 autoproxy,爲 XuanTiServiceImpl 建立代理,以下所示:<aop:aspectj-autoproxy />
註釋:若是Spring決定一個bean要被切入,那麼Spring就會爲這個 bean 自動生成代理來插入外來的方法,保證通知被執行
在 XuanTiServiceImpl 中有以下3個方法,這3個方法符合 pointcut 的描述,所以具體地來講,這3個方法就是切入點,它們被順利地切入了public void save(XuanTi xuanTi) {...}
public boolean update(XuanTi xuanTi) {...}
public boolean delete(String id, String table_name) {...}
如下描述,主要涉及2點,能夠歸納爲 @Aspect 和 @Autowired
@Aspect 就是「切入機制」,@AfterReturning, @Around, pointcut,<aop:aspectj-autoproxy />
等知識點都屬於這個知識體系
@Autowired 是「自動綁定機制」,@Component 註解,廣義的 bean 等知識點屬於 @Autowired 的基礎知識體系另外,下面所提到的調用過程,是 Spring AOP 相關的過程最小子集
XuanTiController 被調用了,具體來講,其中的 xuanTiService.save(xuanTi);
被調用了
由於在 XuanTiController 有標註爲 @Autowired 的 xuanTiService,因此 Application 會去查找 xuanTiService 這個 bean
至於爲何會去查找 xuanTiService 這個 bean 呢,是由於 spring-common.xml 中的配置<context:annotation-config />
註釋:啓用註解配置方式,好比說啓用了 @Autowired 的識別。
XuanTiService 只是一個 interface,實現它的是 XuanTiServiceImpl
XuanTiServiceImpl 這個 bean 找到了,由於在 spring-common.xml 中有配置<bean class="com.tgb.service.impl.XuanTiServiceImpl" />
xuanTiService.save(xuanTi); 被執行了,具體來講,是 XuanTiServiceImpl 中的 public void save(XuanTi xuanTi) {…} 被執行了
根據上一章節「切入機制」的描述,在執行 public void save(XuanTi xuanTi) {...}
的時候,會伴隨着執行 LogAspect 中的 saveLogInsert 方法
在 LogAspect 中,有標註爲 @Autowired 的 logService,因此 Application 會去查找 logService 這個 bean
查找的緣由,就是第3點所描述的
LogService 只是一個 interface,實現它的是 LogServiceImpl
XuanTiServiceImpl 這個 bean 找到了,由於在 spring-common.xml 中有配置<bean id="LogService" class="com.tgb.service.impl.LogServiceImpl" />
註釋:日誌記錄業務邏輯對象
logService.log(log); 被執行了,具體來講,是 LogServiceImpl 中的 public void log(Log log) {…} 被執行了
在本章節,我將會介紹另一些 Spring AOP Docs 中提到的基礎知識。這些基礎知識沒有使用在 PressSystem 項目中,可是做爲 Spring AOP Docs 中的基礎知識,應當有所瞭解。
在前面的章節中,提到過五種類型的通知。這五種類型的通知,在個人 aop_demo 示例程序中都有使用。注意,聲明通知的方法有如下兩種方式。在個人示例程序中,都有展現:
聲明一個通知的時候,是能夠傳入切入點的參數,在通知中使用這個參數的,例如上文中提到的 delete 通知:
@Around("execution(* com.tgb.service.impl.*.delete(..)) && args(id, table_name)")
當我傳入了 (id, table_name)
參數後,能夠在通知中使用 id 和 table_name。delete 通知的目的是,當 delete 方法執行的時候,寫下日誌。在日誌中我要知道刪除的 id 和 table_name。傳入了這兩個參數以後,就能夠記錄了。
通知的參數能夠很複雜,用於知足實際須要。想要了解更多通知參數的使用方法,能夠查看 Spring AOP Docs。
Pointcut
是 Advice
的具體配置,指定了一個 Advice 將會切入到哪些切入點中,這是由切點表達式決定的。Pointcut 是一種十分重要的機制,在 PressSystem 的 LogAspect 中有簡單的應用。想要知道更多使用方法,能夠參考 Spring AOP Docs。
如下是 Spring AOP Docs 中的原版描述:
Introductions (known as inter-type declarations in AspectJ) enable an aspect to declare that advised objects implement a given interface, and to provide an implementation of that interface on behalf of those objects.
相關的代碼,能夠參看 com.spring.demo08 和 com.spring demo14。其中:
根據個人理解,Introduction 的意義就是接口類型強轉,可是看到一句註釋說,這是「接口動態實現」,以爲裏面還有其餘的花頭我沒有理解。。。
Spring 的動態代理有兩種:一是 JDK 的動態代理;另外一個是 cglib 動態代理(經過修改字節碼來實現代理)。能夠以 JDK 動態代理的方式爲例,來粗淺地探知 AOP Proxy 的究竟。
JDK的代理方式主要就是經過反射跟動態編譯來實現的,主要涉及到java.lang.reflect包中的兩個類:Proxy
和 InvocationHandler
。其中 InvocationHandler
是一個接口,能夠經過實現該接口定義橫切邏輯,在並經過反射機制調用目標類的代碼,動態將橫切邏輯和業務邏輯編織在一塊兒。 將會在另外一篇文章中,重點來談一下 Spring AOP 的底層實現技術:JDK 動態代理。
這篇博客寫了好久,一方面是因爲差很少是利用業餘時間在學習,另外一方面是因爲 Spring AOP 的知識點有不少。
在面向切面的編程的概念中,比較重要的是切面類和目標類。切面類中有切面方法,目標類中有切入點。經過正確的配置,可使切面方法切入到目標類的切面點中。
這篇博客首先介紹了 AOP 的概念,而後介紹了通知的5種類型。接下來,以 PressSystem 爲例,講述了 PressSystem 中的 AOP 的基本組件,切入機制和調用過程。
最後,在知識拓展中,十分簡單地提到了五種類型的通知的實現方式,通知參數的使用方法,Pointcut - 切點表達式的使用,Introductions 簡單介紹。最重要的一點,我認爲是 AOP Proxy 原理簡介。有關 AOP Proxy 原理簡介,請參看另外一篇博客:Spring AOP 中的 JDK 動態代理。
創做時間:2016-05-14 星期六 6:32:24 PM