理解SpringIOC和SpringAOP(6.16)

關於SpringIOC和AOP之前也看了很多博客,但是總感覺始終沒有一個很清楚的認識,現在對之前看到的一些博客做一個小總結以及自己的一點理解。

一、Spring IOC(底層實現:反射)

1、SpringIOC是什麼?

即“控制反轉”,控制反轉另外一種說法叫DI,即依賴注入,是利用反射機制,不是什麼技術,而是一種設計思想。在Java開發中,IOC意味着將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。通俗點說許多應用都是通過彼此間的相互合作來實現業務邏輯的,如類A要調用類B的方法,以前我們都是在類A中,通過自身new一個類B,然後再調用類B的方法,現在我們把new類B的事情交給Spring來做,在我們調用的時候,容器會爲我們實例化。

2、IOC 能做什麼?

IoC不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器後,把創建和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,所以對象與對象之間是鬆散耦合,這樣也方便測試,利於功能複用,更重要的是使得程序的整個體系結構變得非常靈活。

其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程序原本是老大,要獲取什麼資源都是主動出擊,但是在IoC/DI思想中,應用程序就變成被動的了,被動的等待IoC容器來創建並注入它所需要的資源了。

IoC很好的體現了面向對象設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由IoC容器幫對象找相應的依賴對象並注入,而不是由對象主動去找。

3、形象理解

IOC: 所謂IoC,對於spring框架來說,就是由spring來負責控制對象的生命週期和對象間的關係。這是什麼意思呢,舉個簡單的例子,我們是如何找女朋友的?常見的情況是,我們到處去看哪裏有長得漂亮身材又好的mm,然後打聽她們的興趣愛好、qq號、電話號、ip號、iq號………,想辦法認識她們,投其所好送其所要,然後嘿嘿……這個過程是複雜深奧的,我們必須自己設計和麪對每個環節。傳統的程序開發也是如此,在一個對象中,如果要使用另外的對象,就必須得到它(自己new一個,或者從JNDI中查詢一個),使用完之後還要將對象銷燬(比如Connection等),對象始終會和其他的接口或類藕合起來。
那麼IoC是如何做的呢?有點像通過婚介找女朋友,在我和女朋友之間引入了一個第三者:婚姻介紹所。婚介管理了很多男男女女的資料,我可以向婚介提出一個列表,告訴它我想找個什麼樣的女朋友,比如長得像李嘉欣,身材像林熙雷,唱歌像周杰倫,速度像卡洛斯,技術像齊達內之類的,然後婚介就會按照我們的要求,提供一個mm,我們只需要去和她談戀愛、結婚就行了。簡單明瞭,如果婚介給我們的人選不符合要求,我們就會拋出異常。整個過程不再由我自己控制,而是有婚介這樣一個類似容器的機構來控制。Spring所倡導的開發方式就是如此,所有的類都會在spring容器中登記,告訴spring你是個什麼東西,你需要什麼東西,然後spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的創建、銷燬都由 spring來控制,也就是說控制對象生存週期的不再是引用它的對象,而是spring。對於某個具體的對象而言,以前是它控制其他對象,現在是所有對象都被spring控制,所以這叫控制反轉。

DI(依賴注入): IoC的一個重點是在系統運行中,動態的向某個對象提供它所需要的其他對象。這一點是通過DI(Dependency Injection,依賴注入)來實現的。比如對象A需要操作數據庫,以前我們總是要在A中自己編寫代碼來獲得一個Connection對象,有了 spring我們就只需要告訴spring,A中需要一個Connection,至於這個Connection怎麼構造,何時構造,A不需要知道。在系統運行時,spring會在適當的時候製造一個Connection,然後像打針一樣,注射到A當中,這樣就完成了對各個對象之間關係的控制。A需要依賴 Connection才能正常運行,而這個Connection是由spring注入到A中的,依賴注入的名字就這麼來的。那麼DI是如何實現的呢? Java 1.3之後一個重要特徵是反射(reflection),它允許程序在運行的時候動態的生成對象、執行對象的方法、改變對象的屬性,spring就是通過反射來實現注入的。

4、實現原理

IOC容器的實現利用了反射(reflection),它允許程序在運行的時候動態的生成對象、執行對象的方法、改變對象的屬性,spring就是通過反射來實現注入的。

二、Spring AOP(底層實現:動態代理)

1、SpringAOP是什麼?

面向切面編程(AOP)就是縱向的編程。比如業務A和業務B現在需要一個相同的操作,傳統方法我們可能需要在A、B中都加入相關操作代碼,而應用AOP就可以只寫一遍代碼,A、B共用這段代碼。並且,當A、B需要增加新的操作時,可以在不改動原代碼的情況下,靈活添加新的業務邏輯實現。

2、形象理解

比如銀行系統會有一個取款流程
在這裏插入圖片描述
我們可以把方框裏的流程合爲一個,另外系統還會有一個查詢餘額流程,我們先把這兩個流程放到一起
在這裏插入圖片描述

有沒有發現,這個兩者有一個相同的驗證流程,我們先把它們圈起來再說下一步

在這裏插入圖片描述

有沒有想過可以把這個驗證用戶的代碼是提取出來,不放到主流程裏去呢,這就是AOP的作用了,有了AOP,你寫代碼時不要把這個驗證用戶步驟寫進去,即完全不考慮驗證用戶,你寫完之後,在另我一個地方,寫好驗證用戶的代碼,然後告訴Spring你要把這段代碼加到哪幾個地方,Spring就會幫你加過去,而不要你自己Copy過去,這裏還是兩個地方,如果你有多個控制流呢,這個寫代碼的方法可以大大減少你的時間,不過AOP的目的不是這樣,這只是一個“副作用”,真正目的是,你寫代碼的時候,事先只需考慮主流程,而不用考慮那些不重要的流程,懂C的都知道,良好的風格要求在函數起始處驗證參數,如果在C上可以用AOP,就可以先不管校驗參數的問題,事後使用AOP就可以隔山打牛的給所有函數一次性加入校驗代碼,而你只需要寫一次校驗代碼。不知道C的沒關係,舉一個通用的例子,經常在debug的時候要打log吧,你也可以寫好主要代碼之後,把打log的代碼寫到另一個單獨的地方,然後命令AOP把你的代碼加過去,注意AOP不會把代碼加到源文件裏,但是它會正確的影響最終的機器代碼。
現在大概明白了AOP了嗎,我們來理一下頭緒,上面那個方框像不像個平面,你可以把它當塊板子,這塊板子插入一些控制流程,這塊板子就可以當成是AOP中的一個切面。所以AOP的本質是在一系列縱向的控制流程中,把那些相同的子流程提取成一個橫向的面,這句話應該好理解吧,我們把縱向流程畫成一條直線,然把相同的部分以綠色突出,如下圖左,而AOP相當於把相同的地方連一條橫線,如下圖右,這個圖沒畫好,大家明白意思就行。

在這裏插入圖片描述

3、AOP涉及名詞

1、切面(Aspect):共有功能的實現。如日誌切面、權限切面、驗籤切面等。在實際開發中通常是一個存放共有功能實現的標準Java類。當Java類使用了@Aspect註解修飾時,就能被AOP容器識別爲切面。

2、通知(Advice):切面的具體實現。就是要給目標對象織入的事情。以目標方法爲參照點,根據放置的地方不同,可分爲前置通知(Before)、後置通知(AfterReturning)、異常通知(AfterThrowing)、最終通知(After)與環繞通知(Around)5種。在實際開發中通常是切面類中的一個方法,具體屬於哪類通知,通過方法上的註解區分。

3、連接點(JoinPoint):程序在運行過程中能夠插入切面的地點。例如,方法調用、異常拋出等。Spring只支持方法級的連接點。一個類的所有方法前、後、拋出異常時等都是連接點。

4、切入點(Pointcut):用於定義通知應該切入到哪些連接點上。不同的通知通常需要切入到不同的連接點上,這種精準的匹配是由切入點的正則表達式來定義的。比如,在上面所說的連接點的基礎上,來定義切入點。我們有一個類,類裏有10個方法,那就產生了幾十個連接點。但是我們並不想在所有方法上都織入通知,我們只想讓其中的幾個方法,在調用之前檢驗下入參是否合法,那麼就用切點來定義這幾個方法,讓切點來篩選連接點,選中我們想要的方法。切入點就是來定義哪些類裏面的哪些方法會得到通知。

5、目標對象(Target):那些即將切入切面的對象,也就是那些被通知的對象。這些對象專注業務本身的邏輯,所有的共有功能等待AOP容器的切入。

6、代理對象(Proxy):將通知應用到目標對象之後被動態創建的對象。可以簡單地理解爲,代理對象的功能等於目標對象本身業務邏輯加上共有功能。代理對象對於使用者而言是透明的,是程序運行過程中的產物。目標對象被織入共有功能後產生的對象。

7、織入(Weaving):將切面應用到目標對象從而創建一個新的代理對象的過程。這個過程可以發生在編譯時、類加載時、運行時。Spring是在運行時完成織入,運行時織入通過Java語言的反射機制與動態代理機制來動態實現。

4、實現原理: 動態代理

三、Spring 常用註解

1、@Controller:用於標註控制器層組件

2、@Service:用於標註業務層組件

3、@Component : 用於標註這是一個受 Spring 管理的組件,組件引用名稱是類名,第一個字母小寫。可以使用@Component(“beanID”) 指定組件的名稱

4、@Repository:用於標註數據訪問組件,即DAO組件

5、@Bean:方法級別的註解,主要用在@Configuration和@Component註解的類裏,@Bean註解的方法會產生一個Bean對象,該對象由Spring管理並放到IoC容器中。引用名稱是方法名,也可以用@Bean(name = “beanID”)指定組件名

6、@Scope(“prototype”):將組件的範圍設置爲原型的(即多例)。保證每一個請求有一個單獨的action來處理,避免action的線程問題。由於Spring默認是單例的,只會創建一個action對象,每次訪問都是同一個對象,容易產生併發問題,數據不安全。

7、@Autowired:默認按類型進行自動裝配。在容器查找匹配的Bean,當有且僅有一個匹配的Bean時,Spring將其注入@Autowired標註的變量中。

8、@Resource:默認按名稱進行自動裝配,當找不到與名稱匹配的Bean時會按類型裝配。

小結:簡單點說,就是,能夠明確該類是一個控制器類組件的,就用@Controller;能夠明確是一個服務類組件的,就用@Service;能夠明確該類是一個數據訪問組件的,就用@Repository;不知道他是啥或者不好區分他是啥,但是就是想讓他動態裝配的就用@Component。@Controller、@Service、@Component、@Repository都是類級別的註解,如果一個方法也想動態裝配,就用@Bean。當我們想按類型進行自動裝配時,就用@Autowired;當我們想按名稱(beanID)進行自動裝配時,就用@Resource;當我們需要根據比如配置信息等來動態裝配不同的組件時,可以getBean(“beanID”)。

四、依賴注入的方式。

Spring有4種依賴注入方式,按照實現方式分爲兩類:註解和配置文件

1、Set方法注入方式(Set方法注入是最簡單、最常用的注入方式。)

(1)註解注入

package com.obob.dao;

public class UserDao {
	public void login() {
		System.out.println("login...");
	}
}
package com.obob.service;
import org.springframework.beans.factory.annotation.Autowired;
import com.obob.dao.UserDao;

public class UserService {
	
	//註解注入(autowire註解默認使用類型注入)
	@Autowired
	private UserDao userDao;
	
	public void login() {
		userDao.login();
	}
}

(2)配置文件注入

UserDao定義不變,UserService去掉註解

package com.obob.service;
import org.springframework.beans.factory.annotation.Autowired;
import com.obob.dao.UserDao;

public class UserService {
	private UserDao userDao;
	public void login() {
		userDao.login();
	}
}

配置文件

<bean name="userService" class="com.obob.service.UserService">
	<property name="userDao" ref="userDao" />
</bean>
<bean name="userDao" class="com.obob.dao.UserDao"></bean>

2、構造器注入方式(構造方法注入是指帶有參數的構造函數注入)

(1)註解注入

package com.obob.service;

import org.springframework.beans.factory.annotation.Autowired;

import com.obob.dao.UserDao;

public class UserService {
	
	private UserDao userDao;
	
	//註解到構造方法處
	@Autowired
	public UserService(UserDao userDao) {
		this.userDao = userDao;
	}
	
	public void login() {
		userDao.login();
	}
}

(2)配置文件注入

UserService.java

package com.obob.service;

import org.springframework.beans.factory.annotation.Autowired;

import com.obob.dao.UserDao;

public class UserService {
	
	private UserDao userDao;
	
	public UserService(UserDao userDao) {
		this.userDao = userDao;
	}
	
	
	public void login() {
		userDao.login();
	}
}

配置文件

<bean name="userService" class="com.obob.service.UserService">
	<constructor-arg index="0" ref="userDao"></constructor-arg> 
</bean>
<bean name="userDao" class="com.obob.dao.UserDao"></bean>

3、 靜態工廠的方法注入(靜態工廠顧名思義,就是通過調用靜態工廠的方法來獲取自己需要的對象。爲了使用Spring的依賴注入IOC,我們不直接通過"工程類.靜態方法()"來獲取對象,而是依然通過spring注入的形式獲取。此種方式無通過註解注入)

配置文件注入

UserService.java

package com.obob.service;

import com.obob.dao.UserDao;

public class UserService {
	
	private UserDao userDao;
	
	public void login() {
		userDao.login();
	}
}

Factory.java

package com.obob;

import com.obob.dao.UserDao;

public class Factory {
	
	public static UserDao initUserDao() {
		return new UserDao();
	}
}

配置文件

<bean name="userService" class="com.obob.service.UserService">
	<property name="staticUserDao" ref="staticUserDao" />
</bean>
<bean name="staticUserDao" class="com.obob.Factory" factory-method="initUserDao"></bean>

4、實例工廠的方法注入(實例工廠的意思是獲取對象實例的方法不是靜態的,所以你需要首先new工廠類,再調用普通的實例方法。此種方式無通過註解注入)

配置文件注入

UserService.java

package com.obob.service;

import com.obob.dao.UserDao;

public class UserService {
	
	private UserDao userDao;
	
	public void login() {
		userDao.login();
	}
}

Factory.java

package com.obob;

import com.obob.dao.UserDao;

public class Factory {
	
	public UserDao initUserDao() {
		return new UserDao();
	}
}

配置文件

<bean name="userService" class="com.obob.service.UserService">
	<property name="staticUserDao" ref="staticUserDao" />
</bean>
<bean name="staticUserDao" factory-bean="factory" factory-method="initUserDao"></bean>
<bean name="factory" class="com.obob.Factory"></bean>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章