1. 程式人生 > 實用技巧 >spring的IOC(反轉控制)

spring的IOC(反轉控制)

Spring概念

1.1.1 spring 是什麼

  Spring 是分層的 Java SE/EE 應用 full-stack 輕量級開源框架,以 IoC(Inverse Of Control:反轉控制)和 AOP(Aspect Oriented Programming:面向切面程式設計)為核心,提供了展現層 Spring MVC 和持久層 Spring JDBC 以及業務層事務管理等眾多的企業級應用技術,還能整合開源世界眾多著名的第三方框架和類庫,逐漸成為使用最多的 Java EE 企業應用開源框架。

Spring 的發展歷程

 1997 年 IBM 提出了 EJB 的思想
 1998 年,SUN 制定開發標準規範 EJB1.0
 1999 年,EJB1.1 釋出
 2001 年,EJB2.0 釋出
 2003 年,EJB2.1 釋出
 2006 年,EJB3.0 釋出
 Rod Johnson(spring 之父) 
 Expert One-to-One J2EE Design and Development(2002)
 闡述了 J2EE 使用 EJB 開發設計的優點及解決方案
 Expert One-to-One J2EE Development without EJB(2004)
 闡述了 J2EE 開發不使用 EJB 的解決方式(Spring 雛形)
 2017 年 9 月份釋出了 spring 的最新版本 spring 5.0 通用版(GA)

spring 的優勢

方便解耦,簡化開發
通過 Spring 提供的 IoC 容器,可以將物件間的依賴關係交由 Spring 進行控制,避免硬編碼所造
成的過度程式耦合。使用者也不必再為單例模式類、屬性檔案解析等這些很底層的需求編寫程式碼,可
以更專注於上層的應用。
AOP 程式設計的支援
通過 Spring 的 AOP 功能,方便進行面向切面的程式設計,許多不容易用傳統 OOP 實現的功能可以通過 AOP 輕鬆應付。
宣告式事務的支援
可以將我們從單調煩悶的事務管理程式碼中解脫出來,通過宣告式方式靈活的進行事務的管理,
提高開發效率和質量。
方便程式的測試
可以用非容器依賴的程式設計方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可
做的事情。
方便整合各種優秀框架


Spring 可以降低各種框架的使用難度,提供了對各種優秀框架(Struts、Hibernate、Hessian、Quartz
等)的直接支援。
降低 JavaEE API 的使用難度
Spring 對 JavaEE API(如 JDBC、JavaMail、遠端呼叫等)進行了薄薄的封裝層,使這些 API 的
使用難度大為降低。
Java 原始碼是經典學習範例
Spring 的原始碼設計精妙、結構清晰、匠心獨用,處處體現著大師對 Java 設計模式靈活運用以
及對 Java 技術的高深造詣。它的原始碼無意是 Java 技術的最佳實踐的範例。

spring 的體系結構

IoC 的概念和作用

程式的耦合和解耦

什麼是程式的耦合

    耦合性(Coupling),也叫耦合度,是對模組間關聯程度的度量。耦合的強弱取決於模組間介面的複雜性、調
用模組的方式以及通過介面傳送資料的多少。<b>模組間的耦合度是指模組之間的依賴關係,包括控制關係、呼叫關
系、資料傳遞關係。模組間聯絡越多,其耦合性越強,同時表明其獨立性越差( 降低耦合性,可以提高其獨立
性)</b>。耦合性存在於各個領域,而非軟體設計中獨有的,但是我們只討論軟體工程中的耦合。
在軟體工程中,耦合指的就是物件之間的依賴性。<b>物件之間的耦合越高,維護成本越高。</b>因此物件的設計
應使類和構件之間的耦合最小。軟體設計中通常用耦合度和內聚度作為衡量模組獨立程度的標準。劃分模組的一個
準則就是高內聚低耦合。
它有如下分類:
(1)內容耦合。當一個模組直接修改或操作另一個模組的資料時,或一個模組不通過正常入口而轉入另
一個模組時,這樣的耦合被稱為內容耦合。內容耦合是最高程度的耦合,應該避免使用之。
(2)公共耦合。兩個或兩個以上的模組共同引用一個全域性資料項,這種耦合被稱為公共耦合。在具有大
量公共耦合的結構中,確定究竟是哪個模組給全域性變數賦了一個特定的值是十分困難的。
(3) 外部耦合 。一組模組都訪問同一全域性簡單變數而不是同一全域性資料結構,而且不是通過引數表傳
遞該全域性變數的資訊,則稱之為外部耦合。
(4) 控制耦合 。一個模組通過介面向另一個模組傳遞一個控制訊號,接受訊號的模組根據訊號值而進
行適當的動作,這種耦合被稱為控制耦合。
(5)標記耦合 。若一個模組 A 通過介面向兩個模組 B 和 C 傳遞一個公共引數,那麼稱模組 B 和 C 之間
存在一個標記耦合。
(6) 資料耦合。模組之間通過引數來傳遞資料,那麼被稱為資料耦合。資料耦合是最低的一種耦合形
式,系統中一般都存在這種型別的耦合,因為為了完成一些有意義的功能,往往需要將某些模組的輸出資料作為另
一些模組的輸入資料。
(7) 非直接耦合 。兩個模組之間沒有直接關係,它們之間的聯絡完全是通過主模組的控制和呼叫來實
現的。
總結:
    耦合是影響軟體複雜程度和設計質量的一個重要因素,在設計上我們應採用以下原則:如果模組間必須
存在耦合,就儘量使用資料耦合,少用控制耦合,限制公共耦合的範圍,儘量避免使用內容耦合。
內聚與耦合
    內聚標誌一個模組內各個元素彼此結合的緊密程度,它是資訊隱蔽和區域性化概念的自然擴充套件。內聚是從
功能角度來度量模組內的聯絡,一個好的內聚模組應當恰好做一件事。它描述的是模組內的功能聯絡。耦合是軟體
結構中各模組之間相互連線的一種度量,耦合強弱取決於模組間介面的複雜程度、進入或訪問一個模組的點以及通
過介面的資料。 程式講究的是低耦合,高內聚。<b>就是同一個模組內的各個元素之間要高度緊密,但是各個模組之
間的相互依存度卻要不那麼緊密。</b>
     內聚和耦合是密切相關的,同其他模組存在高耦合的模組意味著低內聚,而高內聚的模組意味著該模組同其他
模組之間是低耦合。在進行軟體設計時,應力爭做到高內聚,低耦合。
我們在開發中,有些依賴關係是必須的,有些依賴關係可以通過優化程式碼來解除的。
請看下面的示例程式碼:
/**
* 賬戶的業務層實現類
* 
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
}
上面的程式碼表示:
業務層呼叫持久層,並且此時業務層在依賴持久層的介面和實現類。如果此時沒有持久層實現類,編譯
將不能通過。這種編譯期依賴關係,應該在我們開發中杜絕。我們需要優化程式碼解決。
再比如:
早期我們的 JDBC 操作,註冊驅動時,我們為什麼不使用 DriverManager 的 register 方法,而是採
用 Class.forName 的方式?
public class JdbcDemo1 {
/**
* @author 黑馬程式設計師
* @Company http://www.ithiema.com
* @Version 1.0
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//1.註冊驅動
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.獲取連線
//3.獲取預處理 sql 語句物件
//4.獲取結果集
//5.遍歷結果集
} }
原因就是:
我們的類依賴了資料庫的具體驅動類(MySQL),如果這時候更換了資料庫品牌(比如 Oracle),需要
修改原始碼來重新資料庫驅動。這顯然不是我們想要的。

解決程式耦合的思路

當是我們講解 jdbc 時,是通過反射來註冊驅動的,程式碼如下:
Class.forName("com.mysql.jdbc.Driver");//此處只是一個字串
此時的好處是,我們的類中不再依賴具體的驅動類,此時就算刪除 mysql 的驅動 jar 包,依然可以編譯(運
行就不要想了,沒有驅動不可能執行成功的)。
同時,也產生了一個新的問題,mysql 驅動的全限定類名字串是在 java 類中寫死的,一旦要改還是要修改
原始碼。
解決這個問題也很簡單,使用配置檔案配置。

工廠模式解耦

在實際開發中我們可以把三層的物件都使用配置檔案配置起來,當啟動伺服器應用載入的時候,讓一個類中的

方法通過讀取配置檔案,把這些物件創建出來並存起來。在接下來的使用的時候,直接拿過來用就好了。
那麼,這個讀取配置檔案,建立和獲取三層物件的類就是工廠。

控制反轉-Inversion Of Control

上一小節解耦的思路有 2 個問題:
1、存哪去?
分析:由於我們是很多物件,肯定要找個集合來存。這時候有 Map 和 List 供選擇。
 到底選 Map 還是 List 就看我們有沒有查詢需求。有查詢需求,選 Map。
所以我們的答案就是
在應用載入時,建立一個 Map,用於存放三層物件。
我們把這個 map 稱之為容器。 2、還是沒解釋什麼是工廠?
工廠就是負責給我們從容器中獲取指定物件的類。這時候我們獲取物件的方式發生了改變。
原來:
 我們在獲取物件時,都是採用 new 的方式。是主動的。

現在:
 我們獲取物件時,同時跟工廠要,有工廠為我們查詢或者建立物件。是被動的。

這種被動接收的方式獲取物件的思想就是控制反轉,它是 spring 框架的核心之一。

明確 ioc 的作用:
  削減計算機程式的耦合(解除我們程式碼中的依賴關係)。

使用 spring 的 IOC 解決程式耦合

基於 XML 的配置(入門案例)

首先編制bean.xml配置檔案

![](https://img2020.cnblogs.com/blog/2092133/202007/2092133-20200719194205682-1877864804.png)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

讓 spring 管理資源,在配置檔案中配置 service 和 dao

<!-- bean 標籤:用於配置讓 spring 建立物件,並且存入 ioc 容器之中
 id 屬性:物件的唯一標識。
 class 屬性:指定要建立物件的全限定類名
-->
<!-- 配置 service --> 
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
</bean>
<!-- 配置 dao --> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>

測試配置是否成功

/**
* 模擬一個表現層
* @author 黑馬程式設計師
* @Company http://www.ithiema.com
* @Version 1.0
*/
public class Client {
/**
 * 使用 main 方法獲取容器測試執行
*/
public static void main(String[] args) {
//1.使用 ApplicationContext 介面,就是在獲取 spring 容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根據 bean 的 id 獲取物件
IAccountService aService = (IAccountService) ac.getBean("accountService");
System.out.println(aService);
IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
System.out.println(aDao);
} }
執行結果:

Spring 基於 XML 的 IOC 細節

** BeanFactory 和 ApplicationContext 的區別**

BeanFactory 才是 Spring 容器中的頂層介面。
ApplicationContext 是它的子介面。
BeanFactory 和 ApplicationContext 的區別:
建立物件的時間點不一樣。
ApplicationContext:只要一讀取配置檔案,預設情況下就會建立物件。
BeanFactory:什麼使用什麼時候建立物件。

ApplicationContext 介面的實現類

ClassPathXmlApplicationContext:
它是從類的根路徑下載入配置檔案 推薦使用這種
FileSystemXmlApplicationContext:
它是從磁碟路徑上載入配置檔案,配置檔案可以在磁碟的任意位置。
AnnotationConfigApplicationContext:
當我們使用註解配置容器物件時,需要使用此類來建立 spring 容器。它用來讀取註解。

IOC 中 bean 標籤和管理物件細節

bean 標籤

作用:
用於配置物件讓 spring 來建立的。
預設情況下它呼叫的是類中的無參建構函式。如果沒有無參建構函式則不能建立成功。
屬性:
id:給物件在容器中提供一個唯一標識。用於獲取物件。
class:指定類的全限定類名。用於反射建立物件。預設情況下呼叫無參建構函式。
scope:指定物件的作用範圍。
* singleton :預設值,單例的.
* prototype :多例的.
* request :WEB 專案中,Spring 建立一個 Bean 的物件,將物件存入到 request 域中.
* session :WEB 專案中,Spring 建立一個 Bean 的物件,將物件存入到 session 域中.
* global session :WEB 專案中,應用在 Portlet 環境.如果沒有 整合環境那麼
globalSession 相當於 session.
init-method:指定類中的初始化方法名稱。
destroy-method:指定類中銷燬方法名稱。

bean 的作用範圍和生命週期

單例物件:scope="singleton"
一個應用只有一個物件的例項。它的作用範圍就是整個引用。
生命週期:
物件出生:當應用載入,建立容器時,物件就被建立了。
物件活著:只要容器在,物件一直活著。
物件死亡:當應用解除安裝,銷燬容器時,物件就被銷燬了。
多例物件:scope="prototype"
每次訪問物件時,都會重新建立物件例項。
生命週期:
物件出生:當使用物件時,建立新的物件例項。
物件活著:只要物件在使用中,就一直活著。
物件死亡:當物件長時間不用時,被 java 的垃圾回收器回收了。

例項化 Bean 的三種方式

第一種方式:使用預設無參建構函式
<!--在預設情況下:
它會根據預設無參建構函式來建立類物件。如果 bean 中沒有預設無參建構函式,將會建立失敗。
--> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>

第二種方式:spring 管理靜態工廠-使用靜態工廠的方法建立物件
/**
* 模擬一個靜態工廠,建立業務層實現類
*/
public class StaticFactory {
      public static IAccountService createAccountService(){
           return new AccountServiceImpl();
      } 
}
<!-- 此種方式是:使用 StaticFactory 類中的靜態方法 createAccountService 建立物件,並存入 spring 容器
id 屬性:指定 bean 的 id,用於從容器中獲取
class 屬性:指定靜態工廠的全限定類名
factory-method 屬性:指定生產物件的靜態方法
--> <bean id="accountService"class="com.itheima.factory.StaticFactory" factory-method="createAccountService"></bean>
第三種方式:spring 管理例項工廠-使用例項工廠的方法建立物件
/**
* 模擬一個例項工廠,建立業務層實現類
* 此工廠建立物件,必須現有工廠例項物件,再呼叫方法
*/
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
} }
<!-- 此種方式是:
先把工廠的建立交給 spring 來管理。
然後在使用工廠的 bean 來呼叫裡面的方法
factory-bean 屬性:用於指定例項工廠 bean 的 id。
factory-method 屬性:用於指定例項工廠中建立物件的方法。
--> <bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean> <bean id="accountService"
 factory-bean="instancFactory"
 factory-method="createAccountService"></bean>

spring 的依賴注入

依賴注入的概念

依賴注入:Dependency Injection。它是 spring 框架核心 ioc 的具體實現。
我們的程式在編寫時,通過控制反轉,把物件的建立交給了 spring,但是程式碼中不可能出現沒有依賴的情況。
ioc 解耦只是降低他們的依賴關係,但不會消除。例如:我們的業務層仍會呼叫持久層的方法。
那這種業務層和持久層的依賴關係,在使用 spring 之後,就讓 spring 來維護了。
簡單的說,就是坐等框架把持久層物件傳入業務層,而不用我們自己去獲取。

建構函式注入

<!-- 使用建構函式的方式,給 service 中的屬性傳值
要求:
類中需要提供一個對應引數列表的建構函式。
涉及的標籤:
constructor-arg
屬性:
index:指定引數在建構函式引數列表的索引位置
type:指定引數在建構函式中的資料型別
name:指定引數在建構函式中的名稱 用這個找給誰賦值
=======上面三個都是找給誰賦值,下面兩個指的是賦什麼值的==============
value:它能賦的值是基本資料型別和 String 型別
ref:它能賦的值是其他 bean 型別,也就是說,必須得是在配置檔案中配置過的 bean
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> 
<constructor-arg name="name" value="張三"></constructor-arg>
 <constructor-arg name="age" value="18"></constructor-arg> 
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean> <bean id="now" class="java.util.Date"></bean>

set 方法注入

<!-- 通過配置檔案給 bean 中的屬性傳值:使用 set 方法的方式
涉及的標籤:
property

屬性:
name:找的是類中 set 方法後面的部分
ref:給屬性賦值是其他 bean 型別的
value:給屬性賦值是基本資料型別和 string 型別的
實際開發中,此種方式用的較多。
--> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> 
<property name="name" value="test"></property>
 <property name="age" value="21"></property>
 <property name="birthday" ref="now"></property>
</bean> <bean id="now" class="java.util.Date"></bean>

使用 p 名稱空間注入資料(本質還是呼叫 set 方法)

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation=" http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd">
 
<bean id="accountService" 
 class="com.itheima.service.impl.AccountServiceImpl4"
 p:name="test" p:age="21" p:birthday-ref="now"/>
</beans>

5 注入集合屬性

<!-- 注入集合資料
List 結構的:
array,list,set
Map 結構的
map,entry,props,prop
--> 
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 在注入集合資料時,只要結構相同,標籤可以互換 -->
<!-- 給陣列注入資料 --> <property name="myStrs"> <set><value>AAA</value> <value>BBB</value> <value>CCC</value>
</set>
</property>
<!-- 注入 list 集合資料 --> <property name="myList"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value>
</array>
</property>
<!-- 注入 set 集合資料 --> <property name="mySet"> <list><value>AAA</value> <value>BBB</value> <value>CCC</value>
</list>
</property>
<!-- 注入 Map 資料 --> <property name="myMap"> <props> <prop key="testA">aaa</prop> <prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 properties 資料 -->
<property name="myProps"> <map><entry key="testA" value="aaa"></entry> <entry key="testB"> <value>bbb</value>
</entry>
</map>
</property>
</bean>