javascript
Spring Boot————AOP入门案例及切面优先级设置
看了這篇文章,如果你還是不會用AOP來寫程序,請你打我!! =.=|||?
引言
Spring AOP是一個對AOP原理的一種實現方式,另外還有其他的AOP實現如AspectJ等。
AOP意為面向切面編程,是通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術,是OOP面向對象編程的一種補足。它是軟件開發中的一個熱點技術,Spring AOP 也是Spring框架的核心特性之一(另一個核心特性是IOC)。
通過AOP技術,我們希望實現一種通用邏輯的解耦,解決一些系統層面上的問題,如日志、事務、權限等,從而提高應用的可重用性和可維護性,和開發效率。
Struts2的攔截器設計就是基于AOP的思想,是非常經典的理論實踐案例。
重要概念
AOP中包括 5 大核心概念:切面(Aspect)、連接點(JoinPoint)、通知(Advice)、切入點(Pointcut)、AOP代理(Proxy)。(記憶口訣:通知 代理?廚師兩點(連接點、切入點)切面包。)
關于前面四點,將會直接涉及到相關編碼的實現方式,因此將會結合代碼進行解釋,在這里簡單闡述一下AOP代理。
AOP代理,是AOP框架如Spring AOP創建的對象,代理就是對目標對象進行增強,Spring AOP中的代理默認使用JDK動態代理,同時支持CGLIB代理,前者基于接口,后者基于子類。在Spring AOP中,其功能依然離不開IOC容器,代理的生成、管理以及其依賴關系都是由IOC容器負責,而根據目前的開發提倡“面向接口編程”,因此大多使用JDK動態代理。
五大通知類型
1、前置通知 [ Before advice ] :在連接點前面執行,前置通知不會影響連接點的執行,除非此處拋出異常;
2、正常返回通知 [ After returning advice ] :在連接點正常執行完成后執行,如果連接點拋出異常,則不會執行;
3、異常返回通知 [ After throwing advice ] :在連接點拋出異常后執行;
4、返回通知 [ After (finally) advice ] :在連接點執行完成后執行,不管正常執行完成,還是拋出異常,都會執行返回通知中的內容;
5、環繞通知 [ Around advice ] :環繞通知圍繞在連接點前后,比如一個方法調用的前后。這種通知是最強大的通知,能在方法調用前后自定義一些操作。
應用案例分析
在OOP中的基本單元是類,而在AOP中的基本單元是Aspect,它實際上也是一個類,只不過這個類用于管理一些具體的通知方法和切入點。
所謂的連接點,實際上就是一個具體的業務方法,比如Controller中的一個請求方法,而切入點則是帶有通知的連接點,在程序中主要體現為書寫切入點表達式,這個表達式將會定義一個連接點。
就以Controller中的一個請求方法為例,通過AOP的方式實現一定的業務邏輯。
這個邏輯是:GET請求某一方法,然后通過一個Aspect來實現在這個方法調用前和調用后做一些日志輸出處理。
引入依賴jar包
基于spring boot 的maven依賴如下,如果是僅使用spring框架的話,請參考其他資料:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>定義業務方法(連接點)
這個方法就是后面AOP切面中的那個連接點,方法非常簡單,僅僅接收一個姓名和性別,并輸出 “某某做作業......” :
@RestController public class DoHomeWorkController {@GetMapping("/dohomework")public void doHomeWork(String name, Gender gender) {System.out.println(name + "做作業... ...");} }定義切面類、定義切入點及通知方法
下面的代碼中,@Aspect、@Pointcut、@Component都是必須的(@Component用于將這個切面類注入到 IOC容器中,如果不用@Component就用@Bean的方式也是可以的,但總之切面類必須被注入到 IOC容器中,這也就是前面說的Spring AOP不能脫離IOC容器的體現)。而@Before用來定義一個前面提到過的五大通知類型中的 Before advice類型的通知方法,這個根據具體的需要可以進行選擇。
@Pointcut注解的參數是一個表達式,可以當做是一個固定的寫法,“ * ” 表示任意返回值,“ .. ” 也是一種通配。當然,方法的全名可以使用編輯器的復制功能,具體關于execution表達式的說明,在此不做展開討論。
@Aspect @Component public class DoHomeWorkAspect {/** 定義切入點 */@Pointcut("execution(* com.example.demo.controller.DoHomeWorkController.doHomeWork(..))")public void homeWorkPointcut() {}/** 定義Before advice通知類型處理方法 */@Before("homeWorkPointcut()")public void beforeHomeWork() {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();System.out.println(request.getParameter("name") + "想先吃個冰淇淋......");} }再簡單說一下通過RequestContextHolder這個最終獲取request的操作,就當是一個固定寫法,可以從請求上下文中拿到當前的請求對象,并從請求中獲得一些信息,更詳細的API用法不做展開。
執行結果
啟動項目,瀏覽器地址欄輸入:
控制臺顯示如下:
可以從輸出結果中看到,在執行doHomeWork(String name, Gender gender) 方法之前先執行了切面類中定義的beforeHomeWork()方法,成功的完成了在切入點之前執行一個操作的需要。這就是Spring AOP的典型應用。
環繞通知實現
在上一節“應用案例分析”中介紹了Before advice的使用方式,而Spring AOP的通知類型有五種,在Spring 框架里分別有對應的注解來代表每一種通知類型,它們分別是:
@Before 對應——>前置通知 [ Before advice ]
@AfterReturning 對應——>正常返回通知 [ After returning advice ]
@AfterThrowing 對應——>異常返回通知 [ After throwing advice ]
@After 對應——>返回通知 [ After (finally) advice ]
@Around 對應——>環繞通知 [ Around advice ]
其中,前四種通知類型,與@Before的使用完全相同,根據各自不同的使用定義自行選擇。
需要說明的是@Around的使用。在定義環繞通知方法的時候,需要傳入一個org.aspectj.lang.ProceedingJoinPoint 對象:
@Around("homeWorkPointcut()")public void around(ProceedingJoinPoint joinPoint) {System.out.println("環繞通知,方法執行前");try {joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("環繞通知,方法執行后");}執行結果如下:
根據輸出結果,我們注意到了一個問題,即@Around先于@Before通知執行。這就引出了一個非常重要的問題,即各類型通知執行的先后順序。
各類型通知執行先后順序
在實際開發中,有時候我們會針對同一個切入點進行多種Aspect包裝,比如,可以有一個Aspect管理對一個方法進行日志打印的通知,而另一個Aspect管理對這個方法的一些校驗工作。因此,涉及到兩類問題:
1、同一個切入點不同通知的執行順序
2、同一個切入點不同切面的執行順序
我們在前面的“環繞通知實現”結果中看到,@Around是先于@Before執行的,這就是其中一個問題的引出,即同一個切入點不同通知的執行順序。來看下面這張圖:
?可以看到Aspect1 和Aspect2兩個切面類中所有通知類型的執行順序,Method是具體的切入點,order代表優先級,它根據一個int值來判斷優先級的高低,數字越小,優先級越高!所以,不同的切面,實際上是環繞于切入點的同心圓:
?@Order注解改變優先級
@order注解可以使用在類或方法上,但是,直接作用于方法上是無法奏效的,目前的使用方法都是通過標記在切面類上,來實現兩個切面的優先級。
@Order注解接收一個int類型的參數,這個參數可以是任意整型數值,數值小的,優先級高。
對于使用@Order來改變通知方法執行的優先級,親測無法生效。也就是說就算你使用@Order注解,讓@Before的優先級高于@Around也依然不會得到想要的結果,而且,如果在一個Aspect類中有兩個@Before,并使用@Order來分配這兩個@Before的優先級依然不會生效。
因此,在實際開發的過程中,應該避免在一個Aspect類中有多個相同的通知類型,否則,就算使用@Order來區分優先級,可能最后的效果也不符預期。
那么,關于@Order注解實現優先級的方式,我個人總結了以下幾條經驗:
1、在一個Aspect類中不要有多個同種類型的通知,如多個@Before、多個@After;
2、不要在通知方法上使用@Order來區分優先級,要遵循默認的通知方法優先級(同心圓模型);
3、如果避免不了有相同類型的通知,要區分在不同的Aspect類中,并且通過@Order(1)、@Order(2)、@Order(3)... 來區分Aspect類的優先級,即以切面類作為優先級的區分單元,而不是通知方法;
4、在編寫多個通知方法時,應當把實際業務需要與默認通知優先級(同心圓模型)結合編碼。
?綜上,就是關于AOP的實踐與總結,總的來說,還是收獲頗豐的,其中針對于@Order的解釋,是個人的分析和經驗總結,因為把@Order用在通知方法上真的不好使,而且也并未找到比較好的解決辦法,所以還是應該通過巧妙的方式避開這個坑。如有任何疑問,歡迎各位看官文末留言。:)
鳴謝
《Spring AOP APIs》
《AOP-百度百科》
《淺談spring aop的五種通知類型》
《Spring AOP詳細介紹》
《Spring AOP之坑:完全搞清楚advice的執行順序》
總結
以上是生活随笔為你收集整理的Spring Boot————AOP入门案例及切面优先级设置的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript————FormDa
- 下一篇: 一篇搞懂HTTP协议