Java開發必讀,談談對Spring IOC與AOP的理解

华为云开发者联盟發表於2024-06-07

本文分享自華為雲社群《超詳細的Java後臺開發面試題之Spring IOC與AOP》,作者:GaussDB 資料庫。

一、前言

IOC和AOP是Spring中的兩個核心的概念,下面談談對這兩個概念的理解。

二、IOC(Inverse of Control)

控制反轉,也可以稱為依賴倒置。

所謂依賴,從程式的角度看,就是比如A要呼叫B的方法,那麼A就依賴於B,反正A要用到B,則A依賴於B。所謂倒置,你必須理解如果不倒置,會怎麼著,因為A必須要有B,才可以呼叫B,如果不倒置,意思就是A主動獲取B的例項:B b = new B(),這就是最簡單的獲取B例項的方法(當然還有各種設計模式可以幫助你去獲得B的例項,比如工廠、Locator等等),然後你就可以呼叫b物件了。所以,不倒置,意味著A要主動獲取B,才能使用B;到了這裡,就應該明白了倒置的意思了。倒置就是A要呼叫B的話,A並不需要主動獲取B,而是由其它人自動將B送上門來。

形象的舉例就是:

通常情況下,假如你有一天在家裡口渴了,要喝水,那麼你可以到你小區的小賣部去,告訴他們,你需要一瓶水,然後小賣部給你一瓶水!這本來沒有太大問題,關鍵是如果小賣部很遠,那麼你必須知道:從你家如何到小賣部;小賣部裡是否有你需要的水;你還要考慮是否開著車去;等等等等,也許有太多的問題要考慮了。也就是說,為了一瓶水,你還可能需要依賴於車等等這些交通工具或別的工具,問題是不是變得複雜了?那麼如何解決這個問題呢?

解決這個問題的方法很簡單:小賣部提供送貨上門服務,凡是小賣部的會員,你只要告知小賣部你需要什麼,小賣部將主動把貨物給你送上門來!這樣一來,你只需要做兩件事情,你就可以活得更加輕鬆自在:

第一:向小賣部註冊為會員。

第二:告訴小賣部你需要什麼。

這和Spring的做法很類似!Spring就是小賣部,你就是A物件,水就是B物件

第一:在Spring中宣告一個類:A

第二:告訴Spring,A需要B

假設A是UserAction類,而B是UserService類。

<bean id="userService" class="org.leadfar.service.UserService"/>  
<bean id="documentService" class="org.leadfar.service.DocumentService"/>  
<bean id="orgService" class="org.leadfar.service.OrgService"/>  
  
<bean id="userAction" class="org.leadfar.web.UserAction">  
     <property name="userService" ref="userService"/>  
</bean>  

在Spring這個商店(工廠)中,有很多物件/服務:userService,documentService,orgService,也有很多會員:userAction等等,宣告userAction需要userService即可,Spring將透過你給它提供的通道主動把userService送上門來,因此UserAction的程式碼示例類似如下所示:

package org.leadfar.web;  
public class UserAction{  
     private UserService userService;  
     public String login(){  
          userService.valifyUser(xxx);  
     }  
     public void setUserService(UserService userService){  
          this.userService = userService;  
     }  
}  

在這段程式碼裡面,你無需自己建立UserService物件(Spring作為背後無形的手,把UserService物件透過你定義的setUserService()方法把它主動送給了你,這就叫依賴注入!),當然咯,我們也可以使用註解來注入。Spring依賴注入的實現技術是:動態代理。

三、AOP:面向切面程式設計

面向切面程式設計的目標就是分離關注點。什麼是關注點呢?就是你要做的事,就是關注點。假如你是個公子哥,沒啥人生目標,天天就是衣來伸手,飯來張口,整天只知道玩一件事!那麼,每天你一睜眼,就光想著吃完飯就去玩(你必須要做的事),但是在玩之前,你還需要穿衣服、穿鞋子、疊好被子、做飯等等等等事情,這些事情就是你的關注點,但是你只想吃飯然後玩,那麼怎麼辦呢?這些事情通通交給別人去幹。在你走到飯桌之前,有一個專門的僕人A幫你穿衣服,僕人B幫你穿鞋子,僕人C幫你疊好被子,僕人D幫你做飯,然後你就開始吃飯、去玩(這就是你一天的正事),你幹完你的正事之後,回來,然後一系列僕人又開始幫你幹這個幹那個,然後一天就結束了!

AOP的好處就是你只需要幹你的正事,其它事情別人幫你幹。也許有一天,你想裸奔,不想穿衣服,那麼你把僕人A解僱就是了!也許有一天,出門之前你還想帶點錢,那麼你再僱一個僕人E專門幫你幹取錢的活!這就是AOP。每個人各司其職,靈活組合,達到一種可配置的、可插拔的程式結構。

從Spring的角度看,AOP最大的用途就在於提供了事務管理的能力。事務管理就是一個關注點,你的正事就是去訪問資料庫,而你不想管事務(太煩),所以,Spring在你訪問資料庫之前,自動幫你開啟事務,當你訪問資料庫結束之後,自動幫你提交/回滾事務!

我們在使用Spring框架的過程中,其實就是為了使用IOC(依賴注入)和AOP(面向切面程式設計),這兩個是Spring的靈魂。主要用到的設計模式有工廠模式和代理模式。IOC就是典型的工廠模式,透過sessionfactory去注入例項;AOP就是典型的代理模式的體現。

代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類物件與一個委託類物件關聯,代理類物件本身並不真正實現服務,而是透過呼叫委託類的物件相關方法,來提供特定的服務。

Spring IoC容器是spring的核心,spring AOP是spring框架的重要組成部分。

在傳統的程式設計中,當呼叫者需要被呼叫者的協助時,通常由呼叫者來建立被呼叫者的例項。但在spring裡建立被呼叫者的工作不再由呼叫者來完成,因此控制反轉(IoC);建立被呼叫者例項的工作通常由spring容器來完成,然後注入呼叫者,因此也被稱為依賴注入(DI),依賴注入和控制反轉是同一個概念。

面向方面程式設計(AOP)是從另一個角度來考慮程式結構,透過分析程式結構的關注點來完善物件導向程式設計(OOP)。OOP將應用程式分解成各個層次的物件,而AOP將程式分解成多個切面。spring AOP只實現了方法級別的連線點,在J2EE應用中,AOP攔截到方法級別的操作就已經足夠。在spring中為了使IoC方便地使用健壯、靈活的企業服務,需要利用spring AOP實現為IoC和企業服務之間建立聯絡。

IOC:控制反轉也叫依賴注入。利用了工廠模式。

將物件交給容器管理,你只需要在spring配置檔案中配置相應的bean,以及設定相關的屬性,讓spring容器來生成類的例項物件以及管理物件。在spring容器啟動的時候,spring會把你在配置檔案中配置的bean都初始化好,然後在你需要呼叫的時候,就把它已經初始化好的那些bean分配給你需要呼叫這些bean的類(假設這個類名是A),分配的方法就是呼叫A的setter方法來注入,而不需要你在A裡面new這些bean了。

注意:面試的時候,如果有條件,畫圖,這樣更加顯得你懂了.

四、AOP(Aspect-Oriented Programming):面向切面程式設計補充說明

AOP可以說是對OOP的補充和完善。OOP引入封裝、繼承和多型性等概念來建立一種物件層次結構,用以模擬公共行為的一個集合。當我們需要為分散的物件引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌程式碼往往水平地散佈在所有物件層次中,而與它所散佈到的物件的核心功能毫無關係。在OOP設計中,它導致了大量程式碼的重複,而不利於各個模組的重用。

將程式中的交叉業務邏輯(比如安全,日誌,事務等),封裝成一個切面,然後注入到目標物件(具體業務邏輯)中去。

實現AOP的技術,主要分為兩大類:一是採用動態代理技術,利用擷取訊息的方式,對該訊息進行裝飾,以取代原有物件行為的執行;二是採用靜態織入的方式,引入特定的語法建立“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的程式碼.

簡單點解釋,比方說你想在你的biz層所有類中都加上一個列印‘你好’的功能,這時就可以用aop思想來做.你先寫個類寫個類方法,方法經實現列印‘你好’,然後Ioc這個類 ref=“biz.*”讓每個類都注入即可實現。

五、Spring中對 AOP的支援

Spring中 AOP代理由Spring IoC容器負責生成、管理,其依賴關係也由 IoC容器負責管理。因此,AOP代理可以直接使用容器中的其他 Bean例項作為目標,這種關係可由 IoC容器的依賴注入提供。Spring預設使用 Java動態代理來建立AOP代理,這樣就可以為任何介面例項建立代理了。當需要代理的類不是代理介面的時候,Spring自動會切換為使用 CGLIB代理,也可強制使用 CGLIB。

5.1 程式設計師參與部分

AOP程式設計其實是很簡單的事情。縱觀 AOP程式設計,其中需要程式設計師參與的只有三個部分:

  1. 定義普通業務元件。
  2. 定義切入點,一個切入點可能橫切多個業務元件。
  3. 定義增強處理,增強處理就是在AOP框架為普通業務元件織入的處理動作。

所以進行AOP程式設計的關鍵就是定義切入點和定義增強處理。一旦定義了合適的切入點和增強處理,AOP框架將會自動生成AOP代理,即:代理物件的方法 =增強處理 +被代理物件的方法。

5.2 Spring中使用方式

基於 Annotation的“零配置”方式。

(1)啟動註解,配置檔案applicationContext.xml

<!-- 啟動對@AspectJ註解的支援 -->       
<aop:aspectj-autoproxy/>  
<bean id="user" class="com.tgb.spring.aop.IUserImpl"/>  
<bean id="check" class="com.tgb.spring.aop.CheckUser"/>  

(2)編寫切面類

package com.tgb.spring.aop;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.After;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.annotation.Before;  
import org.aspectj.lang.annotation.Pointcut;  
  
@Aspect   
public class CheckUser {  
    @Pointcut("execution(* com.tgb.spring.aop.*.find*(..))")  
    public void checkUser(){  
      System.out.println("The System is Searching Information For You");  
    }  
   
    @Pointcut("execution(* com.tgb.spring.aop.*.add*(..))")  
    public void checkAdd(){  
        System.out.println("<< Add User >> Checking.....");  
    }  
      
    @Before("checkUser()")  
    public void beforeCheck(){  
        System.out.println(">>>>>>>> 準備搜查使用者..........");  
    }  
      
    @After("checkUser()")  
    public void afterCheck(){  
        System.out.println(">>>>>>>> 搜查使用者完畢..........");  
    }  
  
    @Before("checkAdd()")  
    public void beforeAdd(){  
        System.out.println(">>>>>>>> 增加使用者--檢查ing..........");  
    }  
      
    @After("checkAdd()")  
    public void afterAdd(){  
        System.out.println(">>> 增加使用者--檢查完畢!未發現異常!........");  
    }  
 
     //宣告環繞通知    
    @Around("checkUser()")    
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {    
        System.out.println("進入方法---環繞通知");    
        Object o = pjp.proceed();    
        System.out.println("退出方法---環繞通知");    
        return o;    
    } 
}  

(3)定義介面

package com.tgb.spring.aop;  
public interface IUser {  
    public String findUser(String username);  
    public void addUser(String username);  
    public void findAll();  
}

(4)定義實現

package com.tgb.spring.aop;  
import java.util.HashMap;  
import java.util.Map;  
public class IUserImpl implements IUser {  
    public static Map map = null;  
    public static void init(){  
        String[] list = {"Lucy", "Tom", "小明", "Smith", "Hello"};  
        Map tmp = new HashMap();  
        for(int i=0; i<list.length; i++){  
            tmp.put(list[i], list[i]+"00");  
        }  
        map = tmp;  
    }

    public void addUser(String username) {  
        init();  
        map.put(username, username+"11");  
        System.out.println("---【addUser】: "+username+" --------");  
        System.out.println("【The new List:"+map+"");  
    }  
  
    public void findAll() {  
        init();  
        System.out.println("---------------【findAll】: "+map+" ------------------");  
    }  
  
    public String findUser(String username) {  
        init();  
        String password = "沒查到該使用者";  
        if(map.containsKey(username)){  
            password = map.get(username).toString();  
        }  
        System.out.println("-----------------【findUser】-----------------");  
        System.out.println("-----------Username:"+username+"------------");  
        System.out.println("-----【Result】:"+password+"--------");  
        return password;  
    }  
}  

(5)測試

public class Test {  
    public static void main(String as[]){  
        BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");  
        IUser user = (IUser) factory.getBean("user");  
        user.findAll();  
        User u = new User();  
//      u.setUsername("Tom");  
//      user.findUser(u.getUsername());  
        /*u.setUsername("haha"); 
        user.addUser(u.getUsername());*/  
    }  
}

注:@Before是在所攔截方法執行之前執行一段邏輯。@After是在所攔截方法執行之後執行一段邏輯。@Around是可以同時在所攔截方法的前後執行一段邏輯。

以上是針對註解的方式來實現,那麼配置檔案也一樣,只需要在applicationContext.xml中新增如下程式碼:

<!--  <aop:config>  
        <aop:pointcut id="find" expression="execution(* com.tgb.spring.aop.*.find*(..))" />  
        <aop:pointcut id="add"   expression="execution(* com.tgb.spring.aop.*.add*(..))" />  
        <aop:aspect id="checkUser" ref="check">  
            <aop:before method="beforeCheck" pointcut-ref="find"/>  
            <aop:after method="afterCheck" pointcut-ref="find"/>  
        </aop:aspect>  
          
        <aop:aspect id="checkAdd" ref="check">  
            <aop:before method="beforeAdd" pointcut-ref="add"/>  
            <aop:after method="afterAdd" pointcut-ref="add"/>  
        </aop:aspect>  
</aop:config>--> 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章