搞定spring aop面試題,這一篇就夠了

java江南 發佈 2022-05-13T20:07:50.300875+00:00

常見問題什麼是AOPSpring AOP and AspectJ AOP 有什麼區別?AOP 有哪些實現方式?

常見問題

  • 什麼是AOP
  • Spring AOP and AspectJ AOP 有什麼區別?AOP 有哪些實現方式?
  • Jdk動態代理和CGLIB動態代理的區別
  • 解釋一下Spring AOP裡面的幾個名詞
  • 解釋下Spring在運行時通知對象
  • Spring支持什麼級別的連接點
  • Spring通知有哪些類型?
  • 什麼是切面 Aspect?
  • 有幾種不同類型的自動代理?

字節碼和機器碼

  1. 機器碼就是計算機可以直接執行,並且執行速度最快的代碼,是電腦的CPU可直接解讀的數據(計算機只認識0和1)。
  2. 字節碼(byte code)是一種包含執行程序、由一序列 OP代碼(操作碼)/數據對 組成的二進位文件。字節碼是一種中間碼,它比機器碼更抽象,需要直譯器轉譯後才能成為機器碼的中間代碼。

Java程序-編譯

源文件由編譯器編譯成字節碼(ByteCode)

一個常見的java源文件

package com.bugcodes.aop.step1;


/**
 * @author bugcoder
 * @date 2020/7/10
 */
public class Hello {


    public void sayHello() {
        System.out.println("Hello, AspectJ!");
    }


    public static void main(String[] args) {
        Hello hello = new Hello();
        hello.sayHello();
    }
}

一個class文件主要包含常量池和方法字節碼

public class com.bugcodes.aop.step1.Hello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #19.#20        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #21            // Hello, AspectJ!
   #4 = Methodref          #22.#23        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #24            // com/bugcodes/aop/step1/Hello
   #6 = Methodref          #5.#18         // com/bugcodes/aop/step1/Hello."<init>":()V
   #7 = Methodref          #5.#25         // com/bugcodes/aop/step1/Hello.sayHello:()V
   #8 = Class              #26            // java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               sayHello
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               SourceFile
  #17 = Utf8               Hello.java
  #18 = NameAndType        #9:#10         // "<init>":()V
  #19 = Class              #27            // java/lang/System
  #20 = NameAndType        #28:#29        // out:Ljava/io/PrintStream;
  #21 = Utf8               Hello, AspectJ!
  #22 = Class              #30            // java/io/PrintStream
  #23 = NameAndType        #31:#32        // println:(Ljava/lang/String;)V
  #24 = Utf8               com/bugcodes/aop/step1/Hello
  #25 = NameAndType        #13:#10        // sayHello:()V
  #26 = Utf8               java/lang/Object
  #27 = Utf8               java/lang/System
  #28 = Utf8               out
  #29 = Utf8               Ljava/io/PrintStream;
  #30 = Utf8               java/io/PrintStream
  #31 = Utf8               println
  #32 = Utf8               (Ljava/lang/String;)V
{
  public com.bugcodes.aop.step1.Hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0


  public void sayHello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello, AspectJ!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 10: 0
        line 11: 8


  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #5                  // class com/bugcodes/aop/step1/Hello
         3: dup
         4: invokespecial #6                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #7                  // Method sayHello:()V
        12: return
      LineNumberTable:
        line 14: 0
        line 15: 8
        line 16: 12
}

一個class字節碼文件的數據結構如下

java程序-運行

字節碼由java虛擬機解釋運行

從字節碼到jvm虛擬機的運行

1. java反射

常見用例

Class<?> class1 = null;
class1 = Class.forName("com.bugcodes.aop.step0.Student");
Method method = class1.getMethod("pre");
method.invoke(class1.newInstance());

method.invoke()作為反射的核心方法,其流程圖如下

  • MethodAccessor就是上面提到的所有同名method共享的一個實例,由ReflectionFactory創建。一個是Java實現的。Java實現的版本在初始化時需要較多時間,但長久來說性能較好; 另一個是 native code 實現的。native 版本正好相反,啟動時相對較快,但運行時間長了之後速度就比不過 Java 版了。這是 HotSpot 的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越 native 邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機難以分析也將其內聯,於是運行時間長了之後反而是託管版本的代碼更快些。
  • 為了權衡兩個版本的性能,Sun 的 JDK 使用了 inflation 的技巧:讓 Java 方法在被反射調用時,開頭若干次使用 native 版,等反射調用次數超過閾值(15次)時則生成一個專用的 MethodAccessor 實現類,生成其中的 invoke() 方法的字節碼,以後對該 Java 方法的反射調用就會使用 Java 版。

aop與springaop

  1. aop(Aspect Oriented Programming): 面向切面編程,通過預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術
  2. springaop是這種技術的實現

java aop的實現方式

1、靜態AOP:在編譯期,切面直接以字節 碼的形式編譯到目標字節 碼文件中。

AspectJ屬於靜態AOP,是在編譯時進行增強,會在編譯的時候將AOP邏輯織入到代碼中,需要專有的編譯器和織入器。

  • 優點:被織入的類性能不受影響。
  • 缺點:不夠靈活

2、動態AOP(JDK動態代理):在運行期,目標類加載後,為接口動態生成代理類,將切面植入到代理類中。

Java從1.3引入動態代理。實現原理是為被代理的業務接口生成代理類,將AOP邏輯寫入到代理類中,在運行時動態織入AOP,使用反射執行織入的邏輯。 主要實現方式依賴java.lang.reflect包下的InvocationHandler和Proxy類。


    • 優點:Java標準庫原生支持,使用簡單,無需引用額外的包。相對於靜態AOP更靈活。
    • 缺點:帶代理的類必須是接口,靈活性受到一些限制;使用反射會影響一些性能。

3、動態代碼字節生成:在運行期,目標類加載後,動態構建字節碼文件生成目標類的子類,將切面邏輯加入到子類中。

CGLib是動態代碼字節生成的實現,它封裝字節碼生成工具Asm,原理是在運行期間目標字節碼加載後,生成目標類的子類,將切面邏輯加入到子類中,所以使用Cglib實現AOP不需要基於接口。

  • 優點:沒有接口也可以織入,靈活性高。
  • 缺點:擴展類的實例方法為final時,則無法進行織入

4、自定義類加載器:在運行前,目標加載前,將切面邏輯加到目標字節碼中。

可以考慮Javassist來實現。Javassist 是一個編輯字節碼的框架,可以讓你很簡單地操作字節碼。它可以在運行期定義或修改Class。使用Javassist實現AOP的原理是在字節碼加載前直接修改需要切入的方法。

  • 優點:可以對絕大部分類織入。
  • 缺點:如果用到了其他類加載器,則這些類將不被織入

aspectj和springaop的區別

  1. AspectJ是一個比較牛逼的AOP框架,他可以對類的成員變量,方法進行攔截。由於 AspectJ 是 Java 語言語法和語義的擴展,所以它提供了自己的一套處理方面的關鍵字。除了包含欄位和方法之外,AspectJ 的方面聲明還包含切入點和通知成員。
  2. Spring使用了和aspectj一樣的註解,並使用Aspectj來做切入點解析和匹配(AspectJ 5讓第三方使用AspectJ的切入點解析和匹配引擎的工具API)。但是spring AOP運行時仍舊是純的spring AOP,並不依賴於Aspectj的編譯器或者織入器

2. 靜態代理

1. AspectJ的安裝和使用

1.去官網下載aspectj的jar包,關注下自己使用的jdk的版本
https://www.eclipse.org/aspectj/downloads.php
執行命令:java -jar aspectj-1.8.14.jar
2.安裝完成以後需要添加環境變量
vim ./.bash_profile


export PATH=${JAVA_HOME}/bin:$PATH:/Users/zbj/aspectj1.8/bin
export CLASSPATH=${CLASSPATH}:/Users/zbj/aspectj1.8/lib/aspectjrt.jar


使用source ./.bash_profile使剛剛修改的文件生效

public static void main(String[] args) {
        Hello hello = new Hello();
        sayHello_aroundBody1$advice(hello, TxAspect.aspectOf(), (AroundClosure)null);
}


public void ajc$around$com_bugcodes_aop_step1_TxAspect$1$f54fe983(AroundClosure ajc$aroundClosure) {
        System.out.println("開始事務...");
        ajc$around$com_bugcodes_aop_step1_TxAspect$1$f54fe983proceed(ajc$aroundClosure);
        System.out.println("事務結束...");
    }


    public static TxAspect aspectOf() {
        if (ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("com_bugcodes_aop_step1_TxAspect", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
} 

2. jdk靜態代理

/**
 * JDK靜態代理-代理類
 *
 * @author bugcoder
 * @date 2020/7/12
 */
public class JdkProxyService implements JdkService{


    /**
     * 接口
     */
    private JdkService jdkService;


    /**
     * 構造器注入
     * @param jdkService
     */
    public JdkProxyService(JdkService jdkService) {
        this.jdkService = jdkService;
    }


    /**
     * 代理方法,對普通方法進行了增強
     */
    @Override
    public void hello() {
        before();
        jdkService.hello();
        after();
    }


    /**
     * 在委託方法執行之前執行
     */
    private void before(){
        System.out.println("我是代理,在委託方法執行之前執行");
    }


    /**
     * 在委託方法執行之後執行
     */
    private void after(){
        System.out.println("我是代理,在委託方法執行之後執行");
    }
}

3. 動態代理

1. jdk動態代理

2. cglib動態代理

4. spring aop

pointcut

我們如何來識別這些表達式:

  • 正則表達式
  • Aspectj切點表達式解析器
package com.bugcodes.aop.step3.pointcut;




import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.ShadowMatch;


import java.lang.reflect.Method;


/**
 * Aspectj切點表達式解析匹配,需要引入AspectJ的jar
 *
 * @author zbj
 * @date 2020/7/15
 */
public class AspectJPointCut implements PointCut {


    /**
     * Aspectj切點解析器
     */
    private static PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();


    /**
     * 表達式
     */
    private String expression;


    /**
     * pointcut表達式對象
     */
    private PointcutExpression pointcutExpression;


    public AspectJPointCut(String expression) {
        this.expression = expression;
        pointcutExpression = pointcutParser.parsePointcutExpression(expression);
    }


    /**
     * 匹配類
     *
     * @param targetClass
     * @return
     */
    @Override
    public boolean matchClass(Class<?> targetClass) {
        return pointcutExpression.couldMatchJoinPointsInType(targetClass);
    }


    /**
     * 匹配方法
     *
     * @param method
     * @param targetClass
     * @return
     */
    @Override
    public boolean matchMethod(Method method, Class<?> targetClass) {
        ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method);
        return shadowMatch.alwaysMatches();
    }


    public String getExpression() {
        return expression;
    }
}

advice


Spring AOP 提供了5種類型的通知

  • 前置通知(Before):在目標方法被調用之前調用通知功能。
  • 後置通知(After):在目標方法完成之後調用通知,無論該方法是否發生異常。
  • 後置返回通知(After-returning):在目標方法成功執行之後調用通知。
  • 後置異常通知(After-throwing):在目標方法拋出異常後調用通知。
  • 環繞通知(Around):在被通知的方法調用之前和調用之後執行自定義的行為。

可選擇在方法前、後、異常時進行功能增強

weaving

  • 創建bean實例的時候,在bean初始化完成後,再對其進行增強
  • 對bean類及其方法挨個匹配用戶指定的切面,如果有切面匹配就是要增強的
  • 通過代理的方式來織入
  • AdvisorAutoProxyCreator是AOP增強處理的具體實現
  • 判斷bean實例是否要增強
/**
     * 在此判斷bean是否需要進行切面增強
     * @param bean
     * @param beanName
     * @return
     */
    private List<Advisor> getMatchedAdvisors(Object bean, String beanName) {
        if (CollectionUtils.isEmpty(advisors)) {
            return null;
        }
        // 得到類、類的所有方法
        Class<?> beanClass = bean.getClass();
        List<Method> allMethods = this.getAllMethodForClass(beanClass);
        // 存放匹配的Advisor的list
        List<Advisor> matchAdvisors = new ArrayList<>();
        // 遍歷Advisor來找匹配的
        for (Advisor ad : this.advisors) {
            if (ad instanceof PointCutAdvisor) {
                if (isPointcutMatchBean((PointCutAdvisor) ad, beanClass, allMethods)) {
                    matchAdvisors.add(ad);
                }
            }
        }
        return matchAdvisors;
    }




  • 在IOC容器(bean工廠)接口裡面註冊AOP織入(註冊AOP增強處理的觀察者實現)的方法
  • 增強邏輯代碼應該寫在JDK動態代理的invoke方法和cglib動態代理的intercept方法裡面
  • 增強的具體實現
        //1.獲取要對當前方法進行增強的advice
        List<Object> shouldApplyAdvices = getShouldApplyAdvices(target.getClass(), method, advisors, beanFactory);
        //2.如果有增強的advice,就責任鏈式增強執行,如果沒有直接調用
        if (CollectionUtils.isEmpty(shouldApplyAdvices)){
            return method.invoke(target, args);
        }else {
            // 有Advice就責任鏈式執行增強
            AopAdviceChainInvocation chain = new AopAdviceChainInvocation(proxy, target, method, args, shouldApplyAdvices);
            return chain.invoke();
        }


5. spring aop的使用場景

spring aop的使用場景

  • 事務處理
  • 日誌記錄
  • 性能統計
  • 安全控制
  • 異常處理

6. 設計模式

  • 代理模式
  • 觀察者模式
  • 責任鏈模式

什麼是AOP

AOP(Aspect-Oriented Programming),一般稱為面向切面編程,作為面向對象的一種補充,用於將那些與業務無關,但卻對多個對象產生影響的公共行為和邏輯,抽取並封裝為一個可重用的模塊,這個模塊被命名為「切面」(Aspect),減少系統中的重複代碼,降低了模塊間的耦合度,同時提高了系統的可維護性。可用於權限認證、日誌、事務處理等。

Spring AOP and AspectJ AOP 有什麼區別?AOP 有哪些實現方式?

Spring AOP屬於運行時增強,而AspectJ是編譯時增強。

Spring AOP基於代理(Proxying),而AspectJ基於字節碼操作(Bytecode Manipulation)

AOP實現的關鍵在於 代理模式,AOP代理主要分為靜態代理和動態代理。靜態代理的代表為AspectJ;動態代理則以Spring AOP為代表。

(1)AspectJ是靜態代理的增強,所謂靜態代理,就是AOP框架會在編譯階段生成AOP代理類,因此也稱為編譯時增強,他會在編譯階段將AspectJ(切面)織入到Java字節碼中,運行的時候就是增強之後的AOP對象。

(2)Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改字節碼,而是每次運行時在內存中臨時為方法生成一個AOP對象,這個AOP對象包含了目標對象的全部方法,並且在特定的切點做了增強處理,並回調原對象的方法。

Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理:

  • JDK動態代理只提供接口的代理,不支持類的代理。核心InvocationHandler接口和Proxy類,InvocationHandler 通過invoke()方法反射來調用目標類中的代碼,動態地將橫切邏輯和業務編織在一起;接著,Proxy利用 InvocationHandler動態創建一個符合某一接口的的實例, 生成目標類的代理對象。
  • 如果代理類沒有實現 InvocationHandler 接口,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成指定類的一個子類對象,並覆蓋其中特定方法並添加增強代碼,從而實現AOP。CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那麼它是無法使用CGLIB做動態代理的。

靜態代理與動態代理區別在於生成AOP代理對象的時機不同,相對來說AspectJ的靜態代理方式具有更好的性能,但是AspectJ需要特定的編譯器進行處理,而Spring AOP則無需特定的編譯器處理。

InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最終生成的代理實例; method 是被代理目標實例的某個具體方法; args 是被代理目標實例某個方法的具體入參, 在方法反射調用時使用。

解釋一下Spring AOP裡面的幾個名詞

(1)切面(Aspect):切面是通知和切點的結合。通知和切點共同定義了切面的全部內容。 在Spring AOP中,切面可以使用通用類(基於模式的風格) 或者在普通類中以 @AspectJ 註解來實現。

(2)連接點(Join point):指方法,在Spring AOP中,一個連接點 總是 代表一個方法的執行。 應用可能有數以千計的時機應用通知。這些時機被稱為連接點。連接點是在應用執行過程中能夠插入切面的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個欄位時。切面代碼可以利用這些點插入到應用的正常流程之中,並添加新的行為。

(3)通知(Advice):在AOP術語中,切面的工作被稱為通知。

(4)切入點(Pointcut):切點的定義會匹配通知所要織入的一個或多個連接點。我們通常使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。

(5)引入(Introduction):引入允許我們向現有類添加新方法或屬性。

(6)目標對象(Target Object): 被一個或者多個切面(aspect)所通知(advise)的對象。它通常是一個代理對象。也有人把它叫做 被通知(adviced) 對象。 既然Spring AOP是通過運行時代理實現的,這個對象永遠是一個 被代理(proxied) 對象。

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

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

Spring在運行時通知對象

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

直到應用需要被代理的bean時,Spring才創建代理對象。如果使用的是ApplicationContext的話,在ApplicationContext從BeanFactory中加載所有bean的時候,Spring才會創建被代理的對象。因為Spring運行時才創建代理對象,所以我們不需要特殊的編譯器來織入SpringAOP的切面。

Spring只支持方法級別的連接點

因為Spring基於動態代理,所以Spring只支持方法連接點。Spring缺少對欄位連接點的支持,而且它不支持構造器連接點。方法之外的連接點攔截功能,我們可以利用Aspect來補充。

Spring通知有哪些類型?

在AOP術語中,切面的工作被稱為通知,實際上是程序執行時要通過SpringAOP框架觸發的代碼段。

Spring切面可以應用5種類型的通知:

  1. 前置通知(Before):在目標方法被調用之前調用通知功能;
  2. 後置通知(After):在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼;
  3. 返回通知(After-returning ):在目標方法成功執行之後調用通知;
  4. 異常通知(After-throwing):在目標方法拋出異常後調用通知;
  5. 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為。

同一個aspect,不同advice的執行順序: ①沒有異常情況下的執行順序: around before advice before advice target method 執行 around after advice after advice afterReturning ②有異常情況下的執行順序: around before advice before advice target method 執行 around after advice after advice afterThrowing:異常發生 java.lang.RuntimeException: 異常發生

什麼是切面 Aspect?

aspect 由 pointcount 和 advice 組成,切面是通知和切點的結合。 它既包含了橫切邏輯的定義, 也包括了連接點的定義. Spring AOP 就是負責實施切面的框架, 它將切面所定義的橫切邏輯編織到切面所指定的連接點中. AOP 的工作重心在於如何將增強編織目標對象的連接點上, 這裡包含兩個工作:

  • 如何通過 pointcut 和 advice 定位到特定的 joinpoint 上
  • 如何在 advice 中編寫切面代碼.

可以簡單地認為, 使用 @Aspect 註解的類就是切面.

有幾種不同類型的自動代理?

BeanNameAutoProxyCreator

DefaultAdvisorAutoProxyCreator


小夥伴們有興趣想了解內容和更多相關學習資料的請點讚收藏+評論轉發+關注我,後面會有很多乾貨。我有一些面試題、架構、設計類資料可以說是程式設計師面試必備!所有資料都整理到網盤了,需要的話歡迎下載!私信我回復【111】即可免費獲取
































































































原創 bugCoder

連結:https://mp.weixin.qq.com/s?__biz=MjM5NzgyODc1Mw==&mid=2647754263&idx=1&sn=2a06b7968718759b19d6eae35fbce988&chksm=bef127e98986aeff899a82d2525f2d62d7e05374597c7969a11afceb48f4f4005cd9d3e0abca&token=1650286378&lang=zh_CN#rd

關鍵字: