本文分享自華為雲社群《超詳細的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程式設計,其中需要程式設計師參與的只有三個部分:
- 定義普通業務元件。
- 定義切入點,一個切入點可能橫切多個業務元件。
- 定義增強處理,增強處理就是在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>-->
點選關注,第一時間瞭解華為雲新鮮技術~