aop切面排除某个类_AOP 你看这一篇就够了
網(wǎng)上很多人在介紹AOP時都這樣說:面向切面編程,通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。個人認(rèn)為這句話是錯誤。AOP和OOP一樣,是一種程序設(shè)計思想,而非技術(shù)手段。
程序設(shè)計有六大原則,其中第一原則就是 單一職責(zé)原則 。意思就是一個類只負(fù)責(zé)一件事情。這與OOP的封裝特性相得益彰。在這個條件下,我們的程序會被分散到不同的類、不同的方法中去。這樣做的好處是降低了類的復(fù)雜性,提高了程序的可維護(hù)性。但是同時,它也使代碼變得啰嗦了。例如,我們要為方法添加調(diào)用日志,那就必須為所有類的所有方法添加日志調(diào)用,盡管它們都是相同的。為了解決上述問題,AOP應(yīng)運而生了。
AOP旨在將 橫切關(guān)注點 與業(yè)務(wù)主體進(jìn)行分類,從而提高程序代碼的模塊化程度。橫切關(guān)注點是一個抽象的概念,它是指那些在項目中貫穿多個模塊的業(yè)務(wù)。上個例子中日志功能就是一個典型的橫切關(guān)注點。
AOP的幾種實現(xiàn)方式
動態(tài)代理
動態(tài)代理是一種設(shè)計模式。它有以下特征:
- 我們不需要自己寫代理類。
- 運行期通過接口直接生成代理對象。
- 運行期間才確定代理哪個對象。
以下面這個例子為例,我們看一下動態(tài)代理的類圖結(jié)構(gòu)。
通常我們的APP都有一部分功能要求用戶登錄之后才能訪問。如修改密碼、修改用戶名等功能。當(dāng)用戶打算使用這些功能時,我們一般要對用戶的登錄狀態(tài)進(jìn)行判斷,只有用戶登錄了,才能正常使用這些功能。而如果用戶未登錄,我們的APP要跳轉(zhuǎn)到登錄頁。就以修改密碼為例我們看一下動態(tài)代理的類圖。
InvocationHandler是Java JDK提供的動態(tài)代理的入口,用來對被代理對象的方法做處理。
代碼如下:
public static class LoginCheckHandler implements InvocationHandler { private static T proxy(S source, Class tClass) { return (T) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{tClass}, new LoginCheckHandler(source)); } private Object mSource; LoginCheckHandler(Object source) { this.mSource = source; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(!checkLogin()){ jumpToLoginActivity(); return null; } return method.invoke(mSource, args); } private boolean checkLogin(){ System.out.println("用戶未登錄"); return false; } private void jumpToLoginActivity(){ System.out.println("跳轉(zhuǎn)到登錄頁"); } } public class Client { public static void main(String[] args) { IUserSetting source = new UserSetting(); IUserSetting iUserSetting = LoginCheckHandler.proxy(source,IUserSetting.class); iUserSetting.changePwd("new Password"); } }復(fù)制代碼經(jīng)過這樣封裝之后,檢查登錄跳轉(zhuǎn)登錄頁的邏輯作為 橫切關(guān)注點 就和業(yè)務(wù)主體進(jìn)行了分離。當(dāng)有新的需求需要登錄檢查時,我們只需要通過LoginCheckHandler生成新的代理對象即可。
APT
APT(Annotation Processing Tool)是一種編譯期注解處理技術(shù)。它通過定義注解和處理器來實現(xiàn)編譯期生成代碼的功能,并且將生成的代碼和源代碼一起編譯成.class文件。通過APT技術(shù),我們將 橫切關(guān)注點 封裝到注解處理器中,從而實現(xiàn) 橫切關(guān)注點 與業(yè)務(wù)主體的分離。更詳細(xì)的介紹請移步 Android編譯期插樁,讓程序自己寫代碼(一) 。
AspectJ
AspectJ就是一種編譯器,它在Java編譯器的基礎(chǔ)上增加了關(guān)鍵字識別和編譯方法。因此,AspectJ可以編譯Java代碼。它還提供了Aspect程序。在編譯期間,將開發(fā)者編寫的Aspect程序織入到目標(biāo)程序中,擴(kuò)展目標(biāo)程序的功能。開發(fā)者通過編寫AspectJ程序?qū)崿F(xiàn)AOP功能。更詳細(xì)的介紹請移步 Android編譯期插樁,讓程序自己寫代碼(二) 。
Transform + Javassist/ASM
Transform是Android Gradle提供的,可以操作字節(jié)碼的一種方式。App編譯時,源代碼首先會被編譯成class,然后再被編譯成dex。在class編譯成dex的過程中,會經(jīng)過一系列 Transform 處理。 Javassist/ASM 是一個能夠非常方便操作字節(jié)碼的庫。我們通過它們可以修改編譯的.class文件。
橫切關(guān)注點
影響應(yīng)用多處的功能(日志、事務(wù)、安全)
增強(qiáng)(Advice)
增強(qiáng)定義了切面要完成的功能以及什么時候執(zhí)行這個功能。
Spring 切面可以應(yīng)用 5 種類型的增強(qiáng):
- 前置增強(qiáng)(Before) 在目標(biāo)方法被調(diào)用前調(diào)用增強(qiáng)功能
- 后置增強(qiáng)(After) 在目標(biāo)方法完成之后調(diào)用增強(qiáng), 不關(guān)注方法輸出是什么 。
- 返回增強(qiáng)(After-returning) 在目標(biāo)方法成功執(zhí)行之后調(diào)用增強(qiáng)
- 異常增強(qiáng)(After-throwing) 在目標(biāo)方法拋出異常后調(diào)用增強(qiáng)
- 環(huán)繞增強(qiáng)(Around) 在被增強(qiáng)的方法調(diào)用之前和調(diào)用之后執(zhí)行自定義行為,即包括前置增強(qiáng)和后置增強(qiáng)。
連接點(Join Point)
應(yīng)用中每一個有可能會被增強(qiáng)的點被稱為連接點。
切點(Pointcut)
切點是規(guī)則匹配出來的連接點。
切面(Aspect)
切面是增強(qiáng)和切點的結(jié)合,定義了在何時和何處完成其功能。
引入(Introduction)
引入允許我們向現(xiàn)有的類中添加新方法和屬性。可以在不修改現(xiàn)有的類的情況下,讓類具有新的行為和狀態(tài)。
織入(Weaving)
織入是把切面應(yīng)用到目標(biāo)對象中并創(chuàng)建新的代理對象的過程。在目標(biāo)對象的生命周期里有多個點可以進(jìn)行織入:
- 編譯器:切面在目標(biāo)類編譯時織入。這種方式需要特殊的編譯器。AspectJ 的織入編譯器就是以這種方式織入切面的。
- 類加載器:切面在目標(biāo)類加載到 JVM 時被織入。這種方式需要特殊的類加載器(ClassLoader),它可以在目標(biāo)類被引入應(yīng)用之前增強(qiáng)該目標(biāo)類的字節(jié)碼。AspectJ5 的加載時織入(LTW)支持以這種方式織入。
- 運行期:切面在應(yīng)用運行時的某個時刻被織入。一般情況下,在織入切面時,AOP 容器會為目標(biāo)對象動態(tài)地創(chuàng)建一個代理對象。Spring AOP 就是以這種方式織入切面的。
Spring 對 AOP 的支持
Spring 對 AOP 的支持在很多方面借鑒了 AspectJ 項目。目前 Spring 提供了 4 種類型的 AOP 支持:
- 基于代理的經(jīng)典 AOP
- 純 POJO 切面
- @AspectJ 注解驅(qū)動的切面
- 注入式 AspectJ 切面
Spring AOP 構(gòu)建在動態(tài)代理基礎(chǔ)之上,因此 Spring 對 AOP 的支持局限于方法攔截。
運行時增強(qiáng)
通過在代理中包裹切面,Spring 在運行期把切面織入到 Spring 管理的 bean 中。代理類封裝了目標(biāo)類,并攔截被增強(qiáng)方法的調(diào)用,再把調(diào)用轉(zhuǎn)發(fā)給真正的目標(biāo) bean。在代理攔截到方法調(diào)用時,在調(diào)用目標(biāo) bean 方法之前,會執(zhí)行切面邏輯。
直到應(yīng)用需要代理的 bean 時,Spring 才創(chuàng)建代理對象。如果使用 ApplicationContext 的話,在 ApplicationContext 從 BeanFactory 中加載所有 bean 的時候,Spring 才會創(chuàng)建被代理的對象。
方法級別的連接點
Spring 基于動態(tài)代理實現(xiàn) AOP,所以 Spring 只支持方法連接點。其他的 AOP 框架比如 AspectJ 與 JBoss,都提供了字段和構(gòu)造器接入點,允許創(chuàng)建細(xì)粒度的增強(qiáng)。
切點表達(dá)式
Spring AOP 中,使用 AspectJ 的切點表達(dá)式來定義切點。Spring 只支持 AspectJ 切點指示器(pointcut designator)的一個子集。
指示器
AspectJ 指示器描述arg( )限制連接點匹配參數(shù)為指定類型的執(zhí)行方法execution( )用于匹配連接點this指定匹配 AOP 代理的 bean 引用的類型target指定匹配對象為特定的類within( )指定連接點匹配的類型@annotation匹配帶有指定注解的連接點
編寫切點
package concert;public interface Performance { public void perform();}復(fù)制代碼Performance 類可以代表任何類型的現(xiàn)場表演,比如電影、舞臺劇等。現(xiàn)在編寫一個切點表達(dá)式來限定 perform() 方法執(zhí)行時觸發(fā)的增強(qiáng)。
execution(* concert.Performance.perform(..))復(fù)制代碼每個部分的意義如下圖所示:
也可以引入其他注解對匹配規(guī)則做進(jìn)一步限制。比如
execution(* concert.Performance.perform(..)) && within(concert.*)復(fù)制代碼within() 指示器限制了切點僅匹配 concert 包。
Spring 還有一個 bean() 指示器,允許我們在切點表達(dá)式中使用 bean 的 ID 表示 bean。
execution(* concert.Performance.perform(..)) && bean('woodstock')復(fù)制代碼以上的切點就表示限定切點的 bean 的 ID 為 woodstock 。
使用注解創(chuàng)建切面
定義切面
在一場演出之前,我們需要讓觀眾將手機(jī)靜音且就座,觀眾在表演之后鼓掌,在表演失敗之后可以退票。在觀眾類中定義這些功能。
@Aspectpublic class Audience { @Pointcut("execution(* concert.Performance.perform(..)))") public void performance(){} @Before("performance()") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("performance()") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("performance()") public void applause() { System.out.println("CLAP CLAP CLAP!!!"); } @AfterThrowing("performance()") public void demandRefund() { System.out.println("Demanding a refund"); }}復(fù)制代碼@AspectJ 注解表名了該類是一個切面。 @Pointcut 定義了一個類中可重用的切點,寫切點表達(dá)式時,如果切點相同,可以重用該切點。 其余方法上的注解定義了增強(qiáng)被調(diào)用的時間,根據(jù)注解名可以知道具體調(diào)用時間。
到目前為止, Audience 仍然只是 Spring 容器中的一個 bean。即使使用了 AspectJ 注解,但是這些注解仍然不會解析,因為目前還缺乏代理的相關(guān)配置。
如果使用 JavaConfig,在配置類的類級別上使用 @EnableAspectJAutoProxy 注解啟用自動代理功能。
@Configuration@EnableAspectJAutoProxy@ComponentScanpublic class ConcertConfig { @Bean public Audience audience() { return new Audience(); } }復(fù)制代碼如果使用 xml ,那么需要引入 元素。
<?xml version="1.0" encoding="UTF-8"?>復(fù)制代碼環(huán)繞增強(qiáng)
環(huán)繞增強(qiáng)就像在一個增強(qiáng)方法中同時編寫了前置增強(qiáng)和后置增強(qiáng)。
@Aspectpublic class Audience { @Pointcut("execution(* concert.Performance.perform(..)))") public void performance(){} @Around("performance()") public void watchPerformance(ProceedingJoinPoint joinPoint) { try { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); joinPoint.proceed(); System.out.println("CLAP CLAP CLAP!!!"); } catch (Throwable throwable) { System.out.println("Demanding a refund"); } }}復(fù)制代碼可以看到,這個增強(qiáng)達(dá)到的效果與分開寫前置增強(qiáng)與后置增強(qiáng)是一樣的,但是現(xiàn)在所有的功能都位于同一個方法內(nèi)。 注意該方法接收 ProceedingJoinPoint 作為參數(shù),這個對象必須要有,因為需要通過它來調(diào)用被增強(qiáng)的方法。 注意,在這個方法中,我們可以控制不調(diào)用 proceed() 方法,從而阻塞對增強(qiáng)方法的訪問。同樣,我們也可以在增強(qiáng)方法失敗后,多次調(diào)用 proceed() 進(jìn)行重試。
增強(qiáng)方法參數(shù)
修改 Perform#perform() 方法,添加參數(shù)
package concert;public interface Performance { public void perform(int audienceNumbers);}復(fù)制代碼我們可以通過切點表達(dá)式來獲取被增強(qiáng)方法中的參數(shù)。
@Pointcut("execution(* concert.Performance.perform(int)) && args(audienceNumbers)))") public void performance(int audienceNumbers){}復(fù)制代碼注意,此時方法接收的參數(shù)為 int 型, args(audienceNumbers) 指定參數(shù)名為 audienceNumbers ,與切點方法簽名中的參數(shù)匹配,該參數(shù)不一定與增強(qiáng)方法的參數(shù)名一致。
引入增強(qiáng)
切面不僅僅能夠增強(qiáng)現(xiàn)有方法,也能為對象新增新的方法。 我們可以在代理中暴露新的接口,當(dāng)引入接口的方法被調(diào)用時,代理會把此調(diào)用委托給實現(xiàn)了新接口的某個其他對象。實際上,就是一個 bean 的實現(xiàn)被拆分到多個類中了。 定義 Encoreable 接口,將其引入到 Performance 的實現(xiàn)類中。
public interface Encoreable { void performEncore();}復(fù)制代碼創(chuàng)建一個新的切面
@Aspectpublic class EncoreableIntroducer { @DeclareParents(value = "concert.Performance+總結(jié)
以上是生活随笔為你收集整理的aop切面排除某个类_AOP 你看这一篇就够了的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为ac配置radius认证服务器_合作
- 下一篇: 深度复制_Python 列表切片陷阱:引