Java面試題沖刺第八天–Spring框架2

面試題1:聊一下你對AOP的理解吧?

AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(兩外兩個:IOC-控制反轉、DI-依賴註入)。AOP主要應用於處理一些具有橫切性質的系統級服務,如日志收集事務管理安全檢查(權限校驗)緩存對象池管理等

那麼AOP是幹啥的?在我們的程序中,經常存在一些系統性的需求,比如權限校驗、記錄日志等,這些代碼會散落穿插在各個業務邏輯中,非常冗餘且不利於維護。例如下面這個示意圖:

在這裡插入圖片描述

有多少業務操作,就要寫多少重復的校驗和日志記錄代碼,這顯然是無法接受的。當然,用面向對象的思想,我們可以把這些重復的代碼抽離出來,寫成公共方法,就是下面這樣:

在這裡插入圖片描述

這樣,代碼冗餘和可維護性的問題得到瞭解決,但每個業務方法中依然要依次手動調用這些公共方法,也是略顯繁瑣。有沒有更好的方式呢?有的,那就是AOP,AOP將權限校驗、日志記錄等非業務代碼完全提取出來,與業務代碼分離,並尋找節點切入業務代碼中:

在這裡插入圖片描述

使用AOP的好處主要是降低模塊的耦合度、使系統容易擴展、提高代碼復用性

簡單地去理解,其實AOP要做三類事:

  • 在哪裡切入,也就是權限校驗等非業務操作在哪些業務代碼中執行。
  • 在什麼時候切入,是業務代碼執行前還是執行後。
  • 切入後做什麼事,比如做權限校驗、日志記錄等。

因此,AOP的體系可以梳理為下圖:

在這裡插入圖片描述

AOP的一些概念:

  • 切入點(Pointcut):決定處理如權限校驗、日志記錄等在何處切入業務代碼中(即織入切面)。切點分為execution方式和annotation方式。前者可以用路徑表達式指定哪些類織入切面,後者可以指定被哪些註解修飾的代碼織入切面。
  • 通知(Advice):我們也叫它處理(即“切面”對於某個“連接點”所產生的動作),包括處理時機和處理內容。處理內容就是要做什麼事,比如校驗權限和記錄日志。處理時機就是在什麼時機執行處理內容,分為前置處理(即業務代碼執行前)、後置處理(業務代碼執行後)等。
  • 切面(Aspect):即Pointcut和Advice。
  • 連接點(Joint point):是程序執行的一個點。例如,一個方法的執行或者一個異常的處理。在 Spring AOP 中,一個連接點總是代表一個方法執行。
  • 織入(Weaving):就是通過動態代理,在目標對象方法中執行處理內容的過程。
  • 目標對象(Target Object) :被一個或者多個切面所通知的對象。
  • AOP代理(AOP Proxy) 在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。

追問1:Advice通知的類型有哪幾種?

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

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

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

追問2:在同一個切面(Aspect)中,不同Advice的執行順序

無異常情況下:

/*************不同Advice的執行順序*****************/
1. around before advice
2. before advice
3. target method (執行代碼段)
4. around after advice
5. after advice
/*******************前五個都一樣*******************/
6. afterReturning
/*************************************************/

有異常情況下:

/*************不同Advice的執行順序*****************/
1. around before advice
2. before advice
3. target method (執行代碼段)
4. around after advice
5. after advice
/*******************前五個都一樣*******************/
6. afterThrowing:異常發生
7. java.lang.RuntimeException: 異常發生
/*************************************************/

面試題2:AspectJ AOP 和 Spring AOP 有什麼區別?

AOP實現的關鍵在於代理模式,AOP代理主要分為靜態代理動態代理

  • 靜態代理的代表為AspectJ;
  • 動態代理則以Spring AOP為代表;

Spring AOP和AspectJ有不同的目標。

  • Spring AOP旨在通過Spring IoC提供一個簡單的AOP實現,以解決編碼人員面臨的最常出現的問題。這並不是完整的AOP解決方案,它隻能用於Spring容器管理的beans
  • 另一方面,AspectJ是最原始的AOP實現技術,提供瞭玩這個的AOP解決方案。AspectJ更為健壯,相對於Spring AOP也顯得更為復雜。值得註意的是,AspectJ能夠被應用於所有的領域對象。

從原理上看:

Spring AOP

  • 基於動態代理來實現,默認如果使用接口的,用JDK提供的動態代理實現,如果是方法則使用CGLIB實現
  • Spring AOP需要依賴IOC容器來管理,並且隻能作用於Spring容器,使用純Java代碼實現
  • 在性能上,由於Spring AOP是基於動態代理來實現的,在容器啟動時需要生成代理實例,在方法調用上也會增加棧的深度,使得Spring AOP的性能不如AspectJ的那麼好

AspectJ

AspectJ屬於靜態代理(織入),通過修改代碼來實現,有如下幾個織入的時機:

  1. 編譯期織入(Compile-time weaving): 如類 A 使用 AspectJ 添加瞭一個屬性,類 B 引用瞭它,這個場景就需要編譯期的時候就進行織入,否則沒法編譯類 B。
  2. 編譯後織入(Post-compile weaving): 也就是已經生成瞭 .class 文件,或已經打成 jar 包瞭,這種情況我們需要增強處理的話,就要用到編譯後織入。
  3. 類加載後織入(Load-time weaving): 指的是在加載類的時候進行織入,要實現這個時期的織入,有幾種常見的方法。1、自定義類加載器來幹這個,這個應該是最容易想到的辦法,在被織入類加載到 JVM 前去對它進行加載,這樣就可以在加載的時候定義行為瞭。2、在 JVM 啟動的時候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。

AspectJ可以做Spring AOP幹不瞭的事情,它是AOP編程的完全解決方案,Spring AOP則致力於解決企業級開發中最普遍的AOP(方法織入)。而不是成為像AspectJ一樣的AOP方案。

因為AspectJ在實際運行之前就完成瞭織入,所以說它生成的類是沒有額外運行時開銷的

指標項 Spring AOP AspectJ
使用語言 在純 Java 中實現 使用 Java 編程語言的擴展實現
是否需要編譯 不需要單獨的編譯過程 除非設置 LTW,否則需要 AspectJ 編譯器 (ajc)
織入方式 隻能使用運行時織入 運行時織入不可用。支持編譯時、編譯後和加載時織入
織入能力 功能不強-僅支持方法級編織 更強大 – 可以編織字段、方法、構造函數、靜態初始值設定項、最終類/方法等……。
適用范圍 隻能在由 Spring 容器管理的 bean 上實現 可以在所有域對象上實現
切入點要求 僅支持方法執行切入點 支持所有切入點
代理局限 代理是由目標對象創建的, 並且切面應用在這些代理上 在執行應用程序之前 (在運行時) 前, 各方面直接在代碼中進行織入
性能 比 AspectJ 慢很多 更好的性能
復雜度 易於學習和應用 相對於 Spring AOP 來說更復雜

追問1:瞭解JDK動態代理和CGLIB動態代理的原理麼?他倆有哪些區別?

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

JDK動態代理:是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用 InvokeHandler 來處理,他有一個限制,就是它隻能為接口創建代理實例,那麼對於沒有通過接口定義業務方法的類,就要用CGLIB動態代理瞭。 CGLIB(Code Generation Library)動態代理:是一個基於ASM的字節碼生成庫,它允許我們在運行時對字節碼進行修改和動態生成。CGLIB通過繼承方式實現代理,在子類中采用方法攔截的技術攔截所有父類方法的調用並順勢織入橫切邏輯。

JDK動態代理具體實現原理:

  • 通過實現InvocationHandler接口創建自己的調用處理器;
  • 通過為Proxy類指定ClassLoader對象和一組interface來創建動態代理;
  • 通過反射機制獲取動態代理類的構造函數,其唯一參數類型就是調用處理器接口類型;
  • 通過構造函數創建動態代理類實例,構造時調用處理器對象作為參數參入;

JDK動態代理是面向接口的代理模式,如果被代理目標沒有接口那麼Spring也無能為力,Spring通過Java的反射機制生產被代理接口的新的匿名實現類,重寫瞭其中AOP的增強方法。

CGLib動態代理:

利用ASM開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。

如果目標對象實現瞭接口,默認情況下會采用JDK的動態代理實現AOP;

如果目標對象實現瞭接口,可以強制使用CGLIB實現AOP;

如果目標對象沒有實現瞭接口,必須采用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換;

3、兩者對比:

  • JDK動態代理是面向接口的。
  • CGLib動態代理是通過字節碼底層繼承要代理類來實現(對指定的類生成一個子類,覆蓋其中的方法),因此如果被代理類被final關鍵字所修飾,會失敗。

面試題3:什麼是基於Java的Spring註解配置? 給一些註解的例子

基於Java的配置,允許你在少量的Java註解的幫助下進行大部分Spring配置,而非通過XML文件。當然,也不不建議啥都用註解配置,畢竟如果修改就要動class文件很麻煩。因此建議:不會修改、極少修改的用註解,會修改的用xml配置,如AOP的配置我就用XML,因為這個需要改的場景比較多。

@Configuration 註解為例,它用來標記類可以當做一個bean的定義,被Spring IOC容器使用。另一個是通過@Bean註解,它表示此方法將要返回一個對象,作為一個bean註冊進Spring應用上下文。

@Configuration
public class StudentConfig {
    @Bean
    public StudentBean myStudent() {
        return new StudentBean();
    }

怎樣開啟註解裝配呢?

註解裝配在默認情況下是不開啟的,為瞭使用註解裝配,我們必須在Spring配置文件中配置 <context:annotation-config/>元素。

一些常見的註解:

1、@Component:

這將 java 類標記為 bean。它是任何 Spring 管理組件的通用構造型。spring 的組件掃描機制現在可以將其拾取並將其拉入應用程序環境中。

2、@Controller:

這將一個類標記為 Spring Web MVC 控制器。標有它的 Bean 會自動導入到 IoC 容器中。

3、@Service:

此註解是組件註解的特化。它不會對 @Component 註解提供任何其他行為。我們可以在服務層類中使用 @Service 而不是 @Component,因為它以更好的方式指定瞭意圖。

4、@Repository:

這個註解是具有類似用途和功能的 @Component 註解的特化。它為 DAO 提供瞭額外的好處。它將 DAO 導入 IoC 容器,並使未經檢查的異常有資格轉換為 Spring DataAccessException。

5、@Required :

這個註解表明bean的屬性必須在配置的時候設置,通過一個bean定義的顯式的屬性值或通過自動裝配,若@Required註解的bean屬性未被設置,容器將拋出BeanInitializationException

示例:

public class Employee {
    private String name;
    @Required
    public void setName(String name){
        this.name=name;
    }
    public string getName(){
        return name;
    }
}

6、@Autowired :

@Autowired默認是按照類型裝配註入的,默認情況下它要求依賴對象必須存在(可以設置它required屬性為false)。@Autowired 註解提供瞭更細粒度的控制,包括在何處以及如何完成自動裝配。它的用法和@Required一樣,修飾setter方法、構造器、屬性或者具有任意名稱和/或多個參數的PN方法。

public class Employee {
    private String name;
    @Autowired
    public void setName(String name) {
        this.name=name;
    }
    public string getName(){
        return name;
    }

@Autowired和@Resource的區別

@Autowired可用於:構造函數、成員變量、Setter方法

@Autowired和@Resource之間的區別

@Autowired:默認是按照類型裝配註入的,默認情況下它要求依賴對象必須存在(可以設置它required屬性為false)。

@Resource:默認是按照名稱來裝配註入的,隻有當找不到與名稱匹配的bean才會按照類型來裝配註入。

7、@Qualifier :

當創建多個相同類型的 bean 並希望僅使用屬性裝配其中一個 bean 時,可以使用@Qualifier 註解和 @Autowired 通過指定應該裝配哪個確切的 bean 來消除歧義。

8、@RequestMapping :

@RequestMapping 註解用於將特定 HTTP 請求方法映射到將處理相應請求的控制器中的特定類/方法。此註釋可應用於兩個級別:

  • 類級別:映射請求的URL
  • 方法級別:映射 URL 以及 HTTP 請求方法

總結

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更對內容!

推薦閱讀: