javascript
[Spring入门学习笔记][Spring的AOP原理]
AOP是什么?
面向切面編程
軟件工程有一個(gè)基本原則叫做“關(guān)注點(diǎn)分離”(Concern Separation),通俗的理解就是不同的問題交給不同的部分去解決,每部分專注于解決自己的問題。這年頭互聯(lián)網(wǎng)也天天強(qiáng)調(diào)要專注嘛!
這其實(shí)也是一種“分治”或者“分類”的思想,人解決復(fù)雜問題的能力是有限的,所以為了控制復(fù)雜性,我們解決問題時(shí)通常都要對(duì)問題進(jìn)行拆解,拆解的同時(shí)建立各部分之間的關(guān)系,各個(gè)擊破之后整個(gè)問題也迎刃而解了。人類的思考,復(fù)雜系統(tǒng)的設(shè)計(jì),計(jì)算機(jī)的算法,都能印證這一思想。額,扯遠(yuǎn)了,這跟AOP有神馬關(guān)系?
面向切面編程(Aspect Oriented Programming,AOP)其實(shí)就是一種關(guān)注點(diǎn)分離的技術(shù),在軟件工程領(lǐng)域一度是非常火的研究領(lǐng)域。我們軟件開發(fā)時(shí)經(jīng)常提一個(gè)詞叫做“業(yè)務(wù)邏輯”或者“業(yè)務(wù)功能”,我們的代碼主要就是實(shí)現(xiàn)某種特定的業(yè)務(wù)邏輯。但是我們往往不能專注于業(yè)務(wù)邏輯,比如我們寫業(yè)務(wù)邏輯代碼的同時(shí),還要寫事務(wù)管理、緩存、日志等等通用化的功能,而且每個(gè)業(yè)務(wù)功能都要和這些業(yè)務(wù)功能混在一起,痛苦!所以,為了將業(yè)務(wù)功能的關(guān)注點(diǎn)和通用化功能的關(guān)注點(diǎn)分離開來,就出現(xiàn)了AOP技術(shù)。這些通用化功能的代碼實(shí)現(xiàn),對(duì)應(yīng)的就是我們說的切面(Aspect)。
業(yè)務(wù)功能代碼和切面代碼分開之后,責(zé)任明確,開發(fā)者就能各自專注解決問題了,代碼可以優(yōu)雅的組織了,設(shè)計(jì)更加高內(nèi)聚低耦合了(終極目標(biāo)啊!)。但是請(qǐng)注意,代碼分開的同時(shí),我們?nèi)绾伪WC功能的完整性呢? 你的業(yè)務(wù)功能依然需要有事務(wù)和日志等特性,即切面最終需要合并(專業(yè)術(shù)語叫做織入, Weave)到業(yè)務(wù)功能中。怎么做到呢? 這里就涉及AOP的底層技術(shù)啦,有三種方式:
- 編譯時(shí)織入:在代碼編譯時(shí),把切面代碼融合進(jìn)來,生成完整功能的Java字節(jié)碼,這就需要特殊的Java編譯器了,AspectJ屬于這一類
- 類加載時(shí)織入:在Java字節(jié)碼加載時(shí),把切面的字節(jié)碼融合進(jìn)來,這就需要特殊的類加載器,AspectJ和AspectWerkz實(shí)現(xiàn)了類加載時(shí)織入
- 運(yùn)行時(shí)織入:在運(yùn)行時(shí),通過動(dòng)態(tài)代理的方式,調(diào)用切面代碼增強(qiáng)業(yè)務(wù)功能,Spring采用的正是這種方式。動(dòng)態(tài)代理會(huì)有性能上的開銷,但是好處就是不需要神馬特殊的編譯器和類加載器啦,按照寫普通Java程序的方式來就行了!
一個(gè)場景
接下來上例子!David對(duì)土豪老板定機(jī)票的例子比較滿意,所以決定繼續(xù)沿用這個(gè)例子。
Boss在訂機(jī)票時(shí),我們希望能夠記錄訂機(jī)票這個(gè)操作所消耗的時(shí)間,同時(shí)記錄日志(這里我們簡單的在控制臺(tái)打印預(yù)定成功的信息)。
我們來看普通青年的做法吧:
package com.tianmaying.aopdemo;public class Boss {private BookingService bookingService;public Boss() {this.bookingService = new QunarBookingService();}//...public void goSomewhere() {long start = System.currentTimeMillis();//訂機(jī)票boolean status = bookingService.bookFlight();//查看耗時(shí)long duration = System.http://tianmaying.com/tutorial/spring-ioc#/1() - start;System.out.println(String.format("time for booking flight is %d seconds", duration));//記錄日志if (status) {System.out.println("booking flight succeeded!");} else {System.out.println("booking flight failed!");}} }我們看到,在訂機(jī)票的同時(shí),還要處理查看耗時(shí)和記錄日志,關(guān)注的事情太多了,頭大啊。而且項(xiàng)目大了之后,除了訂機(jī)票之外,很多業(yè)務(wù)功能都要寫類似的代碼。讓AOP來拯救我們吧!
使用AOP的場景
相比在IoC例子中的代碼,我們讓BookingService的bookFlight()方法返回一個(gè)boolean值,表示是否預(yù)定成功。這樣我們可以演示如何獲取被切方法的返回值。
通過AOP我們?cè)趺醋瞿?#xff0c;David今天送出獨(dú)門秘籍,告訴你通過3W方法(What-Where-When)來理解AOP。
- What:What當(dāng)然指的時(shí)切面啦!首先我們將記錄消耗時(shí)間和記錄日志這兩個(gè)功能的代碼分離出來,我們可以做成兩個(gè)切面,命名為TimeRecordingAspect和LogAspect。
- Where:切面的織入發(fā)生在哪呢?切面針對(duì)的目標(biāo)對(duì)象(Target)是SmartBoss(區(qū)別于Boss)!這里還有有一個(gè)很關(guān)鍵的概念叫做切入點(diǎn)(Pointcut),在這個(gè)場景中就是指在SmartBoss調(diào)用什么方法的時(shí)候的時(shí)候應(yīng)用切面。顯然,我們希望增強(qiáng)的是bookFlight()方法,即在bookFlight方法調(diào)用的地方,我們加入時(shí)間記錄和日志。
- When: 什么時(shí)候織入呢?這涉及到織入的時(shí)機(jī)問題,我們可以在bookFlight()執(zhí)行前織入,執(zhí)行后織入,或者執(zhí)行前后同時(shí)切入。When的概念用專業(yè)術(shù)語來說叫做通知(Advice)
了解了3W之后,來看看代碼吧,先上LogAspect:
插入一段,POM文件不要忘記了引入Spring AOP相關(guān)的依賴:
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.2.0.RELEASE</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>4.2.0.RELEASE</version> </dependency> <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.5</version> </dependency> <dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.5</version> </dependency>LogAspect.java
package com.tianmaying.aopdemo.aspect;import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect //1 @Component public class LogAspect {@Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))") //2private void logPointCut() {}@AfterReturning(pointcut = "logPointCut()", returning = "retVal") //3public void logBookingStatus(boolean retVal) { //4if (retVal) {System.out.println("booking flight succeeded!");} else {System.out.println("booking flight failed!");}} }- 1 通過一個(gè) @Apsect 標(biāo)注,表示 LogAspect 是一個(gè)切面,解決了What問題。
- 2 通過定義一個(gè)標(biāo)注了@Pointcut 的方法,定義了Where的問題,"execution(* com.tianmaying.aopdemo..*.bookFlight(..))"表示在com.tianmaying.aopdemo包或者子包種調(diào)用名稱為bookFlight的地方就是切入點(diǎn)!定義Pioncut的語法這里不詳解了,David這里要告訴你的時(shí)它的作用:解決Where的問題!
- 3 通過一個(gè)@AfterReturning標(biāo)注表示在bookFlight()調(diào)用之后將切面織入,這是一個(gè)AfterReturning類型的Advice,注意這里可以通過returning屬性獲取bookFlight()的返回值。
- 4 這里定義了實(shí)現(xiàn)切面功能的代碼,經(jīng)過這么一番閃轉(zhuǎn)騰挪,最后寫日志的代碼跑到這里來了!
再來看TimeRecordingAspect:
package com.tianmaying.aopdemo.aspect;import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class TimeRecordingAspect {@Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))")private void timeRecordingPointCut() {}@Around("timeRecordingPointCut()") //1public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { //2long start = System.currentTimeMillis();Object retVal = pjp.proceed(); // 3long duration = System.currentTimeMillis() - start;System.out.println(String.format("time for booking flight is %d seconds", duration));return retVal;} }- 1與LogAspect不同,因?yàn)橐?jì)算bookFlight()的耗時(shí),我們必須在調(diào)用前后到切入代碼,才能算出來這之間的時(shí)間差。因此,在1處,我們定義的是一個(gè)Around類型的Advice。
- 2處是實(shí)現(xiàn)Around Advice的方法,其方法的參數(shù)和返回值是固定寫法。
- 3處也是固定寫法,表示對(duì)目標(biāo)方法(即bookFlight())的調(diào)用,注意不要漏了,漏掉的話原方法就不會(huì)被調(diào)用了,通常情況下肯定不是你想要的結(jié)果!
回頭再看SmartBoss的代碼,比Boss簡單多了,goSomewhere()方法中只剩下一條語句,酷的掉渣啊,只關(guān)注訂機(jī)票,其他的事情都F**k Off吧!
package com.tianmaying.aopdemo;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class SmartBoss {private BookingService bookingService;//...public void goSomewhere() {bookingService.bookFlight();} }當(dāng)然,要讓代碼Run起來,還需要在App類中加上@EnableAspectJAutoProxy標(biāo)注,這樣Spring啟動(dòng)時(shí)就去去掃描AOP相關(guān)的標(biāo)注,在創(chuàng)建對(duì)象時(shí)幫我們?nèi)?zhí)行織入過程!
回到定義
- 切面(Aspect):指的就是通用功能的代碼實(shí)現(xiàn),比如我們上面演示的時(shí)間記錄切面,日志切面,它們都是普通的Java類:TimeRecordingAspect和LogAspect。
- 目標(biāo)對(duì)象(Target):要被織入切面的對(duì)象,例子中的CtripBookingService,有了AOP,它們可以專注于核心業(yè)務(wù)邏輯代碼了!
通知(Advice):切面是一個(gè)類,而通知就是類里的方法以及這個(gè)方法如何織入到目標(biāo)方法的方式(用@AfterReturning和@Around標(biāo)注的方法)。我們的例子中只展示了兩類通知,根據(jù)織入到目標(biāo)方法方式的不同,一共可以分為5種:
- 前置通知(Before)
- 后置通知(AfterReturning)
- 異常通知(AfterThrowing)
- 最終通知(After)
- 環(huán)繞通知(Around)
織入(Weaving):AOP實(shí)現(xiàn)的過程,即將切面應(yīng)用到目標(biāo)對(duì)象,從而創(chuàng)建一個(gè)新的代理對(duì)象的過程,對(duì)于Spring來說,就是初始化Context中的對(duì)象時(shí),完成織入操作。
總結(jié)
- 關(guān)注點(diǎn)分離的思想
- 理解AOP的3W方法
- 切面、目標(biāo)對(duì)象、切入點(diǎn)、通知和織入的概念
總結(jié)
以上是生活随笔為你收集整理的[Spring入门学习笔记][Spring的AOP原理]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 来讲讲新生代剪辑工具,人工智能视频剪辑软
- 下一篇: 快速入门Spring之SpringAOP