AOP,面向切面編程,做爲Spring的核心思想之一,度娘上有太多的教程啊、解釋啊,但博主仍是要本身按照本身的思路和理解再來闡釋一下。緣由很簡單,別人的思想終究是別人的,本身的理解纔是本身的,尤爲當用文字、代碼來闡述一遍事後,理解層面上又彷佛變得不同了。java
博主就不概念化解釋AOP了,這裏只簡單說下爲啥要使用這樣一種編程思想和相關的AOP技術。其實很簡單,就是爲了業務模塊間的解耦,尤爲在現代的軟件設計中強調高內聚、低耦合,要求咱們的業務模塊化,各個功能模塊只關注本身的邏輯實現,而不用關注與主業務邏輯不相關的功能。然而,在面向對象的系統設計中,系統中不可或缺的一些功能如日誌、事務是散佈在應用各處與主邏輯代碼高度耦合的,這讓主業務代碼變得至關冗餘、難以複用。而在面向切面的編程思想中,咱們是考慮將那些散佈在應用多處的重複性代碼抽離出來封裝成模塊化的功能類,一來讓主業務邏輯更加專一、簡單,二來模塊化的日誌、事務也便於複用和移植,這就是解耦的思想。可是,解耦並不等於斷耦,抽離的功能最終仍是要以某種方式"還"(qie)回去,不然應用的功能就不完善了。這裏,"還"(qie)回去的技術就是AOP技術,而這種解耦的編程思想就是AOP的編程思想。在Java的生態中,提供AOP技術的框架也有很多,主要的運用就是Spring的AOP和Spring"借鑑"幷包含進了本身的生態體系的 AspectJ的AOP。程序員
爲便於理解闡述,博主先嘮叨幾句。上面的基本闡述中,咱們知道,AOP要乾的事情其實也很簡單,就是要將對象編程中,抽離出來的模塊代碼(權限、日誌、事務)還(qie)回去,但確定不能是對象思惟中的代碼冗雜的組合,而是應該更加高明一些,最好能在原來的業務代碼執行的過程當中不知不覺的還(qie)回去——也就是說要在主業務邏輯執行的流程裏,動態的添加(權限、日誌、事務)代碼抽離前乾的那些事情。怎麼能作到呢?用代理啊,親!想一想,咱們對一個目標對象採用代理不就是爲了在目標對象邏輯執行時候經過在代理對象中乾點額外的事情嗎?這樣,雖然,原目標對象並無增長任何額外的功能,經過代理的一番暗中騷操做,展現給調用者的就好像目標對象有了代理對象中的那些額外的功能同樣。因而你也很好理解,爲何Spring的AOP中要用到動態代理了。好了,通過一番嘮叨,咱們再來看AOP的相關術語就要好理解得多——spring
如上描述,咱們把日誌、事務、權限等代碼重複性極高卻散佈在應用程序各個地方的功能稱爲橫切關注點。sql
被代理的目標對象在業務邏輯執行的過程當中,能夠被代理對象動態切入代理功能的一些時機節點,好比方法執行前、後,異常時,成功返回時等等。固然,這只是針對Spring來講的,由於Spring基於動態代理,只支持方法級別的AOP切入,實際上,AspectJ、JBoss等框架的AOP還能提供構造器以及更細粒度字段等的鏈接點支持。express
如上描述,就是代理對象在什麼時機要爲目標對象額外增長的功能代碼,於是不少教程資料上稱之爲 加強。請注意博主對通知的描述裏有提到什麼時機,這很好理解,你的代理對象要給目標對象增長額外功能,總得清楚要增長在哪些時機吧,因此,咱們的通知按照功能切入的時機分爲如下5個類型:編程
被代理對象目標方法執行過程當中真正的要執行通知代碼的一個或多個鏈接點,這會經過切點表達式語言進行匹配。安全
## 六、切面(Aspect)多線程
通知和切點的結合,切面完整的包含了代理對象對目標對象進行通知的三個基本要素:什麼時候(前、後、異常、環繞、返回等),何地(切點),幹什麼(通知切入的功能)。架構
## 七、織入(Weaving)
將切面應用到被代理對象並建立代理對象的的過程。切面會在指定的鏈接點(切點)被織入到被代理對象的執行方法中。其實,被代理對象的生命週期中有多個時機(編譯、類加載、運行)均可以進行織入,就 Spring 而言,是在被代理對象運行期進行代理對象的建立,織入切面邏輯的。併發
注:以上描述都是基於Spring 方法級別的AOP 來進行闡述
說了那麼多,仍是上代碼最簡單直接。準備工做:
① 測試依賴的包及其版本(注:不少教程中都提到須要 aopalliance包,可是博主測試過程當中並無確認此包存在的必要性)
aspectjweaver-1.9.2.jar commons-logging-1.2.jar spring-aop-4.3.18.RELEASE.jar spring-beans-4.3.18.RELEASE.jar spring-context-4.3.18.RELEASE.jar spring-core-4.3.18.RELEASE.jar spring-expression-4.3.18.RELEASE.jar spring-test-4.3.18.RELEASE.jar
② 定義兩個基礎模型類(以下),業務是:給只有打電話功能的手機動態的添加 拍照、玩遊戲這樣的非主業務功能。
//主業務功能 public class HuaWeiPhone { public void ring() { System.out.println("華爲手機,產銷第一"); } } //額外添加的功能 public class Photograph { public void takePictures(){ System.out.println("華爲手機,拍照牛批"); } public void playGames(){ System.out.println("華爲手機,遊戲玩得也這麼暢快"); } }
根據以上Java代碼,進行很是簡單的配置,就能看到動態的爲手機增長了拍照功能的效果了——
<bean class="main.java.model.HuaWeiPhone"/> <bean id="photograph" class="main.model.Photograph"/> <aop:config> <aop:pointcut id="ring" expression="execution(* main.model.HuaWeiPhone.ring(..))"/> <aop:aspect ref="photograph"> <aop:before method="takePictures" pointcut-ref="ring"/> <aop:after method="playGames" pointcut-ref="ring"/> </aop:aspect> </aop:config>
在Spring環境下測試類XML配置——
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml") public class SpringTest { @Autowired HuaWeiPhone huaWeiPhone; @Test public void testXml(){ huaWeiPhone.ring(); } }
輸出結果
須要先說明的是,Spring的基於註解的 AOP 其實是借鑑吸取了AspectJ的功能,因此你會看到不少相似 AspectJ 框架的註解。在以前的模型類上經過添加相應的註解改形成一個切面——
@Aspect //將該類標註爲一個AOP切面 @Component public class Photograph { @Pointcut("execution(* main.model.HuaWeiPhone.ring(..))") public void chenbenbuyi (){ } @Before("chenbenbuyi()") public void takePictures(){ System.out.println("華爲手機,拍照牛批"); } @After("chenbenbuyi()") public void playGames(){ System.out.println("華爲手機,遊戲玩得也這麼暢快"); } }
一樣的,目標類(HuaWeiPhone)上也要添加@Componet註解將其交給Spring 容器管理。而後,若是是純註解的話,還要一個配置類——
//配置註解掃描 @ComponentScan(basePackages = "main") //啓用AspectJ的自動代理功能 @EnableAspectJAutoProxy public class JavaConfig { }
最後,在Spring的環境下測試——
@RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml") @ContextConfiguration(classes = JavaConfig.class) public class SpringTest { @Autowired HuaWeiPhone huaWeiPhone; @Test public void testAnno(){ huaWeiPhone.ring(); } }
結果同上,這裏就不展現了。不過須要注意的是,無論什麼配置方式,基於Spring 的AOP編程實現的前提都是要將通知對象和被通知方法交給Spring IOC容器管理,也就是要聲明爲Spring 容器中的Bean。
在第三部分中,博主只是展現了最最簡單的AOP功能實現,還有稍微複雜的技能點沒有列出。好比,5種通知類型中的環繞通知呢?再好比,個人切面代碼若是要傳參數怎麼辦呢?接下來博主依次講解。
① 關於環繞通知的運用
基於 二 中的闡述,5 種通知類型中 環繞通知 是功能最爲強大,實際上,咱們能夠在環繞通知中個性化的定製出前置 、後置、異常和返回的通知類型,而若是單獨的採用前置、後置等通知類型,若是業務涉及多線程對成員變量的修改,可能出現併發問題,因此環繞要比單獨的使用另外的幾種通知類型更加的安全。咱們對上面的切面基於環繞通知進行修改,使之包含全部的通知類型的功能——
@Aspect @Component public class Photograph { @Pointcut("execution(* main.model.HuaWeiPhone.ring(..))") public void chenbenbuyi (){ } @Around("chenbenbuyi()") public void surround(ProceedingJoinPoint joinPoint){ try { System.out.println("目標方法執行前執行,我就是前置通知"); joinPoint.proceed(); // ① // int i =1/0; // ② 製造異常 System.out.println("正常返回,我就是返回通知"); } catch (Throwable e) { System.out.println("出異常了,我就是異常通知"); } finally { System.out.println("後置通知,我就是最終要執行的通知"); } } }
XML的配置和上面的其它通知類型同樣,只不過元素標籤爲 <aop:around />而已。上面的打印語句的位置就對應了其它幾種通知類型執行切面邏輯的時機。這裏注意,環繞通知方法體中須要有 ProceedingJoinPoint 接口做爲參數,在環繞通知中,經過執行該參數的 proceed() 方法來調用通知須要切入的目標方法。若是不執行 ① 處的調用,被通知方法實際上會被阻塞掉,因此你會看到,明明測試中執行了被通知的方法,實際卻沒有執行。該參數對象還能夠獲取方法簽名、代理對象、目標對象等信息,能夠本身測試着玩。
② 關於通知的傳參問題
切面雖然是通用邏輯,但實際在切入不一樣的目標方的時候,可能仍是但願通知方法根據被通知方法的不一樣(好比參數不一樣)而執行不同的邏輯,這就要求咱們的通知也能獲取到被通知方法傳入的參數。經過切點表達式,這也很容易辦到。首先咱們修改被通知的方法能夠傳參:
public void ring(String str) { System.out.println("華爲手機,產銷第一"); int i =1/0; }
而後切面中切點表達式和切面方法也作對應的修改——
@Aspect @Component public class Photograph { /** * Spring 藉助於 AspectJ的切點表達式語言中的arg()表達式執行參數的傳遞工做 */ @Pointcut("execution(* main.model.HuaWeiPhone.ring(String))&&args(name)") public void chenbenbuyi (String name){ } /** * ① 在引用空標方法的切點表達式時同時也就要傳入相應的參數 * ② 傳入的參數形參名字必須和切點表達式中的相同 */ @Before("chenbenbuyi(name)") public void takePictures(String name){ System.out.println("喂喂,你好我是 "+ name); } /** * 對於異常通知,有專門的異常參數能夠直接獲取到被通知方法出現異常後信息的 */ @AfterThrowing(pointcut = "chenbenbuyi(name)",throwing = "e") public void excep(String name,Throwable e){ System.out.println("出異常了,異常信息是:"+e.getMessage()); } }
XML中配置參數傳遞
<bean class="main.java.model.HuaWeiPhone"/> <bean id="photograph" class="main.java.model.Photograph"/> <aop:config> <aop:pointcut id="ring" expression="execution(* main.java.model.HuaWeiPhone.ring(..)) and args(name)"/> <aop:aspect ref="photograph"> <aop:before method="takePictures" pointcut-ref="ring" arg-names="name" /> <aop:after-throwing method="excep" throwing="e" arg-names="name,e" pointcut-ref="ring"/> </aop:aspect> </aop:config>
測試代碼——
@RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml") @ContextConfiguration(classes = JavaConfig.class) public class SpringTest { @Autowired HuaWeiPhone huaWeiPhone; @Test public void testAnno(){ huaWeiPhone.ring("Java高級架構獅"); } }
注意點:
分享免費學習資料
針對於Java程序員,我這邊準備免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)
爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個小贊和關注下我,之後還會更新技術乾貨,謝謝您的支持!
資料領取方式:加入Java技術交流羣963944895
,點擊加入羣聊,私信管理員便可免費領取