日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

aop切面排除某个类_AOP 你看这一篇就够了

發布時間:2025/5/22 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 aop切面排除某个类_AOP 你看这一篇就够了 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

網上很多人在介紹AOP時都這樣說:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。個人認為這句話是錯誤。AOP和OOP一樣,是一種程序設計思想,而非技術手段。

程序設計有六大原則,其中第一原則就是 單一職責原則 。意思就是一個類只負責一件事情。這與OOP的封裝特性相得益彰。在這個條件下,我們的程序會被分散到不同的類、不同的方法中去。這樣做的好處是降低了類的復雜性,提高了程序的可維護性。但是同時,它也使代碼變得啰嗦了。例如,我們要為方法添加調用日志,那就必須為所有類的所有方法添加日志調用,盡管它們都是相同的。為了解決上述問題,AOP應運而生了。

AOP旨在將 橫切關注點 與業務主體進行分類,從而提高程序代碼的模塊化程度。橫切關注點是一個抽象的概念,它是指那些在項目中貫穿多個模塊的業務。上個例子中日志功能就是一個典型的橫切關注點。

AOP的幾種實現方式

動態代理

動態代理是一種設計模式。它有以下特征:

  • 我們不需要自己寫代理類。
  • 運行期通過接口直接生成代理對象。
  • 運行期間才確定代理哪個對象。

以下面這個例子為例,我們看一下動態代理的類圖結構。

通常我們的APP都有一部分功能要求用戶登錄之后才能訪問。如修改密碼、修改用戶名等功能。當用戶打算使用這些功能時,我們一般要對用戶的登錄狀態進行判斷,只有用戶登錄了,才能正常使用這些功能。而如果用戶未登錄,我們的APP要跳轉到登錄頁。就以修改密碼為例我們看一下動態代理的類圖。

InvocationHandler是Java JDK提供的動態代理的入口,用來對被代理對象的方法做處理。

代碼如下:

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("跳轉到登錄頁"); } } public class Client { public static void main(String[] args) { IUserSetting source = new UserSetting(); IUserSetting iUserSetting = LoginCheckHandler.proxy(source,IUserSetting.class); iUserSetting.changePwd("new Password"); } }復制代碼

經過這樣封裝之后,檢查登錄跳轉登錄頁的邏輯作為 橫切關注點 就和業務主體進行了分離。當有新的需求需要登錄檢查時,我們只需要通過LoginCheckHandler生成新的代理對象即可。

APT

APT(Annotation Processing Tool)是一種編譯期注解處理技術。它通過定義注解和處理器來實現編譯期生成代碼的功能,并且將生成的代碼和源代碼一起編譯成.class文件。通過APT技術,我們將 橫切關注點 封裝到注解處理器中,從而實現 橫切關注點 與業務主體的分離。更詳細的介紹請移步 Android編譯期插樁,讓程序自己寫代碼(一)

AspectJ

AspectJ就是一種編譯器,它在Java編譯器的基礎上增加了關鍵字識別和編譯方法。因此,AspectJ可以編譯Java代碼。它還提供了Aspect程序。在編譯期間,將開發者編寫的Aspect程序織入到目標程序中,擴展目標程序的功能。開發者通過編寫AspectJ程序實現AOP功能。更詳細的介紹請移步 Android編譯期插樁,讓程序自己寫代碼(二)

Transform + Javassist/ASM

Transform是Android Gradle提供的,可以操作字節碼的一種方式。App編譯時,源代碼首先會被編譯成class,然后再被編譯成dex。在class編譯成dex的過程中,會經過一系列 Transform 處理。 Javassist/ASM 是一個能夠非常方便操作字節碼的庫。我們通過它們可以修改編譯的.class文件。

橫切關注點

影響應用多處的功能(日志、事務、安全)

增強(Advice)

增強定義了切面要完成的功能以及什么時候執行這個功能。

Spring 切面可以應用 5 種類型的增強:

  • 前置增強(Before) 在目標方法被調用前調用增強功能
  • 后置增強(After) 在目標方法完成之后調用增強, 不關注方法輸出是什么
  • 返回增強(After-returning) 在目標方法成功執行之后調用增強
  • 異常增強(After-throwing) 在目標方法拋出異常后調用增強
  • 環繞增強(Around) 在被增強的方法調用之前和調用之后執行自定義行為,即包括前置增強和后置增強。

連接點(Join Point)

應用中每一個有可能會被增強的點被稱為連接點。

切點(Pointcut)

切點是規則匹配出來的連接點。

切面(Aspect)

切面是增強和切點的結合,定義了在何時和何處完成其功能。

引入(Introduction)

引入允許我們向現有的類中添加新方法和屬性。可以在不修改現有的類的情況下,讓類具有新的行為和狀態。

織入(Weaving)

織入是把切面應用到目標對象中并創建新的代理對象的過程。在目標對象的生命周期里有多個點可以進行織入:

  • 編譯器:切面在目標類編譯時織入。這種方式需要特殊的編譯器。AspectJ 的織入編譯器就是以這種方式織入切面的。
  • 類加載器:切面在目標類加載到 JVM 時被織入。這種方式需要特殊的類加載器(ClassLoader),它可以在目標類被引入應用之前增強該目標類的字節碼。AspectJ5 的加載時織入(LTW)支持以這種方式織入。
  • 運行期:切面在應用運行時的某個時刻被織入。一般情況下,在織入切面時,AOP 容器會為目標對象動態地創建一個代理對象。Spring AOP 就是以這種方式織入切面的。

Spring 對 AOP 的支持

Spring 對 AOP 的支持在很多方面借鑒了 AspectJ 項目。目前 Spring 提供了 4 種類型的 AOP 支持:

  • 基于代理的經典 AOP
  • 純 POJO 切面
  • @AspectJ 注解驅動的切面
  • 注入式 AspectJ 切面

Spring AOP 構建在動態代理基礎之上,因此 Spring 對 AOP 的支持局限于方法攔截。

運行時增強

通過在代理中包裹切面,Spring 在運行期把切面織入到 Spring 管理的 bean 中。代理類封裝了目標類,并攔截被增強方法的調用,再把調用轉發給真正的目標 bean。在代理攔截到方法調用時,在調用目標 bean 方法之前,會執行切面邏輯。

直到應用需要代理的 bean 時,Spring 才創建代理對象。如果使用 ApplicationContext 的話,在 ApplicationContext 從 BeanFactory 中加載所有 bean 的時候,Spring 才會創建被代理的對象。

方法級別的連接點

Spring 基于動態代理實現 AOP,所以 Spring 只支持方法連接點。其他的 AOP 框架比如 AspectJ 與 JBoss,都提供了字段和構造器接入點,允許創建細粒度的增強。

切點表達式

Spring AOP 中,使用 AspectJ 的切點表達式來定義切點。Spring 只支持 AspectJ 切點指示器(pointcut designator)的一個子集。

指示器

AspectJ 指示器描述arg( )限制連接點匹配參數為指定類型的執行方法execution( )用于匹配連接點this指定匹配 AOP 代理的 bean 引用的類型target指定匹配對象為特定的類within( )指定連接點匹配的類型@annotation匹配帶有指定注解的連接點

編寫切點

package concert;public interface Performance { public void perform();}復制代碼

Performance 類可以代表任何類型的現場表演,比如電影、舞臺劇等。現在編寫一個切點表達式來限定 perform() 方法執行時觸發的增強。

execution(* concert.Performance.perform(..))復制代碼

每個部分的意義如下圖所示:

也可以引入其他注解對匹配規則做進一步限制。比如

execution(* concert.Performance.perform(..)) && within(concert.*)復制代碼

within() 指示器限制了切點僅匹配 concert 包。

Spring 還有一個 bean() 指示器,允許我們在切點表達式中使用 bean 的 ID 表示 bean。

execution(* concert.Performance.perform(..)) && bean('woodstock')復制代碼

以上的切點就表示限定切點的 bean 的 ID 為 woodstock 。

使用注解創建切面

定義切面

在一場演出之前,我們需要讓觀眾將手機靜音且就座,觀眾在表演之后鼓掌,在表演失敗之后可以退票。在觀眾類中定義這些功能。

@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"); }}復制代碼

@AspectJ 注解表名了該類是一個切面。 @Pointcut 定義了一個類中可重用的切點,寫切點表達式時,如果切點相同,可以重用該切點。 其余方法上的注解定義了增強被調用的時間,根據注解名可以知道具體調用時間。

到目前為止, Audience 仍然只是 Spring 容器中的一個 bean。即使使用了 AspectJ 注解,但是這些注解仍然不會解析,因為目前還缺乏代理的相關配置。

如果使用 JavaConfig,在配置類的類級別上使用 @EnableAspectJAutoProxy 注解啟用自動代理功能。

@Configuration@EnableAspectJAutoProxy@ComponentScanpublic class ConcertConfig { @Bean public Audience audience() { return new Audience(); } }復制代碼

如果使用 xml ,那么需要引入 元素。

<?xml version="1.0" encoding="UTF-8"?>復制代碼

環繞增強

環繞增強就像在一個增強方法中同時編寫了前置增強和后置增強。

@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"); } }}復制代碼

可以看到,這個增強達到的效果與分開寫前置增強與后置增強是一樣的,但是現在所有的功能都位于同一個方法內。 注意該方法接收 ProceedingJoinPoint 作為參數,這個對象必須要有,因為需要通過它來調用被增強的方法。 注意,在這個方法中,我們可以控制不調用 proceed() 方法,從而阻塞對增強方法的訪問。同樣,我們也可以在增強方法失敗后,多次調用 proceed() 進行重試。

增強方法參數

修改 Perform#perform() 方法,添加參數

package concert;public interface Performance { public void perform(int audienceNumbers);}復制代碼

我們可以通過切點表達式來獲取被增強方法中的參數。

@Pointcut("execution(* concert.Performance.perform(int)) && args(audienceNumbers)))") public void performance(int audienceNumbers){}復制代碼

注意,此時方法接收的參數為 int 型, args(audienceNumbers) 指定參數名為 audienceNumbers ,與切點方法簽名中的參數匹配,該參數不一定與增強方法的參數名一致。

引入增強

切面不僅僅能夠增強現有方法,也能為對象新增新的方法。 我們可以在代理中暴露新的接口,當引入接口的方法被調用時,代理會把此調用委托給實現了新接口的某個其他對象。實際上,就是一個 bean 的實現被拆分到多個類中了。 定義 Encoreable 接口,將其引入到 Performance 的實現類中。

public interface Encoreable { void performEncore();}復制代碼

創建一個新的切面

@Aspectpublic class EncoreableIntroducer { @DeclareParents(value = "concert.Performance+

總結

以上是生活随笔為你收集整理的aop切面排除某个类_AOP 你看这一篇就够了的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。