天天看點

死磕Spring之AOP篇 - Spring AOP常見面試題

該系列文章是本人在學習 Spring 的過程中總結下來的,裡面涉及到相關源碼,可能對讀者不太友好,請結合我的源碼注釋 Spring 源碼分析 GitHub 位址 進行閱讀。

Spring 版本:5.1.14.RELEASE

在開始閱讀 Spring AOP 源碼之前,需要對 Spring IoC 有一定的了解,可檢視我的 《死磕Spring之IoC篇 - 文章導讀》 這一系列文章

該系列其他文章請檢視:《死磕 Spring 之 AOP 篇 - 文章導讀》

目錄

  • 什麼是 AOP?
  • 為什麼要引入 AOP?
  • 簡述 AOP 的使用場景?
  • 簡述 AOP 中幾個比較重要的概念
  • 你知道哪幾種 AOP 架構?
  • 什麼是 AOP 代理?
  • 講講 JDK 動态代理?
  • 講講 CGLIB 動态代理?
  • JDK 動态代理和 CGLIB 動态代理有什麼不同?
  • Spring AOP 和 AspectJ 有什麼關聯?
  • Spring AOP 中有哪些 Advice 類型?
  • Spring AOP 中 Advisor 接口是什麼?
  • 簡述 Spring AOP 自動代理的實作
  • 請解釋 Spring @EnableAspectJAutoProxy 的原理?
  • Spring Configuration Class CGLIB 提升與 AOP 類代理關系?
  • Sping AOP 應用到哪些設計模式?
  • Spring AOP 在 Spring Framework 内部有哪些應用?

什麼是 AOP?

官方文檔:

AspectJ:Aspect-oriented programming is a way of modularizing crosscutting concerns much like object-oriented programming is a way of modularizing common concerns.

Spring:Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)

AOP(Aspect-oriented Programming)面向切面程式設計,是一種開發理念,是 OOP 面向對象程式設計的補充。我們知道,Java 就是一門面向對象程式設計的語言,在 OOP 中最小的單元就是“Class 對象”,但是在 AOP 中最小的單元是“切面”。一個“切面”可以包含很多種類型和對象,對它們進行子產品化管理,例如事務管理。

為什麼要引入 AOP?

Java OOP 存在哪些局限性?

  • 靜态化語言:類結構一旦定義,不容易被修改
  • 侵入性擴充:通過繼承或組合組織新的類結構

通過 AOP 我們可以把一些非業務邏輯的代碼(比如安全檢查、監控等代碼)從業務中抽取出來,以非入侵的方式與原方法進行協同。這樣可以使得原方法更專注于業務邏輯,代碼接口會更加清晰,便于維護。

簡述 AOP 的使用場景?

日志場景

  • 診斷上下文,如:log4j 或 logback 中的 _x0008_MDC
  • 輔助資訊,如:方法執行時間

統計場景

  • 方法調用次數
  • 執行異常次數
  • 資料抽樣
  • 數值累加

安防場景

  • 熔斷,如:Netflix Hystrix
  • 限流和降級:如:Alibaba Sentinel
  • 認證和授權,如:Spring Security
  • 監控,如:JMX

性能場景

  • 緩存,如 Spring Cache
  • 逾時控制

可以說在我們的日常開發環境中都是離不開 AOP 的。

簡述 AOP 中幾個比較重要的概念

在 AOP 中有以下幾個概念:

  • AspectJ:切面,隻是一個概念,沒有具體的接口或類與之對應,是 Join point,Advice 和 Pointcut 的一個統稱。
  • Join point:連接配接點,指程式執行過程中的一個點,例如方法調用、異常處理等。在 Spring AOP 中,僅支援方法級别的連接配接點。
  • Advice:通知,即我們定義的一個切面中的橫切邏輯,有“around”,“before”和“after”三種類型。在很多的 AOP 實作架構中,Advice 通常作為一個攔截器,也可以包含許多個攔截器作為一條鍊路圍繞着 Join point 進行處理。
  • Pointcut:切點,用于比對連接配接點,一個 AspectJ 中包含哪些 Join point 需要由 Pointcut 進行篩選。
  • Introduction:引介,讓一個切面可以聲明被通知的對象實作任何他們沒有真正實作的額外的接口。例如可以讓一個代理對象代理兩個目标類。
  • Weaving:織入,在有了連接配接點、切點、通知以及切面,如何将它們應用到程式中呢?沒錯,就是織入,在切點的引導下,将通知邏輯插入到目标方法上,使得我們的通知邏輯在方法調用時得以執行。
  • AOP proxy:AOP 代理,指在 AOP 實作架構中實作切面協定的對象。在 Spring AOP 中有兩種代理,分别是 JDK 動态代理和 CGLIB 動态代理。
  • Target object:目标對象,就是被代理的對象。

你知道哪幾種 AOP 架構?

主流 AOP 架構:

  • AspectJ:完整的 AOP 實作架構
  • Spring AOP:非完整的 AOP 實作架構

Spring AOP 是基于 JDK 動态代理和 Cglib 提升實作的,兩種代理方式都屬于運作時的一個方式,是以它沒有編譯時的一個處理,那麼是以 Spring 是通過 Java 代碼實作的。AspectJ 自己有一個編譯器,在編譯時期可以修改 .class 檔案,在運作時也會進行處理。

Spring AOP 有别于其他大多數 AOP 實作架構,目的不是提供最完整的 AOP 實作(盡管 Spring AOP 相當強大);相反,其目的是在 AOP 實作和 Spring IoC 之間提供緊密的內建,以提供企業級核心特性。

Spring AOP 從未打算與 AspectJ 競争以提供全面的 AOP 解決方案,我們認為 Spring AOP 等基于代理實作的架構和 AspectJ 等成熟的架構都是有價值的,并且它們是互補的,而不是競争關系。Spring 将 Spring AOP 和 IoC 與 AspectJ 無縫內建,以實作 AOP 的所有功能都可以在一個 Spring 應用中。這種內建不會影響 Spring AOP API 或 AOP Alliance API,保持向後相容。

什麼是 AOP 代理?

代理模式是一種結構性設計模式,通過代理類為其他對象提供一種代理以控制對這個對象的通路。AOP 代理是 AOP 架構中 AOP 的實作,主要分為靜态代理和動态代理,如下:

  • 靜态代理:代理類需要實作被代理類所實作的接口,同時持有被代理類的引用,新增處理邏輯,進行攔截處理,不過方法還是由被代理類的引用所執行。靜态代理通常需要由開發人員在編譯階段就定義好,不易于維護。
    • 常用 OOP 繼承群組合相結合
    • AspectJ,在編輯階段會織入 Java 位元組碼,且在運作期間會進行增強。
  • 動态代理:不會修改位元組碼,而是在 JVM 記憶體中根據目标對象新生成一個 Class 對象,這個對象包含了被代理對象的全部方法,并且在其中進行了增強。
    • JDK 動态代理
    • 位元組碼提升,例如 CGLIB

講講 JDK 動态代理?

基于接口代理,通過反射機制生成一個實作代理接口的類,在調用具體方法時會調用 InvocationHandler 來處理。

需要借助 JDK 的

java.lang.reflect.Proxy

來建立代理對象,調用

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

方法建立一個代理對象,方法的三個入參分别是:

  • ClassLoader loader

    :用于加載代理對象的 Class 類加載器
  • Class<?>[] interfaces

    :代理對象需要實作的接口
  • InvocationHandler h

    :代理對象的處理器

新生成的代理對象的 Class 對象會繼承

Proxy

,且實作所有的入參

interfaces

中的接口,在實作的方法中實際是調用入參

InvocationHandler

invoke(..)

方法。

為什麼 JDK 動态代理隻能基于接口代理,不能基于類代理?

因為 JDK 動态代理生成的代理對象需要繼承

Proxy

這個類,在 Java 中類隻能是單繼承關系,無法再繼承一個代理類,是以隻能基于接口代理。
為什麼 InvocationHandler 不直接聲明到這個代理對象裡面,而是放入繼承的

Proxy

父類中?

我覺得代理類既然是 JDK 動态生成的,那麼 JDK 就需要識别出哪些類是生成的代理類,哪些是非代理類,或者說 JDK 需要對代理類做統一的處理,這時如果沒有一個統一的類 Proxy 來進行引用根本無法處理。這隻是筆者的想法,具體為什麼這麼做不知道有小夥伴知道不 ~

講講 CGLIB 動态代理?

JDK 動态代理的目标對象必須是一個接口,在我們日常生活中,無法避免開發人員不寫接口直接寫類,或者根本不需要接口,直接用類進行表達。這個時候我們就需要通過一些位元組碼提升的手段,來幫助做這個事情,在運作時,非編譯時,來建立一個新的 Class 對象,這種方式稱之為位元組碼提升。在 Spring 内部有兩個位元組碼提升的架構,ASM(過于底層,直接操作位元組碼)和 CGLIB(相對于前者更加簡便)。

CGLIB 動态代理則是基于類代理(位元組碼提升),通過 ASM(Java 位元組碼的操作和分析架構)将被代理類的 class 檔案加載進來,修改其位元組碼生成一個子類。

需要借助于 CGLIB 的

org.springframework.cglib.proxy.Enhancer

類來建立代理對象,設定以下幾個屬性:

  • Class<?> superClass

    :被代理的類
  • Callback callback

    :回調接口

新生成的代理對象的 Class 對象會繼承

superClass

被代理的類,在重寫的方法中會調用

callback

回調接口(方法攔截器)進行處理。

如果你想設定一個 Callback[] 數組去處理不同的方法,那麼需要設定一個 CallbackFilter 篩選器,用于選擇具體的方法使用數組中的哪個 Callback 去處理

JDK 動态代理和 CGLIB 動态代理有什麼不同?

兩者都是在 JVM 運作時期新建立一個 Class 對象,執行個體化一個代理對象,對目标類(或接口)進行代理。JDK 動态代理隻能基于接口進行代理,生成的代理類實作了這些接口;而 CGLIB 動态代理則是基于類進行代理的,生成的代理類繼承目标類,但是不能代理被 final 修飾的類,也不能重寫 final 或者 private 修飾的方法。

CGLIB 動态代理比 JDK 動态代理複雜許多,性能也相對比較差。

Spring AOP 和 AspectJ 有什麼關聯?

Spring AOP 和 AspectJ 都是 AOP 的實作架構,AspectJ 是 AOP 的完整實作,Spring AOP 則是部分實作。AspectJ 有一個很好的程式設計模型,包含了注解的方式,也包含了特殊文法。Spring 認為 AspectJ 的實作在 AOP 體系裡面是完整的,不需要在做自己的一些實作。

Spring AOP 整合 AspectJ 注解與 Spring IoC 容器,比 AspectJ 的使用更加簡單,也支援 API 和 XML 的方式進行使用。不過 Spring AOP 僅支援方法級别的 Pointcut 攔截。

為什麼 Spring AOP 底層沒有使用 AspectJ 動态代理建立代理對象?

我覺得是因為 AspectJ 的特殊文法對于 Spring 或者 Java 開發人員來說不是很友好,使用起來可能有點困難。Spring 也選擇整合 AspectJ 的注解,使用起來非常友善。

Spring AOP 中有哪些 Advice 類型?

  • Around Advice,圍繞型通知器,需要主動去觸發目标方法的執行,這樣可以在觸發的前後進行相關相關邏輯處理
  • Before Advice,前置通知器,在目标方法執行前會被調用
  • After Advice,後置通知器
    • AfterReturning,在目标方法執行後被調用(方法執行過程中出現異常不會被調用)
    • After,在目标方法執行後被調用(執行過程出現異常也會被調用)
    • AfterThrowing,執行過程中抛出異常後會被調用(如果異常類型比對)

執行順序(Spring 5.2.7 之前的版本):Around “前處理” > Before > 方法執行 > Around “後處理” > After > AfterReturning|AfterThrowing

執行順序(Spring 5.2.7 開始):Around “前處理” > Before > 方法執行 > AfterReturning|AfterThrowing > After > Around “後處理”

如下(在後續文章會進行分析,Spring 5.1.14):

死磕Spring之AOP篇 - Spring AOP常見面試題

Spring AOP 中 Advisor 接口是什麼?

Advisor 是 Advice 的一個容器接口,與 Advice 是一對一的關系,它的子接口 PointcutAdvisor 是 Pointcut 和 Advice 的容器接口,将 Pointcut 過濾 Joinpoint 的能力和 Advice 進行整合,這樣一來就将兩者進行關聯起來了。

Pointcut 提供 ClassFilter 和 MethedMatcher,分别支援篩選類和方法,通過 PointcutAdvisor 和 Advice 進行整合,可以說是形成了一個“切面”。

簡述 Spring AOP 自動代理的實作

在我們有了 Join point(連接配接點)、Pointcut(切點)、Advice(通知)以及 AspectJ(切面)後,我們應該如何将他們“織入”我們的應用呢?在 Sping AOP 中提供了自動代理的實作,底層借助 JDK 動态代理和 CGLIB 動态代理建立對象。

回顧 Spring IoC 中 Bean 的加載過程,在整個過程中,Bean 的執行個體化前和初始化後等生命周期階段都提供了擴充點,會調用相應的 BeanPostProcessor 處理器對 Bean 進行處理。當我們開啟了 AspectJ 自動代理(例如通過

@EnableAspectJAutoProxy

注解),則會往 IoC 容器中注冊一個

AbstractAutoProxyCreator

自動代理對象,該對象實作了幾種 BeanPostProcessor,例如在每個 Bean 初始化後會被調用,解析出目前 Spring 上下文中所有的 Advisor(會緩存),如果這個 Bean 需要進行代理,則會通過 JDK 動态代理或者 CGLIB 動态代理建立一個代理對象并傳回,是以得到的這個 Bean 實際上是一個代理對象。這樣一來,開發人員隻需要配置好 AspectJ 相關資訊,Spring 則會進行自動代理,和 Spring IoC 完美地整合在一起。

參考:《死磕Spring之IoC篇 - Bean 的建立過程》

請解釋 Spring @EnableAspectJAutoProxy 的原理?

使用了

@EnableAspectJAutoProxy

注解則會開啟 Spring AOP 自動代理,該注解上面有一個

@Import(AspectJAutoProxyRegistrar.class)

注解,

AspectJAutoProxyRegistrar

實作了

ImportBeanDefinitionRegistrar

這個接口,在實作的方法中會注冊一個

AnnotationAwareAspectJAutoProxyCreator

自動代理對象(如果沒有注冊的話),且将其優先級設定為最高,同時解析

@EnableAspectJAutoProxy

注解的配置并進行設定。這個自動代理對象是一個 BeanPostProcessor 處理器,在 Spring 加載一個 Bean 的過程中,如果它需要被代理,那麼會建立一個代理對象(JDK 動态代理或者 CGLIB 動态代理)。

除了注解的方式,也可以通過

<aop:aspectj-autoproxy />

标簽開啟 Spring AOP 自動代理,原理和注解相同,同樣是注冊一個自動代理對象。

@Import

注解的原理參考:《死磕Spring之IoC篇 - @Bean 等注解的實作原理》

XML 自定義标簽的原理參考:《死磕Spring之IoC篇 - 解析自定義标簽(XML 檔案)》

Spring Configuration Class CGLIB 提升與 AOP 類代理關系?

在 Spring 底層 IoC 容器初始化後,會通過

BeanDefinitionRegistryPostProcessor

對其進行後置處理,其中會有一個

ConfigurationClassPostProcessor

處理器會對

@Configuration

标注的 BeanDefinition 進行處理,進行 CGLIB 提升,這樣一來對于後續的 Spring AOP 工作就非常簡單了,因為這個 Bean 天然就是一個 CGLIB 代理。

在 Spring 5.2 開始

@Configuration

注解中新增了一個

proxyBeanMethods

屬性(預設為 true),支援顯示的配置是否進行 CGLIB 提升,畢竟進行 CGLIB 提升在啟動過程會有一定的性能損耗,且建立的代理對象會占有一定的記憶體,通過該配置進行關閉,可以減少不必要的麻煩,對 Java 雲原生有一定的提升。

@Configuration

注解的 Bean 進行 CGLIB 提升後有什麼作用呢?

舉個例子,大多數情況下,

@Configuration

Class 會通過

@Bean

注解為 Bean 定義,比如 @Bean User user() { return new User(); },那這樣是不是每次主動調用這個方法都會傳回一個新的 User 對象呢?

不是的,

@Configuration

Class 在得到 CGLIB 提升後,會設定一個攔截器專門對

@Bean

方法進行攔截處理,通過依賴查找的方式從 IoC 容器中擷取 Bean 對象,如果是單例 Bean,那麼每次都是傳回同一個對象,是以當主動調用這個方法時擷取到的都是同一個 User 對象。

參考:《死磕Spring之IoC篇 - @Bean 等注解的實作原理》

Sping AOP 應用到哪些設計模式?

如下:

  • 建立型模式:抽象工廠模式、工廠方法模式、建構器模式、單例模式、原型模式
  • 結構型模式:擴充卡模式、組合模式、裝飾器模式、享元模式、代理模式
  • 行為型模式:模闆方法模式、責任鍊模式、觀察者模式、政策模式、指令模式、狀态模式

關于每種設計模式,以及在 Spring AOP 中的應用在後續文章會進行簡單的介紹。

Spring AOP 在 Spring Framework 内部有哪些應用?

Spring 事件、Spring 事務、Spring 資料、Spring 緩存抽象、Spring 本地排程、Spring 整合、Spring 遠端調用

在後續文章進行分析。