AOP概述及实现原理
AOP
引言
Spring 為解耦而生,其中AOP(面向切面編程)是很濃重的一筆。AOP(Aspect-Oriented-Programming)指一種程序設(shè)計(jì)范型,該范型以一種稱(chēng)為切面(aspect)的語(yǔ)言構(gòu)造為基礎(chǔ),切面是一種新的模塊化機(jī)制,用來(lái)描述分散在對(duì)象、類(lèi)或方法中的橫切關(guān)注點(diǎn)(crosscutting)。
AOP相關(guān)概念
- 連接點(diǎn)(JoinPoint):程序執(zhí)行到某個(gè)特定位置(如:某個(gè)方法調(diào)用前,調(diào)用后,方法拋出異常),一個(gè)類(lèi)或一段程序代碼擁有一些具有邊界性質(zhì)的特定點(diǎn),這些代碼中的特定點(diǎn)就是連接點(diǎn)。
- 切點(diǎn)(PointCut):如果連接點(diǎn)相當(dāng)于數(shù)據(jù)中的記錄,那么切點(diǎn)就相當(dāng)于查詢條件,一個(gè)切點(diǎn)可以匹配多個(gè)連接點(diǎn)。SpringAop的規(guī)則解析引擎負(fù)責(zé)解析切點(diǎn)所設(shè)定的查詢條件,找到對(duì)應(yīng)的連接點(diǎn)。
- 增強(qiáng)(Advice):增強(qiáng)是織入到目標(biāo)類(lèi)連接點(diǎn)上的一段程序代碼,Spring提供的增強(qiáng)接口都是帶方位的,如BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。,很多資料上將增強(qiáng)譯為“通知”,這明顯是詞不達(dá)意的。
說(shuō)明:Advice在國(guó)內(nèi)很多的書(shū)面資料上都被翻譯為“通知”,但這個(gè)翻譯無(wú)法表達(dá)其本質(zhì),有少量的讀物將這個(gè)詞翻譯為“增強(qiáng)”,這個(gè)翻譯是比較為準(zhǔn)確的詮釋,我們通過(guò)AOP將橫切關(guān)注功能加到原有的業(yè)務(wù)邏輯上,這就是對(duì)原有的業(yè)務(wù)邏輯進(jìn)行的一種增強(qiáng),這個(gè)增強(qiáng)可以是前置增強(qiáng),后置增強(qiáng),返回后增強(qiáng),拋出異常時(shí)增強(qiáng),和包圍型增強(qiáng)。
- 引介(Introduction):引介是一種特殊的增強(qiáng),他為類(lèi)添加一些屬性和方法,這樣,即使一個(gè)業(yè)務(wù)類(lèi)原本沒(méi)有實(shí)現(xiàn)某個(gè)接口,通過(guò)引介功能,可以動(dòng)態(tài)的為該業(yè)務(wù)類(lèi)添加接口的實(shí)現(xiàn)邏輯,讓業(yè)務(wù)類(lèi)稱(chēng)為這個(gè)接口的實(shí)現(xiàn)類(lèi)
- 織入(Weaving):織入是將增強(qiáng)添加到目標(biāo)類(lèi)具體連接點(diǎn)上的過(guò)程,AOP由三種織入方式:1 編譯期織入:需要特殊的Java編譯期(例如Aspect的ajc);2 裝載期織入:要求使用特殊的類(lèi)加載器,在裝載類(lèi)的時(shí)候?qū)︻?lèi)進(jìn)行增強(qiáng),3 運(yùn)行時(shí)織入:在運(yùn)行時(shí)為目標(biāo)類(lèi)生成代理實(shí)現(xiàn)增強(qiáng)。Spring采用動(dòng)態(tài)代理的方式事實(shí)現(xiàn)了運(yùn)行時(shí)織入,而AspectJ采用了編譯期織入和裝載期織入的方式。
- 切面(Aspect):切面是由切點(diǎn)和增強(qiáng)(引介)組成的,它包括了對(duì)橫切關(guān)注功能的定義,也包括了對(duì)連接點(diǎn)的定義。
原理
AOP是運(yùn)用了動(dòng)態(tài)代理。代理模式是GoF提出的23種設(shè)計(jì)模式中最為經(jīng)典的模式之一,代理是對(duì)象的結(jié)構(gòu)模式,他給某一個(gè)對(duì)象提供一個(gè)對(duì)象代理,并由代理控制對(duì)原對(duì)象的引用,簡(jiǎn)單的說(shuō),代理對(duì)象可以完成比原對(duì)象更多的職責(zé),當(dāng)需要為原對(duì)象添加橫切關(guān)注功能時(shí),就可以使用原對(duì)象的代理對(duì)象。我們?cè)诖蜷_(kāi)Office系列的Word文檔時(shí),如果文檔中有插圖,當(dāng)文檔剛加載時(shí),文檔中的插圖都只是一個(gè)虛框的占位符,等真正用戶翻到某一頁(yè)要查看該圖片時(shí),才會(huì)真正加載這張圖片,這其實(shí)就是代理模式的使用,代替真正的虛框就是一個(gè)虛擬代理,Hibernate的load方法也是返回一個(gè)虛擬代理對(duì)象,等真正用戶需要訪問(wèn)對(duì)象的屬性時(shí),才向數(shù)據(jù)庫(kù)發(fā)出SQL語(yǔ)句。
按照代理創(chuàng)建初期,代理可以分為兩種:
- 靜態(tài)代理:由程序員創(chuàng)建或特定工具自動(dòng)生成源代碼,再對(duì)其編譯,在程序運(yùn)行前,代理類(lèi)的.class文件就已經(jīng)存在了。
- 動(dòng)態(tài)代理:在程序運(yùn)行時(shí),運(yùn)行反射機(jī)制動(dòng)態(tài)創(chuàng)建而成 另外還有CGLib動(dòng)態(tài)代理,它是針對(duì)類(lèi)實(shí)現(xiàn)的代理。
實(shí)現(xiàn)
JDK代理
靜態(tài)代理
下面用一個(gè)找槍手代考的例子演示代理模式的使用
package com.test.test2.interfaces;/*** @Author: Young* @QQ: 403353323* @Date: 2019/4/25 10:35*/ public interface Candidate {public void answerTheQuestion(); }復(fù)制代碼package com.test.test2;import com.test.test2.interfaces.Candidate;/*** @Author: Young* @QQ: 403353323* @Date: 2019/4/25 10:36*/ public class LazyStudent implements Candidate {private String name;public LazyStudent(String name) {this.name = name;}@Overridepublic void answerTheQuestion() {// 懶學(xué)生只能寫(xiě)出自己名字不會(huì)答題System.out.println("姓名" + name);} }復(fù)制代碼package com.test.test2;import com.test.test2.interfaces.Candidate;/*** @Author: Young* @QQ: 403353323* @Date: 2019/4/25 10:40*/ public class Gunman implements Candidate {private Candidate target; //被代理的對(duì)象public Gunman(Candidate target) {this.target = target;}@Overridepublic void answerTheQuestion() {// 搶手要寫(xiě)上代考學(xué)生的名字target.answerTheQuestion();// 搶手幫助懶學(xué)生答題并交卷System.out.println("奮筆疾書(shū)書(shū)寫(xiě)正確答案");System.out.println("交卷");} }復(fù)制代碼@Test public void testGunman(){Candidate c = new Gunman(new LazyStudent("王小二"));c.answerTheQuestion(); } 復(fù)制代碼觀察代理可以發(fā)現(xiàn)每一個(gè)代理類(lèi)只能為一個(gè)接口服務(wù),這樣一來(lái)程序開(kāi)發(fā)中必然會(huì)產(chǎn)生很多的代理,而且,所有代理除了調(diào)用方法不一樣之外,其他操作都一樣,則此時(shí)肯定是重復(fù)代碼,解決這一個(gè)問(wèn)題最好的做法是通過(guò)一個(gè)代理類(lèi)完成全部的代理功能,那么此時(shí)就必須使用動(dòng)態(tài)代理了。
動(dòng)態(tài)代理
從JDK 1.3 開(kāi)始,Java提供了動(dòng)態(tài)技術(shù),動(dòng)態(tài)代理類(lèi)的字節(jié)碼在程序運(yùn)行時(shí)由Java反射機(jī)制動(dòng)態(tài)生成,無(wú)需程序員手工編寫(xiě)它的源代碼。動(dòng)態(tài)代理類(lèi)不僅簡(jiǎn)化了編程工作,而且提高了軟件系統(tǒng)的可擴(kuò)展性,因?yàn)榉瓷錂C(jī)制可以生成任意類(lèi)型的動(dòng)態(tài)代理。
java.lang.reflect包中的Proxy類(lèi)和InvocationHandler接口提供了生成動(dòng)態(tài)代理的能力。
JDK動(dòng)態(tài)代理中包含一個(gè)接口和一個(gè)類(lèi):
- InvocationHandler接口
參數(shù)說(shuō)明:
| Object proxy | 指被代理的對(duì)象 |
| Method method | 要調(diào)用的方法 |
| Object[] args | 方法調(diào)用時(shí)的參數(shù) |
可以將InvocationHandler接口的子類(lèi)想象成一個(gè)代理的最終操作類(lèi),替換掉ProxySubject。
- Proxy類(lèi):
- Proxy類(lèi)是專(zhuān)門(mén)完成代理的操作類(lèi),可以通過(guò)此類(lèi)為一個(gè)或多個(gè)接口動(dòng)態(tài)生成實(shí)現(xiàn)類(lèi),此類(lèi)提供了如下的操作方法
參數(shù)說(shuō)明:
源碼中的參數(shù)說(shuō)明
* @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class to implement * @param h the invocation handler to dispatch method invocations to 復(fù)制代碼| ClassLoader | 類(lèi)加載器 |
| Class<?>[] interfaces | 得到全部的接口 |
| InvocationHandler h | 得到InvocationHandler接口的子類(lèi)實(shí)例 |
Ps:在Proxy中類(lèi)的newProxyInstance()方法中需要一個(gè)ClassLoader類(lèi)的實(shí)例,ClassLoader實(shí)際上對(duì)應(yīng)的是類(lèi)加載器。在Java中主要有三種類(lèi)加載器:
- Bootstrap ClassLoader:此口加載器采用C++編寫(xiě),一般開(kāi)發(fā)中是看不到的。
- Extendsion ClassLoader:用來(lái)進(jìn)行擴(kuò)展類(lèi)的加載器,一般對(duì)應(yīng)的是jre\lib\ext目錄中的類(lèi);
- APPClassLoader:(默認(rèn))加載classpath制定的類(lèi),是最常使用的一種加載器。
BookFacade.java
public interface BookFacade { public void addBook(); } 復(fù)制代碼BookFacadeImpl.java
import net.battier.dao.BookFacade; public class BookFacadeImpl implements BookFacade { @Override public void addBook() { System.out.println("增加圖書(shū)方法。。。"); } } 復(fù)制代碼BookFacadeProxy.java
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * JDK動(dòng)態(tài)代理代理類(lèi) */ public class BookFacadeProxy implements InvocationHandler { private Object target; /** * 綁定委托對(duì)象并返回一個(gè)代理類(lèi) * @param target * @return */ public Object bind(Object target) { this.target = target; //取得代理對(duì)象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); //要綁定接口(這是一個(gè)缺陷,cglib彌補(bǔ)了這一缺陷) } @Override /** * 調(diào)用方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; System.out.println("事物開(kāi)始"); //執(zhí)行方法 result=method.invoke(target, args); System.out.println("事物結(jié)束"); return result; } } 復(fù)制代碼TestProxy.java
import net.battier.dao.BookFacade; import net.battier.dao.impl.BookFacadeImpl; import net.battier.proxy.BookFacadeProxy; public class TestProxy { public static void main(String[] args) { BookFacadeProxy proxy = new BookFacadeProxy(); BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl()); bookProxy.addBook(); } } 復(fù)制代碼說(shuō)明:使用Java的動(dòng)態(tài)代理有一個(gè)局限性就是被代理的類(lèi)必須實(shí)現(xiàn)接口(也就是說(shuō)只能對(duì)該類(lèi)所實(shí)現(xiàn)接口中定義的方法進(jìn)行代理),雖然面向接口編程是每個(gè)優(yōu)秀的Java程序員都知道的規(guī)則,但現(xiàn)實(shí)往往不盡人意,對(duì)于沒(méi)有實(shí)現(xiàn)接口的代理類(lèi)如何為其生成代理類(lèi)呢?繼承繼承是最經(jīng)典的擴(kuò)展已有代碼能力的手段。CGLib代理就是這樣實(shí)現(xiàn)代理。
CGLib動(dòng)態(tài)代理
CGLib采用非常底層的字節(jié)碼生成技術(shù),通過(guò)為一個(gè)類(lèi)創(chuàng)建子類(lèi)來(lái)生成代理類(lèi),它彌補(bǔ)了Java動(dòng)態(tài)代理的不足,因此,Spring中的動(dòng)態(tài)代理和CGLib都是創(chuàng)建代理的重要手段,對(duì)于實(shí)現(xiàn)了接口的類(lèi)就用動(dòng)態(tài)代理為其生成代理類(lèi),而沒(méi)有實(shí)現(xiàn)接口的類(lèi)就用CGLib通過(guò)繼承方式為其創(chuàng)建代理類(lèi)。但因?yàn)椴捎玫氖抢^承,所以不能對(duì)final修飾的類(lèi)進(jìn)行代理。
intercept參數(shù)列表:
| Object obj | 代理的對(duì)象 |
| Metho method | 表示攔截的方法 |
| Object[] args | 數(shù)組表示參數(shù)列表,基本數(shù)據(jù)類(lèi)型需要傳入其包裝類(lèi)型,如int-->Integer、long-Long、double-->Double |
| MethodProxy methodProxy | 表示對(duì)方法的代理,invokeSuper方法表示對(duì)被代理對(duì)象方法的調(diào)用 |
參考
AOP實(shí)現(xiàn)原理
Java面試知識(shí)點(diǎn)總結(jié)及解析
java動(dòng)態(tài)代理(JDK和cglib)
Cglib及其基本使用
總結(jié)
以上是生活随笔為你收集整理的AOP概述及实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java手写实现BST
- 下一篇: [题集]串