本文分享自華爲(wéi / wèi)雲社區《超詳細的(de)Java後台開發面試題之(zhī)Spring IOC與AOP-雲社區-華爲(wéi / wèi)雲》,作者:GaussDB 數據庫。
一(yī / yì /yí)、前言
IOC和(hé / huò)AOP是(shì)Spring中的(de)兩個(gè)核心的(de)概念,下面談談對這(zhè)兩個(gè)概念的(de)理解。
二、IOC(Inverse of Control)
控制反轉,也(yě)可以(yǐ)稱爲(wéi / wèi)依賴倒置。
所謂依賴,從程序的(de)角度看,就(jiù)是(shì)比如A要(yào / yāo)調用B的(de)方法,那麽A就(jiù)依賴于(yú)B,反正A要(yào / yāo)用到(dào)B,則A依賴于(yú)B。所謂倒置,你必須理解如果不(bù)倒置,會怎麽着,因爲(wéi / wèi)A必須要(yào / yāo)有B,才可以(yǐ)調用B,如果不(bù)倒置,意思就(jiù)是(shì)A主動獲取B的(de)實例:B b = new B(),這(zhè)就(jiù)是(shì)最簡單的(de)獲取B實例的(de)方法(當然還有各種設計模式可以(yǐ)幫助你去獲得B的(de)實例,比如工廠、Locator等等),然後你就(jiù)可以(yǐ)調用b對象了(le/liǎo)。所以(yǐ),不(bù)倒置,意味着A要(yào / yāo)主動獲取B,才能使用B;到(dào)了(le/liǎo)這(zhè)裏,就(jiù)應該明白了(le/liǎo)倒置的(de)意思了(le/liǎo)。倒置就(jiù)是(shì)A要(yào / yāo)調用B的(de)話,A并不(bù)需要(yào / yāo)主動獲取B,而(ér)是(shì)由其它人(rén)自動将B送上(shàng)門來(lái)。
形象的(de)舉例就(jiù)是(shì):
通常情況下,假如你有一(yī / yì /yí)天在(zài)家裏口渴了(le/liǎo),要(yào / yāo)喝水,那麽你可以(yǐ)到(dào)你小區的(de)小賣部去,告訴他(tā)們,你需要(yào / yāo)一(yī / yì /yí)瓶水,然後小賣部給你一(yī / yì /yí)瓶水!這(zhè)本來(lái)沒有太大(dà)問題,關鍵是(shì)如果小賣部很遠,那麽你必須知道(dào):從你家如何到(dào)小賣部;小賣部裏是(shì)否有你需要(yào / yāo)的(de)水;你還要(yào / yāo)考慮是(shì)否開着車去;等等等等,也(yě)許有太多的(de)問題要(yào / yāo)考慮了(le/liǎo)。也(yě)就(jiù)是(shì)說(shuō),爲(wéi / wèi)了(le/liǎo)一(yī / yì /yí)瓶水,你還可能需要(yào / yāo)依賴于(yú)車等等這(zhè)些交通工具或别的(de)工具,問題是(shì)不(bù)是(shì)變得複雜了(le/liǎo)?那麽如何解決這(zhè)個(gè)問題呢?
解決這(zhè)個(gè)問題的(de)方法很簡單:小賣部提供送貨上(shàng)門服務,凡是(shì)小賣部的(de)會員,你隻要(yào / yāo)告知小賣部你需要(yào / yāo)什麽,小賣部将主動把貨物給你送上(shàng)門來(lái)!這(zhè)樣一(yī / yì /yí)來(lái),你隻需要(yào / yāo)做兩件事情,你就(jiù)可以(yǐ)活得更加輕松自在(zài):
第一(yī / yì /yí):向小賣部注冊爲(wéi / wèi)會員。
第二:告訴小賣部你需要(yào / yāo)什麽。
這(zhè)和(hé / huò)Spring的(de)做法很類似!Spring就(jiù)是(shì)小賣部,你就(jiù)是(shì)A對象,水就(jiù)是(shì)B對象
第一(yī / yì /yí):在(zài)Spring中聲明一(yī / yì /yí)個(gè)類:A
第二:告訴Spring,A需要(yào / yāo)B
假設A是(shì)UserAction類,而(ér)B是(shì)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>在(zài)Spring這(zhè)個(gè)商店(工廠)中,有很多對象/服務:userService,documentService,orgService,也(yě)有很多會員:userAction等等,聲明userAction需要(yào / yāo)userService即可,Spring将通過你給它提供的(de)通道(dào)主動把userService送上(shàng)門來(lái),因此UserAction的(de)代碼示例類似如下所示:
package org.leadfar.web; public class UserAction{ private UserService userService; public String login(){ userService.valifyUser(xxx); } public void setUserService(UserService userService){ this.userService = userService; } }在(zài)這(zhè)段代碼裏面,你無需自己創建UserService對象(Spring作爲(wéi / wèi)背後無形的(de)手,把UserService對象通過你定義的(de)setUserService()方法把它主動送給了(le/liǎo)你,這(zhè)就(jiù)叫依賴注入!),當然咯,我們也(yě)可以(yǐ)使用注解來(lái)注入。Spring依賴注入的(de)實現技術是(shì):動态代理。
三、AOP:面向切面編程
面向切面編程的(de)目标就(jiù)是(shì)分離關注點。什麽是(shì)關注點呢?就(jiù)是(shì)你要(yào / yāo)做的(de)事,就(jiù)是(shì)關注點。假如你是(shì)個(gè)公子(zǐ)哥,沒啥人(rén)生目标,天天就(jiù)是(shì)衣來(lái)伸手,飯來(lái)張口,整天隻知道(dào)玩一(yī / yì /yí)件事!那麽,每天你一(yī / yì /yí)睜眼,就(jiù)光想着吃完飯就(jiù)去玩(你必須要(yào / yāo)做的(de)事),但是(shì)在(zài)玩之(zhī)前,你還需要(yào / yāo)穿衣服、穿鞋子(zǐ)、疊好被子(zǐ)、做飯等等等等事情,這(zhè)些事情就(jiù)是(shì)你的(de)關注點,但是(shì)你隻想吃飯然後玩,那麽怎麽辦呢?這(zhè)些事情通通交給别人(rén)去幹。在(zài)你走到(dào)飯桌之(zhī)前,有一(yī / yì /yí)個(gè)專門的(de)仆人(rén)A幫你穿衣服,仆人(rén)B幫你穿鞋子(zǐ),仆人(rén)C幫你疊好被子(zǐ),仆人(rén)D幫你做飯,然後你就(jiù)開始吃飯、去玩(這(zhè)就(jiù)是(shì)你一(yī / yì /yí)天的(de)正事),你幹完你的(de)正事之(zhī)後,回來(lái),然後一(yī / yì /yí)系列仆人(rén)又開始幫你幹這(zhè)個(gè)幹那個(gè),然後一(yī / yì /yí)天就(jiù)結束了(le/liǎo)!
AOP的(de)好處就(jiù)是(shì)你隻需要(yào / yāo)幹你的(de)正事,其它事情别人(rén)幫你幹。也(yě)許有一(yī / yì /yí)天,你想裸奔,不(bù)想穿衣服,那麽你把仆人(rén)A解雇就(jiù)是(shì)了(le/liǎo)!也(yě)許有一(yī / yì /yí)天,出(chū)門之(zhī)前你還想帶點錢,那麽你再雇一(yī / yì /yí)個(gè)仆人(rén)E專門幫你幹取錢的(de)活!這(zhè)就(jiù)是(shì)AOP。每個(gè)人(rén)各司其職,靈活組合,達到(dào)一(yī / yì /yí)種可配置的(de)、可插拔的(de)程序結構。
從Spring的(de)角度看,AOP最大(dà)的(de)用途就(jiù)在(zài)于(yú)提供了(le/liǎo)事務管理的(de)能力。事務管理就(jiù)是(shì)一(yī / yì /yí)個(gè)關注點,你的(de)正事就(jiù)是(shì)去訪問數據庫,而(ér)你不(bù)想管事務(太煩),所以(yǐ),Spring在(zài)你訪問數據庫之(zhī)前,自動幫你開啓事務,當你訪問數據庫結束之(zhī)後,自動幫你提交/回滾事務!
我們在(zài)使用Spring框架的(de)過程中,其實就(jiù)是(shì)爲(wéi / wèi)了(le/liǎo)使用IOC(依賴注入)和(hé / huò)AOP(面向切面編程),這(zhè)兩個(gè)是(shì)Spring的(de)靈魂。主要(yào / yāo)用到(dào)的(de)設計模式有工廠模式和(hé / huò)代理模式。IOC就(jiù)是(shì)典型的(de)工廠模式,通過sessionfactory去注入實例;AOP就(jiù)是(shì)典型的(de)代理模式的(de)體現。
代理模式是(shì)常用的(de)java設計模式,他(tā)的(de)特征是(shì)代理類與委托類有同樣的(de)接口,代理類主要(yào / yāo)負責爲(wéi / wèi)委托類預處理消息、過濾消息、把消息轉發給委托類,以(yǐ)及事後處理消息等。代理類與委托類之(zhī)間通常會存在(zài)關聯關系,一(yī / yì /yí)個(gè)代理類對象與一(yī / yì /yí)個(gè)委托類對象關聯,代理類對象本身并不(bù)真正實現服務,而(ér)是(shì)通過調用委托類的(de)對象相關方法,來(lái)提供特定的(de)服務。
Spring IoC容器是(shì)spring的(de)核心,spring AOP是(shì)spring框架的(de)重要(yào / yāo)組成部分。
在(zài)傳統的(de)程序設計中,當調用者需要(yào / yāo)被調用者的(de)協助時(shí),通常由調用者來(lái)創建被調用者的(de)實例。但在(zài)spring裏創建被調用者的(de)工作不(bù)再由調用者來(lái)完成,因此控制反轉(IoC);創建被調用者實例的(de)工作通常由spring容器來(lái)完成,然後注入調用者,因此也(yě)被稱爲(wéi / wèi)依賴注入(DI),依賴注入和(hé / huò)控制反轉是(shì)同一(yī / yì /yí)個(gè)概念。
面向方面編程(AOP)是(shì)從另一(yī / yì /yí)個(gè)角度來(lái)考慮程序結構,通過分析程序結構的(de)關注點來(lái)完善面向對象編程(OOP)。OOP将應用程序分解成各個(gè)層次的(de)對象,而(ér)AOP将程序分解成多個(gè)切面。spring AOP隻實現了(le/liǎo)方法級别的(de)連接點,在(zài)J2EE應用中,AOP攔截到(dào)方法級别的(de)操作就(jiù)已經足夠。在(zài)spring中爲(wéi / wèi)了(le/liǎo)使IoC方便地(dì / de)使用健壯、靈活的(de)企業服務,需要(yào / yāo)利用spring AOP實現爲(wéi / wèi)IoC和(hé / huò)企業服務之(zhī)間建立聯系。
IOC:控制反轉也(yě)叫依賴注入。利用了(le/liǎo)工廠模式。
将對象交給容器管理,你隻需要(yào / yāo)在(zài)spring配置文件中配置相應的(de)bean,以(yǐ)及設置相關的(de)屬性,讓spring容器來(lái)生成類的(de)實例對象以(yǐ)及管理對象。在(zài)spring容器啓動的(de)時(shí)候,spring會把你在(zài)配置文件中配置的(de)bean都初始化好,然後在(zài)你需要(yào / yāo)調用的(de)時(shí)候,就(jiù)把它已經初始化好的(de)那些bean分配給你需要(yào / yāo)調用這(zhè)些bean的(de)類(假設這(zhè)個(gè)類名是(shì)A),分配的(de)方法就(jiù)是(shì)調用A的(de)setter方法來(lái)注入,而(ér)不(bù)需要(yào / yāo)你在(zài)A裏面new這(zhè)些bean了(le/liǎo)。
注意:面試的(de)時(shí)候,如果有條件,畫圖,這(zhè)樣更加顯得你懂了(le/liǎo).
四、AOP(Aspect-Oriented Programming):面向切面編程補充說(shuō)明
AOP可以(yǐ)說(shuō)是(shì)對OOP的(de)補充和(hé / huò)完善。OOP引入封裝、繼承和(hé / huò)多态性等概念來(lái)建立一(yī / yì /yí)種對象層次結構,用以(yǐ)模拟公共行爲(wéi / wèi)的(de)一(yī / yì /yí)個(gè)集合。當我們需要(yào / yāo)爲(wéi / wèi)分散的(de)對象引入公共行爲(wéi / wèi)的(de)時(shí)候,OOP則顯得無能爲(wéi / wèi)力。也(yě)就(jiù)是(shì)說(shuō),OOP允許你定義從上(shàng)到(dào)下的(de)關系,但并不(bù)适合定義從左到(dào)右的(de)關系。例如日志功能。日志代碼往往水平地(dì / de)散布在(zài)所有對象層次中,而(ér)與它所散布到(dào)的(de)對象的(de)核心功能毫無關系。在(zài)OOP設計中,它導緻了(le/liǎo)大(dà)量代碼的(de)重複,而(ér)不(bù)利于(yú)各個(gè)模塊的(de)重用。
将程序中的(de)交叉業務邏輯(比如安全,日志,事務等),封裝成一(yī / yì /yí)個(gè)切面,然後注入到(dào)目标對象(具體業務邏輯)中去。
實現AOP的(de)技術,主要(yào / yāo)分爲(wéi / wèi)兩大(dà)類:一(yī / yì /yí)是(shì)采用動态代理技術,利用截取消息的(de)方式,對該消息進行裝飾,以(yǐ)取代原有對象行爲(wéi / wèi)的(de)執行;二是(shì)采用靜态織入的(de)方式,引入特定的(de)語法創建“方面”,從而(ér)使得編譯器可以(yǐ)在(zài)編譯期間織入有關“方面”的(de)代碼.
簡單點解釋,比方說(shuō)你想在(zài)你的(de)biz層所有類中都加上(shàng)一(yī / yì /yí)個(gè)打印‘你好’的(de)功能,這(zhè)時(shí)就(jiù)可以(yǐ)用aop思想來(lái)做.你先寫個(gè)類寫個(gè)類方法,方法經實現打印‘你好’,然後Ioc這(zhè)個(gè)類 ref=“biz.*”讓每個(gè)類都注入即可實現。
五、Spring中對 AOP的(de)支持
Spring中 AOP代理由Spring IoC容器負責生成、管理,其依賴關系也(yě)由 IoC容器負責管理。因此,AOP代理可以(yǐ)直接使用容器中的(de)其他(tā) Bean實例作爲(wéi / wèi)目标,這(zhè)種關系可由 IoC容器的(de)依賴注入提供。Spring默認使用 Java動态代理來(lái)創建AOP代理,這(zhè)樣就(jiù)可以(yǐ)爲(wéi / wèi)任何接口實例創建代理了(le/liǎo)。當需要(yào / yāo)代理的(de)類不(bù)是(shì)代理接口的(de)時(shí)候,Spring自動會切換爲(wéi / wèi)使用 CGLIB代理,也(yě)可強制使用 CGLIB。
5.1 程序員參與部分
AOP編程其實是(shì)很簡單的(de)事情。縱觀 AOP編程,其中需要(yào / yāo)程序員參與的(de)隻有三個(gè)部分:
定義普通業務組件。定義切入點,一(yī / yì /yí)個(gè)切入點可能橫切多個(gè)業務組件。定義增強處理,增強處理就(jiù)是(shì)在(zài)AOP框架爲(wéi / wèi)普通業務組件織入的(de)處理動作。所以(yǐ)進行AOP編程的(de)關鍵就(jiù)是(shì)定義切入點和(hé / huò)定義增強處理。一(yī / yì /yí)旦定義了(le/liǎo)合适的(de)切入點和(hé / huò)增強處理,AOP框架将會自動生成AOP代理,即:代理對象的(de)方法 =增強處理 +被代理對象的(de)方法。
5.2 Spring中使用方式
基于(yú) Annotation的(de)“零配置”方式。
(1)啓動注解,配置文件applicationContext.xml
<!-- 啓動對@AspectJ注解的(de)支持 --> <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("退出(chū)方法---環繞通知"); 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 = "沒查到(dào)該用戶"; 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是(shì)在(zài)所攔截方法執行之(zhī)前執行一(yī / yì /yí)段邏輯。@After是(shì)在(zài)所攔截方法執行之(zhī)後執行一(yī / yì /yí)段邏輯。@Around是(shì)可以(yǐ)同時(shí)在(zài)所攔截方法的(de)前後執行一(yī / yì /yí)段邏輯。
以(yǐ)上(shàng)是(shì)針對注解的(de)方式來(lái)實現,那麽配置文件也(yě)一(yī / yì /yí)樣,隻需要(yào / yāo)在(zài)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>-->關注#華爲(wéi / wèi)雲開發者聯盟# 點擊下方,第一(yī / yì /yí)時(shí)間了(le/liǎo)解華爲(wéi / wèi)雲新鮮技術~