深入理解Spring以及IOC與AOP

一、Spring體系

在這裏插入圖片描述

  • Container:
    spring-Core 、spring-beans 包含了框架的核心實現,包括IOC依懶注入等特性
    spring-context 在spring-core 基礎上構建它提供一種框架方式訪問對象的方法

  • WEB
    spring-web 提供了基本的面向WEB的功能,多文件上傳、使用Servlet監聽器的IOC容器初始化。一個面向WEB的應用層上下文
    web-mvc: 包含MVC 和rest 服務相關組件

  • AOP
    spring-aop 提供了面向切面編程的豐富支持
    spring-aspects 提供對AspectJ的支持,以便可以方便的將面向方面的功能集成進IDE中,比如Eclipse AJDT。
    instrumentation 提供對javaagent 的支持和類加載器
    instrumentation-tomcat 專門針對tomcat 進行類轉換與加載管理

  • DATA
    spring-jdbc: 提供了一個JDBC抽象層
    spring-tx: 編程式和聲明式事物管理
    spring-orm:
    spring-oxm:
    spring-jms:
    spring-redis:

二、Spring IOC 源碼剖析

在這裏插入圖片描述

2.1、IOC容器組成邏輯與體系結構

UserServiceImpl:

package com.maltose.studyproject.service.Impl;

import com.maltose.studyproject.dao.UserDao;
import com.maltose.studyproject.service.UserService;

/**
 * @Author: sgw
 * @Date 2019/10/27 18:41
 * @Description: service實現類
 **/
public class UserServiceImpl implements UserService {
   private UserDao userDao;

   public UserServiceImpl(){
       System.out.println("創建Bean");
   }

    @Override
    public String getUser() {
        return "獲取user";
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

Bean的創建、存儲、獲取、依賴

package com.maltose.studyproject.myTest;

import com.maltose.studyproject.dao.impl.UserDaoImpl;
import com.maltose.studyproject.service.Impl.UserServiceImpl;

import org.junit.Test;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;

/**
 * @Author: sgw
 * @Date 2019/10/27 18:48
 * @Description: TODO
 **/
public class BeanFactoryTest {
    /**
     * 1、Bean的創建
     */
    @Test
    public void createBeanTest() {

        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        UserServiceImpl bean = factory.createBean(UserServiceImpl.class);
        System.out.println(bean);
    }

    /**
     * 2、Bean的存儲與獲取
     */
    @Test
    public void storeBeanTest() {

        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        //Bean的存儲
        factory.registerSingleton("service", new UserServiceImpl());
        //bean的獲取
        factory.getBean("service");
    }

    /**
     * 3、Bean的依賴(UserServiceImpl裏依賴了UserDao,這裏自動完成UserDao依賴的檢測與注入)
     */
    @Test
    public void dependesBeanTest() {

        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        //Bean的存儲
        factory.registerSingleton("dao", new UserDaoImpl());
        /**
         * 參數二:根據什麼進行匹配(名字,類型),這裏是根據類型
         * 參數三:true--自動完成依賴的檢測與注入  false--不檢測依賴
         */
        UserServiceImpl bean = (UserServiceImpl) factory.createBean(UserServiceImpl.class, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
        System.out.println(bean);
    }
}

三、Spring IOC詳細介紹

3.1、IOC理論背景

我們都知道,在採用面向對象方法設計的軟件系統中,它的底層實現都是由N個對象組成的,所有的對象通過彼此的合作,最終實現系統的業務邏輯。下圖中,每一個齒輪就相當於一個Object
在這裏插入圖片描述
圖1:不採用IOC的軟件系統裏對象之間互相耦合

如果我們打開機械式手錶的後蓋,就會看到與上面類似的情形,各個齒輪分別帶動時針、分針和秒針順時針旋轉,從而在錶盤上產生正確的時間。圖1中描述的就是這樣的一個齒輪組,它擁有多個獨立的齒輪,這些齒輪相互齧合在一起,協同工作,共同完成某項任務。我們可以看到,在這樣的齒輪組中,如果有一個齒輪出了問題,就可能會影響到整個齒輪組的正常運轉。
齒輪組中齒輪之間的齧合關係,與軟件系統中對象之間的耦合關係非常相似。對象之間的耦合關係是無法避免的,也是必要的,這是協同工作的基礎。現在,伴隨着工業級應用的規模越來越龐大,對象之間的依賴關係也越來越複雜,經常會出現對象之間的多重依賴性關係,因此,架構師和設計師對於系統的分析和設計,將面臨更大的挑戰。對象之間耦合度過高的系統,必然會出現牽一髮而動全身的情形。
在這裏插入圖片描述
圖2:對象之間的複雜依賴關係
耦合關係不僅會出現在對象與對象之間,也會出現在軟件系統的各模塊之間,以及軟件系統和硬件系統之間。如何降低系統之間、模塊之間和對象之間的耦合度,是軟件工程永遠追求的目標之一。爲了解決對象之間的耦合度過高的問題,軟件專家Michael Mattson提出了IOC理論,用來實現對象之間的“解耦”,目前這個理論已經被成功地應用到實踐當中,很多的J2EE項目均採用了IOC框架產品Spring。

3.2、什麼是控制反轉(IOC)

IOC是Inversion of Control的縮寫,多數書籍翻譯成“控制反轉”,還有些書籍翻譯成爲“控制反向”或者“控制倒置”。
1996年,Michael Mattson在一篇有關探討面向對象框架的文章中,首先提出了IOC 這個概念。對於面向對象設計及編程的基本思想,前面我們已經講了很多了,不再贅述,簡單來說就是把複雜系統分解成相互合作的對象,這些對象類通過封裝以後,內部實現對外部是透明的,從而降低了解決問題的複雜度,而且可以靈活地被重用和擴展。IOC理論提出的觀點大體是這樣的:藉助於“第三方”實現具有依賴關係的對象之間的解耦,如下圖:
在這裏插入圖片描述
圖3:IOC解耦原理

可以看到了,由於引進了中間位置的“第三方”,也就是IOC容器,使得A、B、C、D這4個對象沒有了耦合關係,齒輪之間的傳動全部依靠“第三方”了,所有對象的控制權全部上繳給“第三方”IOC容器,所以,IOC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有對象粘合在一起發揮作用,如果沒有這個“粘合劑”,對象與對象之間會彼此失去聯繫,這就是有人把IOC容器比喻成“粘合劑”的由來。
我們再來做個試驗:把上圖中間的IOC容器拿掉,然後再來看看這套系統:
在這裏插入圖片描述
圖4:拿掉IoC容器後的系統
我們現在看到的畫面,就是我們要實現整個系統所需要完成的全部內容。這時候,A、B、C、D這4個對象之間已經沒有了耦合關係,彼此毫無聯繫,這樣的話,當你在實現A的時候,根本無須再去考慮B、C和D了,對象之間的依賴關係已經降低到了最低程度。所以,如果真能實現IOC容器,對於系統開發而言,這將是一件多麼美好的事情,參與開發的每一成員只要實現自己的類就可以了,跟別人沒有任何關係!
我們再來看看,控制反轉(IOC)到底爲什麼要起這麼個名字?我們來對比一下:
軟件系統在沒有引入IOC容器之前,如圖1所示,對象A依賴於對象B,那麼對象A在初始化或者運行到某一點的時候,自己必須主動去創建對象B或者使用已經創建的對象B。無論是創建還是使用對象B,控制權都在自己手上。
軟件系統在引入IOC容器之後,這種情形就完全改變了,如圖3所示,由於IOC容器的加入,對象A與對象B之間失去了直接聯繫,所以,當對象A運行到需要對象B的時候,IOC容器會主動創建一個對象B注入到對象A需要的地方。
通過前後的對比,我們不難看出來:對象A獲得依賴對象B的過程,由主動行爲變爲了被動行爲,控制權顛倒過來了,這就是“控制反轉”這個名稱的由來。

3.3、IOC的別名:依賴注入

2004年,Martin Fowler探討了同一個問題,既然IOC是控制反轉,那麼到底是“哪些方面的控制被反轉了呢?”,經過詳細地分析和論證後,他得出了答案:“獲得依賴對象的過程被反轉了”。控制被反轉之後,獲得依賴對象的過程由自身管理變爲了由IOC容器主動注入。於是,他給“控制反轉”取了一個更合適的名字叫做“依賴注入(Dependency Injection)”。他的這個答案,實際上給出了實現IOC的方法:注入。所謂依賴注入,就是由IOC容器在運行期間,動態地將某種依賴關係注入到對象之中。

所以,依賴注入(DI)和控制反轉(IOC)是從不同的角度的描述的同一件事情,就是指通過引入IOC容器,利用依賴關係注入的方式,實現對象之間的解耦。
我們舉一個生活中的例子,來幫助理解依賴注入的過程。大家對USB接口和USB設備應該都很熟悉吧,USB爲我們使用電腦提供了很大的方便,現在有很多的外部設備都支持USB接口。
在這裏插入圖片描述
圖5:USB接口和USB設備

現在,我們利用電腦主機和USB接口來實現一個任務:從外部USB設備讀取一個文件。
電腦主機讀取文件的時候,它一點也不會關心USB接口上連接的是什麼外部設備,而且它確實也無須知道。它的任務就是讀取USB接口,掛接的外部設備只要符合USB接口標準即可。所以,如果我給電腦主機連接上一個U盤,那麼主機就從U盤上讀取文件;如果我給電腦主機連接上一個外置硬盤,那麼電腦主機就從外置硬盤上讀取文件。掛接外部設備的權力由我("我"就相當於IOC容器)作主,即控制權歸,至於USB接口掛接的是什麼設備,電腦主機是決定不了,它只能被動的接受。電腦主機需要外部設備的時候,根本不用它告訴就會主動幫它掛上它想要的外部設備,你看的服務是多麼的到位。這就是我們生活中常見的一個依賴注入的例子。在這個過程中,就起到了IOC容器的作用。
通過這個例子,依賴注入的思路已經非常清楚:當電腦主機讀取文件的時候,就把它所要依賴的外部設備,幫他掛接上。整個外部設備註入的過程和一個被依賴的對象在系統運行時被注入另外一個對象內部的過程完全一樣。
我們把依賴注入應用到軟件系統中,再來描述一下這個過程:
對象A依賴於對象B,當對象 A需要用到對象B的時候,IOC容器就會立即創建一個對象B送給對象A。IOC容器就是一個對象製造工廠,你需要什麼,它會給你送去,你直接使用就行了,而再也不用去關心你所用的東西是如何製成的,也不用關心最後是怎麼被銷燬的,這一切全部由IOC容器包辦。
在傳統的實現中,由程序內部代碼來控制組件之間的關係。我們經常使用new關鍵字來實現兩個組件之間關係的組合,這種實現方式會造成組件之間耦合。IOC很好地解決了該問題,它將實現組件間關係從程序內部提到外部容器,也就是說由容器在運行期將組件間的某種依賴關係動態注入組件中。

3.4、IOC帶來的好處

我們還是從USB的例子說起,使用USB外部設備比使用內置硬盤,到底帶來什麼好處?
第一、USB設備作爲電腦主機的外部設備,在插入主機之前,與電腦主機沒有任何的關係,只有被我們連接在一起之後,兩者才發生聯繫,具有相關性。所以,無論兩者中的任何一方出現什麼的問題,都不會影響另一方的運行。這種特性體現在軟件工程中,就是可維護性比較好,非常便於進行單元測試,便於調試程序和診斷故障。代碼中的每一個Class都可以單獨測試,彼此之間互不影響,只要保證自身的功能無誤即可,這就是組件之間低耦合或者無耦合帶來的好處。
第二、USB設備和電腦主機的之間無關性,還帶來了另外一個好處,生產USB設備的廠商和生產電腦主機的廠商完全可以是互不相干的人,各幹各事,他們之間唯一需要遵守的就是USB接口標準。這種特性體現在軟件開發過程中,好處可是太大了。每個開發團隊的成員都只需要關心實現自身的業務邏輯,完全不用去關心其它的人工作進展,因爲你的任務跟別人沒有任何關係,你的任務可以單獨測試,你的任務也不用依賴於別人的組件,再也不用扯不清責任了。所以,在一個大中型項目中,團隊成員分工明確、責任明晰,很容易將一個大的任務劃分爲細小的任務,開發效率和產品質量必將得到大幅度的提高。
第三、同一個USB外部設備可以插接到任何支持USB的設備,可以插接到電腦主機,也可以插接到DV機,USB外部設備可以被反覆利用。在軟件工程中,這種特性就是可複用性好,我們可以把具有普遍性的常用組件獨立出來,反覆利用到項目中的其它部分,或者是其它項目,當然這也是面向對象的基本特徵。顯然,IOC不僅更好地貫徹了這個原則,提高了模塊的可複用性。符合接口標準的實現,都可以插接到支持此標準的模塊中。
第四、同USB外部設備一樣,模塊具有熱插拔特性。IOC生成對象的方式轉爲外置方式,也就是把對象生成放在配置文件裏進行定義,這樣,當我們更換一個實現子類將會變得很簡單,只要修改配置文件就可以了,完全具有熱插撥的特性。
以上幾點好處,難道還不足以打動我們,讓我們在項目開發過程中使用IOC框架嗎?

3.5、IOC容器的技術剖析

IOC中最基本的技術就是“反射(Reflection)”編程。有關反射的概念和用法,大家應該都很清楚,通俗來講就是根據給出的類名(字符串方式)來動態地生成對象。這種編程方式可以讓對象在生成時才決定到底是哪一種對象。反射的應用是很廣泛的,很多的成熟的框架,比如象Java中的Hibernate、Spring框架,.Net中 NHibernate、Spring.Net框架都是把“反射”做爲最基本的技術手段。
反射技術其實很早就出現了,但一直被忽略,沒有被進一步的利用。當時的反射編程方式相對於正常的對象生成方式要慢至少得10倍。現在的反射技術經過改良優化,已經非常成熟,反射方式生成對象和通常對象生成方式,速度已經相差不大了,大約爲1-2倍的差距。
我們可以把IOC容器的工作模式看做是工廠模式的昇華,可以把IOC容器看作是一個工廠,這個工廠裏要生產的對象都在配置文件中給出定義,然後利用編程語言的的反射編程,根據配置文件中給出的類名生成相應的對象。從實現來看,IOC是把以前在工廠方法裏寫死的對象生成代碼,改變爲由配置文件來定義,也就是把工廠和對象生成這兩者獨立分隔開來,目的就是提高靈活性和可維護性。

3.6、使用IOC框架注意事項

使用IOC框架產品能夠給我們的開發過程帶來很大的好處,但是也要充分認識引入IOC框架的缺點,做到心中有數,杜絕濫用框架。

  1. 軟件系統中由於引入了第三方IOC容器,生成對象的步驟變得有些複雜,本來是兩者之間的事情,又憑空多出一道手續,所以,我們在剛開始使用IOC框架的時候,會感覺系統變得不太直觀。所以,引入了一個全新的框架,就會增加團隊成員學習和認識的培訓成本,並且在以後的運行維護中,還得讓新加入者具備同樣的知識體系。
  2. 由於IOC容器生成對象是通過反射方式,在運行效率上有一定的損耗。如果你要追求運行效率的話,就必須對此進行權衡。
  3. 具體到IOC框架產品(比如:Spring)來講,需要進行大量的配製工作,比較繁瑣,對於一些小的項目而言,客觀上也可能加大一些工作成本。
  4. IOC框架產品本身的成熟度需要進行評估,如果引入一個不成熟的IOC框架產品,那麼會影響到整個項目,所以這也是一個隱性的風險。

我們大體可以得出這樣的結論:
一些工作量不大的項目或者產品,不太適合使用IOC框架產品。另外,如果團隊成員的知識能力欠缺,對於IOC框架產品缺乏深入的理解,也不要貿然引入。最後,特別強調運行效率的項目或者產品,也不太適合引入IOC框架產品,象WEB2.0網站就是這種情況。

該文章大量引用了這篇文章的內容:
Spring的IOC原理[通俗解釋一下]

四、Spring AOP詳細介紹

4.1、AOP介紹

什麼時候會出現面向切面編程的需求?按照軟件重構的思想,如果多個類中出現重複的代碼,就應該考慮定義一個共同的抽象類,將這些共同的代碼提取到抽象類中,比如Teacher,Student都有username,那麼就可以把username及相關的get、set方法抽取到SysUser中,這種情況,我們稱爲縱向抽取。
但是如果,我們的情況是以下情況,又該怎麼辦? 給所有的類方法添加性能檢測,事務控制,該怎麼抽取?AOP就是希望將這些分散在各個業務邏輯代碼中的相同代碼,通過橫向切割的方式抽取到一個獨立的模塊中,讓業務邏輯類依然保存最初的單純。抽取出來簡單,難點就是如何將這些獨立的邏輯融合到業務邏輯中,完成跟原來一樣的業務邏輯,這就是AOP解決的主要問題。

AOP就是在某一個類或方法執行前後打個標記,聲明在執行到這裏之前要先執行什麼,執行完這裏之後要接着執行什麼。

接下來,通過案例來具體瞭解
我們以數據庫的操作爲例來說明:

  1. 獲取連接對象
  2. 執行SQL(核心業務代碼)
  3. 如果有異常,回滾事務,無異常則提交事務
  4. 關閉連接
    上述的幾個部署,“2”是核心業務代碼,其他都是非核心業務代碼,但是我們又必須寫,而面向切面編程就是爲了解決這樣的問題,將這些非核心業務代碼進行抽離,這樣開發者只需要關注“核心業務代碼”即可。 這樣開發效率自然提高。

在這裏插入圖片描述

4.2、AOP術語

  • 連接點(Joinpoint) 程序執行的某個特定位置,如某個方法調用前,調用後,方法拋出異常後,這些代碼中的特定點稱爲連接點。簡單來說,就是在哪加入你的邏輯增強
    連接點表示具體要攔截的方法,上面切點是定義一個範圍,而連接點是具體到某個方法
  • 切點(PointCut) 每個程序的連接點有多個,如何定位到某個感興趣的連接點,就需要通過切點來定位。比如,連接點–數據庫的記錄,切點–查詢條件
    切點用於來限定Spring-AOP啓動的範圍,通常我們採用表達式的方式來設置,所以關鍵詞是範圍
  • 增強(Advice) 增強是織入到目標類連接點上的一段程序代碼。在Spring中,像BeforeAdvice等還帶有方位信息
    通知是直譯過來的結果,我個人感覺叫做“業務增強”更合適 對照代碼就是攔截器定義的相關方法,通知分爲如下幾種:
    前置通知(before):在執行業務代碼前做些操作,比如獲取連接對象
    後置通知(after):在執行業務代碼後做些操作,無論是否發生異常,它都會執行,比如關閉連接對象
    異常通知(afterThrowing):在執行業務代碼後出現異常,需要做的操作,比如回滾事務
    返回通知(afterReturning),在執行業務代碼後無異常,會執行的操作
    環繞通知(around),這個目前跟我們談論的事務沒有對應的操作,所以暫時不談
  • 目標對象(Target) 需要被加強的業務對象
  • 織入(Weaving) 織入就是將增強添加到對目標類具體連接點上的過程。
    織入是一個形象的說法,具體來說,就是生成代理對象並將切面內容融入到業務流程的過程。
  • 代理類(Proxy) 一個類被AOP織入增強後,就產生了一個代理類。
  • 切面(Aspect) 切面由切點和增強組成,它既包括了橫切邏輯的定義,也包括了連接點的定義,SpringAOP就是將切面所定義的橫切邏輯織入到切面所制定的連接點中。
    比如上文討論的數據庫事務,這個數據庫事務代碼貫穿了我們的整個代碼,我們就可以這個叫做切面。 SpringAOP將切面定義的內容織入到我們的代碼中,從而實現前後的控制邏輯。 比如我們常寫的攔截器Interceptor,這就是一個切面類
    在這裏插入圖片描述
    注意點:
  1. AOP編程不是Spring獨有的,Spring只是支持AOP編程的框架之一,這一點非常重要,切勿搞反了關係。
  2. AOP分兩類,一類可以對方法的參數進行攔截,一類是對方法進行攔截,SpringAOP屬於後者,所以Spring的AOP是屬於方法級的

Spring AOP實現-基於註解的方式

@使用Asoect註解創建切面類
在這裏插入圖片描述

  • @Before: 標識一個前置增強方法,相當於BeforeAdvice的功能.
  • @After: final增強,不管是拋出異常或者正常退出都會執行.
  • @AfterReturning: 後置增強,似於AfterReturningAdvice, 方法正常退出時執行.
  • @AfterThrowing: 異常拋出增強,相當於ThrowsAdvice.
  • @Around: 環繞增強,相當於MethodInterceptor.

execution:用於匹配方法執行的連接點
eg:
任意公共方法的執行

execution(public * *(..))

任何一個以“set”開始的方法的執行:

execution(* set*(..))

AccountService 接口的任意方法的執行:

execution(* com.xyz.service.AccountService.*(..))

定義在service包裏的任意方法的執行:

 execution(* com.xyz.service.*.*(..))

定義在service包和所有子包裏的任意類的任意方法的執行:

execution(* com.xyz.service..*.*(..))
第一個表示匹配任意的方法返回值, …(兩個點)表示零個或多個,第一個…表示service包及其子包,第二個表示所有類, 
第三個*表示所有方法,第二個…表示方法的任意參數個數

定義在pointcutexp包和所有子包裏的JoinPointObjP2類的任意方法的執行:execution(*com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")
pointcutexp包裏的任意類:

within(com.test.spring.aop.pointcutexp.*)

pointcutexp包和所有子包裏的任意類:

within(com.test.spring.aop.pointcutexp..*)

實現了Intf接口的所有類,如果Intf不是接口,限定Intf單個類:

this(com.test.spring.aop.pointcutexp.Intf)

當一個實現了接口的類被AOP的時候,用getBean方法必須cast爲接口類型,不能爲該類的類型
帶有@Transactional標註的所有類的任意方法: @within(org.springframework.transaction.annotation.Transactional) @target(org.springframework.transaction.annotation.Transactional)
帶有@Transactional標註的任意方法:
@annotation(org.springframework.transaction.annotation.Transactional)
@within和@target針對類的註解,@annotation是針對方法的註解
參數帶有@Transactional標註的方法:@args(org.springframework.transaction.annotation.Transactional)
參數爲String類型(運行是決定)的方法: args(String)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章