面試官:IOC 是如何實現解耦合的?

前言

回想寫過的圖書管理系統、租房系統、電影院賣票系統都是基於原生的 JavaSE、OOP,沒有用到任何框架,在層與層的關係中一個類要想獲得與其他類的聯繫主要的方式還是靠 new,這就導致層與層之間、對象與對象之間的依賴性強 “動一發而遷全身”。特別是在處理數據層和業務層之間時,由於對象沒有統一管理導致很複雜!而 Spring 中的 IOC 就很好地解決了這一問題, 降低耦合就是它的一大特色

一. 所謂耦合 (️‍)

個人建議:學習 Spring 之前要多寫原生的 OOP 項目,充分體會層之間、類之間的聯繫才能深刻理解 Spring 的特色

在項目開發中,對象之間的耦合度就是多個對象間的依賴性、關聯性。對象之間的耦合越高,維護成本越高,因此對象的設計應使類和類之間的耦合最小

當使用 Spring 框架後你不用再考慮 new 對象了,只要寫好配置文件,IOC 就會幫你做,這就降低了層與層、對象與對象之間之間的耦合度

二. Spring

Spring 是分層的 Java SE/EE 應用輕量級開源框架,以 loC(Inverse Of Control:反轉控制) 和 AOP (Aspect Oriented Programming:面向切面編程)爲內核

三. 核心 IOC 理解

IoC(Inversion of Control) 名爲控制反轉,顧名思義就是將控制權反轉

通過控制反轉,把對象的創建由原來的人爲 new 反轉成 Spring 來處理,但是代碼中不可能出現沒有依賴的情況,所以 IOC 解耦只是降低他們的依賴關係,不會消除依賴關係

1. 容器 (️‍)

容器的核心是 Bean,這個單詞譯爲——豆莢,顧名思義我們的對象都被裝在這個豆莢裏統一管理

裏面存的是各種對象 (在 xml 裏配置的bean、@repository、@service、@controller、@component) ,實際上就是抽象的(k-v)map——(id-class)

在項目啓動的時候會讀取配置文件裏面的 bean 節點,根據全限定類名使用反射創建對象放到 map 裏、掃描到打上上述註解的類還是通過反射創建對象放到 map 裏。

這個時候 map 裏就有各種對象了, 接下來我們在代碼裏需要用到裏面的對象時, 再通過 DI 注入 (autowired、resource等註解, xml 裏 bean 節點內的 ref 屬性,項目啓動的時候會讀取 xm | 節點 ref 屬性根據 id 注入, 也會掃描這 些註解,根據類型或 id 注入:id 就是對象名)

2. 控制反轉 (️‍)

沒有引入 IOC 容器之前

對象 A 依賴於對象 B, 那麼對象 A 在初始化或者運行到某一點的時候, 自己必須主動去創建對象 B 或者使用已經創建的對象 B,無論是創建還是使用對象 B, 控制權都在自己手上

引入 IOC 容器之後

對象 A 與對象 B 之間失去了直接聯繫, 當對象 A 運行到需要對象 B 的時候,IOC 容器會主動創建一個對象 B 注入到對象 A 需要的地方

通過前後的對比,不難看出來: 對象 A 獲得依賴對象 B 的過程, 由主動行爲變爲了被動行爲, 控制權顛倒過來了,這就是 "控制反轉" 這個名稱的由來

全部對象的控制權全部上繳給 "第三方"IOC 容器,所以,IOC 容器成了整個系統的關鍵核心, 它起到了一種類似 “粘合劑,固體膠” 的作用,把系統中的所有對象粘合在一起發揮作用, 如果沒有這個 "粘合劑", 對象與對象之間會彼此失去聯繫,這就是有人把 IOC 容器比喻成 “粘合劑” 的由來

3. 依賴注入

“獲得依賴對象的過程被反轉了 "。控制被反轉之後, 獲得依賴對象的過程由自身管理變爲了由 IOC 容器主動注入。依賴注入是實現 IOC 的方法,就是由 IOC 容器在運行期間,動態地將某種依賴關係注入到對象之中(下面詳說)

四. Bean 的實例化

以 Dao 層代表持久層,Service 層代表業務層來舉例

1. 無參構造

在 Bean 中存在默認無參構造函數的情況下,根據默認無參構造方法來創建對象,就像這樣:

<bean id="userDao" class="yu7daily.dao.impl.UserDaoImpl"/>

2. 工廠靜態方法

①首先寫好配置文件

<bean id="userDao" class="yu7daily.factory.StaticFactory" factory-method="getUserDao"></bean>

②工廠的靜態方法返回 Bean 的實例

public class StaticFactory {
    public static UserDao getUserDao(){ return new UserDaoImpl(); }
}

3. 工廠實例方法(常用)

配置好工廠的 Bean

<bean id="factory" class="yu7daily.factory.DynamicFactory">
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"/>

返回實例化的 Bean 對象

public class DynamicFactory {
    public UserDao getUserDao(){ return new UserDaoImpl(); }
}

由於上述的方式,factory-bean的名稱不固定,不夠簡便,於是又產生了新的簡便方法

public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始實例工廠中創建對象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

我還可以通過public boolean isSingleton() { return true; }來確定造出的對象是否爲單例(true 代表單例) 配置 bean 也變得簡單很多

<bean id="userDao" class="yu7daily.factory.UserDaoFactoryBean"/>

五. Bean 的依賴注入

他是 Spring 核心 IOC 的具體體現,簡言之就是把持久層對象傳入業務層,不用我們自己去 new 了依賴注入的目的就是降低耦合 依賴注入的前提是寫好 Bean 配置,和上面的相似,以下就不寫了

1.set 注入 (️‍)

set 注入可以減少硬編碼問題,本質是在容器內部將一個類設置到另一個類中,就像這樣

而在這裏,想要在容器內部實現把 B 設置到 A 中就可以通過 set 注入的方式,實現起來就是在 A 類中寫一個引入 B 的 set 方法,就像這樣:

xmlns:p="http://www.springframework.org/schema/p"
<bean id="a" class="yu7daily.service.A" p:b-ref="b">
private B b;
public void setB(B b) {
    this.b = b;
   }

這樣的話我們就可以在 A 中爲所欲爲地調用 B 裏的方法啦關鍵是不用 new~

比較起來看,不用注入依賴的話,我想在 A 類中調用 B 裏的方法還需要獲取容器、得到 Bean、最後得來對象,十分麻煩

2. 有參構造

和 Set 注入大同小異,本質也是類之間的設置,只不過是形式不同而已

private B b;
public A(B b) {
  this.b = b;
  }

六. 第一個 Spring 案例

①導入開發的基本座標

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.22.RELEASE</version>
    </dependency>
</dependencies>

②編寫接口和實現類

③創建 Spring 核心配置文件

在類路徑中的 resources 裏創建

<?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 配置文件中向 bean 裏添加 BookDaoImql 和 BookServiceImpl

<!--    配置bean-->
<!--    id表示給bean起名字,class表示給bean定義類型-->
<bean id="bookDao1" class="yu7daily.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="yu7daily.service.impl.BookServiceImpl">
    <!--配置service與dao的關係,也就是數據層和業務層-->
    <!--        property標籤表示配置當前bean的屬性-->
    <!--        name屬性表示配置哪一個具體的屬性-->
    <!--        ref屬性表示參照哪一個bean-->
    <property />
</bean>

⑤使用 Spring 的 API 獲取 Bean 的實例

//獲取ioc容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//獲取bean
BookDao bookDao = (BookDao) ctx.getBean("bookDao1");
bookDao.save();
//獲取service
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();

運行結果:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/zRWvSWgYIaioBteW-yzMdQ