1. 程式人生 > >Spring基礎之IOC&AOP&DI

Spring基礎之IOC&AOP&DI

這篇博文,主要面試Spring的時候,經常被問到的問題,順序有點雜亂,讀者們可以閱讀自己需要的部分。

  • 初認,什麼是Spring以及核心構成?

    1. Spring是一個Java企業級應用開源的開發框架,**主要解決物件的建立以及物件之間的依賴的關係。**一般在開發中,起到一個粘合/整合作用,比如和Spring/Strut,Spring/hibernate。
    2. 對於Spring的幾個核心包的瞭解:
      • spring-core,提供核心功能,IOC就是該包提供。
      • spring-context,給spring提供一個執行時環境,用以儲存各個物件的狀態。
      • spring-web,提供對WEB的支援,比如和與Strut2進行整合,以及SpringMVC模式。
      • spring-DAO,提供對JDBC操作的支援。
      • Spring-AOP,提供面向物件程式設計。
  • 認識,DI

    1. DI(Dependency Injection),即依賴注入。要理解依賴注入,筆者把這個詞語拆開,即

      • 依賴注入 = 依賴 + 注入。
    2. 什麼叫做依賴?

      • 顧名思義,物件與物件之間的依賴關係。比如,有兩個物件A和B,A裡面包含B物件的引用。講道理,在建立A的時候,我們也需要建立物件B,這樣物件A才完整。即,物件A的建立依賴物件B的建立。

      • 在是用Spring的情況下,物件StudentController物件的建立需要主動且手動去建立StudentService物件。

        public class StudentController {
        	// 依賴
        	private StudentService service;
        	public StudentController(StudentService service) {
        		this.service = service;
        	}
        }
        
    3. 什麼叫做注入?

      • StudentController裡面的StudentService物件其實是一個引用,在沒有顯示呼叫建構函式的情況下,預設是null的。所以,我們需要一個針管,在系統執行的時候向StudentService的引用注入一個真正的物件。
    4. 關於DI,N句話總結:

      • 假設有兩個物件A和B,A包含B的引用。以前,想要建立A,我們總是手動編寫程式碼來建立物件B。有了Spring的DI,我們只需要告訴Spring,A需要一個物件B,那麼在系統執行的時候,Spring會在適當的時候建立一個物件B,並注入到A中。
      • 一句話,就是如何對某個物件的屬性(物件引用)進行賦值。
    5. 在程式碼中,依賴注入有幾種方式,以及各方式的優缺點:

      • 通過seter方進行注入,XML程式碼示例。

        <bean id="userAction" class="cn.itmayiedu.UserAction">
        	<property name="userService" ref="userService"></property>
        </bean>
        
        1. 優點:其一,在描述性上要比建構函式方式注入要好一些。其二,setter方法可以被繼承,允許設定預設值,並且在IDE中可以直接使用快捷鍵生成,相對比較方便。
        2. 缺點:物件無法在構造完成後馬上進入就緒狀態。
      • 通過構造方法,有參和無參建構函式。

        1. 優點:物件在構造完成之後,即馬上進入就緒狀態,可以馬上使用
        2. 缺點:**構造引數維護較為複雜,構造方法無法被繼承。**主要體現,其一,當依賴物件過多,構造方法引數列表變長,而通過反射技術構造物件的時候,對相同型別引數的處理比較麻煩。其二,建構函式無法被繼承,無法設定預設值,所以對於非必須依賴處理,可能會需要引入多個構造方法。
      • 介面方式注入。

        1. 早已退役,並不提倡的方式。原因是,該方式會強制被注入物件(外賣場景中,客戶),實現不必要的介面,就像言多必失,帶有侵入性。
        2. 就像在外賣場景,客戶點了一份炸雞,則需要在客戶頭上戴上一頂炸雞帽子,才能領到商品,多此一舉。
      • 註解方式(工程中)。

      • 面試中被問到,回答前三種比較合適。

  • 認識IOC

    1. IOC**(Inversion of Controller)**,即控制反轉。很多書籍,將DI稱做IOC實現的一種方式,這僅是觀點不同,本質是一樣的。如果面試中DI和IOC都被問到,那麼敘述的時候,DI更多強調動作形態,而IOC更多是一種理念。

      • IOC**提倡讓別人為你服務!**就像送外賣,你只要下單,別人就會千里迢迢地給你送來。

      • 還是拿DI的程式碼塊做例子,StudentController稱為被依賴物件,即外賣中的下單客戶。而StudentService稱為被注入物件,就像外賣中的商品。那麼,這時Spring相當於一個外賣小哥,在客戶下單的時候,在規定的時候內,將商品送到客戶手中。

        在這裡插入圖片描述

  • 認識AOP

    1. AOP**(Aspect Oriented Programming),面向切面程式設計**。

      • 即,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。

      • 即,在執行時通過動態代理在不修改原始碼的前提下**,給程式動態統一增加功能的技術。**

      • 面向切面程式設計,是一種減少重複程式碼的方式。

      • 假設,一個銀行系統分為取錢、存錢功能,在執行所有模組之前都需要身份驗證。意思就是,要想進行取錢、存錢操作,必須身份驗證正確的情況下。

        1. 那麼在不使用AOP的情況下,是這樣的:在編寫業務邏輯程式碼的時候,取錢和存錢模組都會包含身份驗證的邏輯程式碼。講道理,當系統模組增多的時候,每一個模組都會包含身份驗證程式碼。一但身份驗證驗邏輯程式碼改變,那麼在維護的時候需要手動修改所有模組的程式碼,所有對於程式設計師來說很造孽。

          在這裡插入圖片描述

        2. 所以,為了不造孽,使用AOP程式設計就變成這樣:身份驗證的邏輯程式碼,就像一把40米大刀,直接一刀切下去,客戶要想進行取錢和存錢,卻不想進行身份驗證,那就要問問這把40米大刀,同不同意了。

          在這裡插入圖片描述

    2. AOP中幾個基本術語,術語賊難記,以下是尬聊:

      • 通知(Advice):
        1. 就是你想要的功能,用方法實現的功能。
        2. 比如,上文中的身份驗證邏輯,我們可以把方法寫好,這時候寫好的方法還不能稱為通知。必須要在想用的地方呼叫該方法,併產生作用後,才能稱之為通知
        3. 關鍵點:寫好邏輯程式碼 + 封裝方法 + 呼叫方法。
      • 連線點(JoinPoint):
        1. 如果將通知簡單稱為通知方法的呼叫,那麼連線點就是在什麼地方呼叫通知方法
        2. 在什麼地方呼叫通知方法,其實有很多地點,基本每一個普通方法(比如上文的取錢和存錢操作)的前後或丟擲異常的前後,都可以作為呼叫的地點。
        3. 簡單說,一個類中,有幾個方法,那麼每個方法都可以稱連線點。因為,Spring中只支援方法連線點。
      • 切入點(PointCut):
        1. 如果連線點可以簡單理解,一個類中的方法個數。那麼,切入點就是篩選過的連線點。
        2. 為什麼要篩選?因為,我並不想讓所有的方法,都作為通知的地點。比如,一個系統有取錢、存錢、查詢等方法。那麼,查詢是不需要進行通知(身份驗證)的。所以,切入點篩選出來的取錢、存錢方法。
      • 切面(Aspect):
        1. 講道理,在抽象定義中物件與物件之間,方法與方法之間,模組與模組之間都是一個個切面。
        2. 通俗點,切面 = 通知 + 切入點。其實,切面是一個集合,包含通知(在哪裡幹)和切入點(在哪裡乾的集合)
    3. 貼上程式碼,貌似更容易理解AOP吧:

      @Aspect // 切面
      public class AOP {
      	/**
      	 * 以下三個方法,都是連線點
      	 * 在此,筆者篩選出身份檢查連線點,進行通知。這時候,身份檢查則變成了切入點。
      	 * 通知分類:
      	 */
      	// @Before("pointCut_()")				前置通知: 目標方法之前執行
      	// @After("pointCut_()")					後置通知:目標方法之後執行(始終執行)
      	// @AfterReturning("pointCut_()")		    返回後通知: 執行方法結束前執行(異常不執行)
      	// @AfterThrowing("pointCut_()")			異常通知:  出現異常時候執行
      	// @Around("pointCut_()")				環繞通知: 環繞目標方法執行
      	@Before("execution(* com.xx.xx.saveMoney(..))")
      	public void check() {
      		System.out.println("身份檢查");
      	}
      	// 連線點
      	public void log() {
      		System.out.println("日誌記錄");
      	}
      	//連線點
      	public void handler() {
      		System.out.println("資源處理");
      	}
      }