Spring之IoC理論

概述

上一篇spring概述我們搭建完基于 Spring 框架的環(huán)境, 這篇我們開始真正的閱讀 Spring 的源碼,分析 Spring 的源碼之前我們先來簡單回顧下 Spring 核心功能的簡單使用。


為什么需要 IoC

假如有這么一個業(yè)務場景:dao 層從不同的地方獲取用戶數據,service 層用來調用獲取用戶的方法,如何控制從想要的地方獲取用戶數據?

1、先寫一個 User 類

public class User {    private String name;    public User() {    }    public User(String name) {        this.name = name;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                '}';    }}

2、寫一個 UserDao 接口

public interface UserDao {    public void getUser();}

3、再去寫 Dao 的實現(xiàn)類

public class UserDaoImpl implements UserDao {    public void getUser() {        User user = new User("hresh");        System.out.println("從bean中獲取到的用戶數據為"+user);    }}

4、寫 UserService 的接口

public interface UserService {    public void getUser();}

5、最后寫 UserService 的實現(xiàn)類

public class UserServiceImpl implements UserService {    private UserDao userDao = new UserDaoImpl();    public void getUser() {        userDao.getUser();    }}

6、測試一下

public class UserGetTest {    @Test    public void getUser(){        UserService userService = new UserServiceImpl();        userService.getUser();    }}

這樣就實現(xiàn)了一種讀取用戶信息的方式,接下來我們再增加一種從 Mysql 數據庫中讀取用戶信息的方法。

再增加 UserDao 的實現(xiàn)類

public class UserDaoMysqlImpl implements UserDao {    public void getUser() {        User user = new User("acorn");        System.out.println("從MySQL數據庫中獲取到的用戶數據為"+user);    }}

緊接著我們要去使用 MySql 的話 , 我們就需要去 service 實現(xiàn)類里面修改對應的實現(xiàn)。

public class UserServiceImpl implements UserService {    private UserDao userDao = new UserDaoMySqlImpl();    @Override    public void getUser() {        userDao.getUser();    }}

同樣如果我們需要從 Oracle 數據庫中讀取數據,還需要構建一個 UserDao 的實現(xiàn)類,然后修改 UserServiceImpl 類。 假設我們的這種需求非常大 , 這種方式就根本不適用了,每次變動 , 都需要修改大量代碼 . 這種設計的耦合性太高了, 牽一發(fā)而動全身 。

那我們如何去解決?

我們可以在調用 UserDao 實現(xiàn)類的地方,不去實例化該對象,而是留出一個接口 ,利用 set 方法,代碼如下:

public class UserServiceImpl implements UserService {    private UserDao userDao;    // 利用set實現(xiàn)    public void setUserDao(UserDao userDao) {        this.userDao = userDao;    }    @Override    public void getUser() {        userDao.getUser();    }}

現(xiàn)在在測試類里,進行測試:

public class UserGetTest {    @Test    public void getUser(){        UserServiceImpl userService = new UserServiceImpl();        userService.setUserDao(new UserDaoImpl());        userService.getUser();        userService.setUserDao(new UserDaoMysqlImpl());        userService.getUser();    }}

執(zhí)行結果為:

從bean中獲取到的用戶數據為User{name='hresh'}從MySQL數據庫中獲取到的用戶數據為User{name='acorn'}

雖然只是 UserServiceImpl 類中的代碼做了修改,看起來變動不大,甚至你可能會說測試類中還變復雜了。但是仔細想一下,之前所有的 Dao 實現(xiàn)類都是在 UserServiceImpl 中控制創(chuàng)建,而現(xiàn)在由更接近用戶的測試類中控制創(chuàng)建對象,把主動權交給了調用者,程序不用去管怎么創(chuàng)建,怎么實現(xiàn)了,它只負責提供一個接口即可。

這種思想 ,從本質上解決了問題 , 我們程序員不再去管理對象的創(chuàng)建了,更多的去關注業(yè)務的實現(xiàn) ,耦合性大大降低 。這也就是 IoC 的原型 !


IoC本質

IoC( Inverse of Control:控制反轉 )是一種設計思想,就是將原本在程序中手動創(chuàng)建對象的控制權,交由 Spring 框架來管理。IoC 在其他語言中也有應用,并非 Spring 特有。IoC 容器是 Spring 用來實現(xiàn) IoC 的載體,IoC 容器實際上就是個 Map(key,value),Map 中存放的是各種對象。

要了解控制反轉,有必要先了解軟件設計的一個重要思想:依賴倒置原則( Dependency Inversion Principle )。

  • 高層模塊不應該依賴于底層模塊,兩者應該依賴于其抽象。
  • 抽象不應該依賴具體實現(xiàn),具體實現(xiàn)應該依賴抽象。

上面2點是依賴倒置原則的概念,也是核心。主要是說模塊之間不要依賴具體實現(xiàn),依賴接口或抽象。

其實依賴倒置原則的核心思想是面向接口編程。

image.png

將對象之間的相互依賴關系交給 IoC 容器來管理,并由 IoC 容器完成對象的注入。這樣可以很大程度上簡化應用的開發(fā),把應用從復雜的依賴關系中解放出來。IoC 容器就像是一個工廠一樣,當我們需要創(chuàng)建一個對象的時候,只需要配置好配置文件/注解即可,完全不用考慮對象是如何被創(chuàng)建出來的。在實際項目中一個 Service 類可能有幾百甚至上千個類作為它的底層,假如我們需要實例化這個 Service,你可能要每次都要搞清楚這個 Service 所有底層類的構造函數,這可能會把人逼瘋。如果利用 IoC 的話,你只需要配置好,然后在需要的地方引用就行了,這大大增加了項目的可維護性且降低了開發(fā)難度。

IoC 在 Spring 中有多種實現(xiàn)方式,可以使用 XML 配置,也可以使用注解,新版本的 Spring 也可以零配置實現(xiàn) IoC。

Spring 容器在初始化時先讀取配置文件,根據配置文件或元數據創(chuàng)建與組織對象存入容器中,程序使用時再從 IoC 容器中取出需要的對象。

image.png

采用 XML 方式配置 Bean 的時候,Bean 的定義信息是和實現(xiàn)分離的,而采用注解的方式可以把兩者合為一體,Bean 的定義信息直接以注解的形式定義在實現(xiàn)類中,從而達到了零配置的目的。


實戰(zhàn)分析

編寫代碼

定義一個 bean 類:

public class User {    private String name;    public User() {    }    public User(String name) {        this.name = name;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                '}';    }}

源碼很簡單,bean 沒有特別之處,Spring 的目的就是讓我們的 bean 成為一個純粹的 POJO,這就是 Spring 追求的,接下來就是在配置文件中定義這個 bean,配置文件如下:

<?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">    <bean id="user" class="com.msdn.bean.User">        <property name="name" value="hresh" />    </bean></beans>復制代碼

在上面的配置中我們可以看到bean的聲明方式,在spring中的bean定義有N種屬性,但是我們只要像上面這樣簡單的聲明就可以使用了。
具體測試代碼如下:

public class MyBeanTest {    @Test    public void MyBean(){        //解析application_context.xml文件 , 生成管理相應的Bean對象        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");        //getBean : 參數即為spring配置文件中bean的id .        User user = (User) context.getBean("user");        System.out.println(user);    }}

執(zhí)行結果為:

User{name='hresh'}

思考

  • User 對象是誰創(chuàng)建的?【user 對象是由 Spring 創(chuàng)建的】

  • User 對象的屬性是怎么設置的?【user 對象的屬性是由 Spring 容器設置的】

    這個過程就叫做控制反轉:

  • 控制:誰來控制對象的創(chuàng)建,傳統(tǒng)應用程序的對象是由程序本身控制創(chuàng)建的,使用 Spring 后,對象是由 Spring 來創(chuàng)建的。

  • 反轉:程序本身不創(chuàng)建對象,而變成被動地接收對象。

    依賴注入:利用 set 方法來進行注入的。

    IOC是一種編程思想,由主動的編程變成被動的接收 。

    關于 ClassPathXmlApplicationContext 的學習后續(xù)會單獨介紹,有興趣的朋友可以去看一下。

按照上述的方式我們對之前提到的業(yè)務場景進行修改。首先新增 一個 Spring 配置文件 application_context.xml

<?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">    <bean id="mysqlImpl" class="com.msdn.dao.UserDaoMysqlImpl" />    <bean id="oracleImpl" class="com.msdn.dao.UserDaoOracleImpl" />    <bean id="serviceImpl" class="com.msdn.service.UserServiceImpl">         <!--注意: 這里的name并不是屬性 , 而是set方法后面的那部分 , 首字母小寫-->        <!--引用另外一個bean , 不是用value 而是用 ref-->        <property name="userDao" ref="oracleImpl" />    </bean></beans>復制代碼

測試代碼如下:

@Testpublic void MyBean(){    ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");    UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl");    serviceImpl.getUser();}

之后我們不需要再去程序中改動了,要實現(xiàn)不同的操作,只需要在 XML 配置文件中進行修改。所謂的 IoC 就是對象由 Spring 來創(chuàng)建、管理和裝配。


IoC涉及到的組件

在上文測試代碼中我們用到的是 ApplicationContext,具體實現(xiàn)是 ClassPathXmlApplicationContext。所以接下來我們簡單分析一下在此過程中涉及到的組件。

首先是 ClassPathXmlApplicationContext 類的繼承關系圖。

image.png

基本上包含了 IOC 體系中大部分的核心類和接口。 下面我們就針對這個圖進行簡單的拆分和補充說明。

Resource 主要負責對資源的抽象,它的每一個實現(xiàn)類都代表了一種資源的訪問策略,如 ClasspathResource 、 URLResource ,F(xiàn)ileSystemResource 等。

image.png

有了資源,就需要有資源加載模塊,Spring 利用 ResourceLoader 來進行統(tǒng)一資源加載,關系圖如下:

image.png

資源加載完畢之后就需要 BeanFactory 來進行加載解析,它是一個 bean 容器,其中 BeanDefinition 是它的基本結構,它內部維護著一 個 BeanDefinition map ,并可根據 BeanDefinition 的描述進行 bean 的創(chuàng)建和管理。

image.png

BeanFacoty 有三個直接子類 ListableBeanFactory、HierarchicalBeanFactoryAutowireCapableBeanFactory,DefaultListableBeanFactory 為最終默認實現(xiàn),它實現(xiàn)了所有接口。

BeanDefinition 用來描述 Spring 中的 Bean 對象。

image.png

BeanDefinitionReader 的作用是讀取 Spring 配置文件中的內容,將其轉換為 IoC 容器內部的數據結構:BeanDefinition。

image.png

ApplicationContext 是個 Spring 容器,也叫做應用上下文。它繼承 BeanFactory,同時也是 BeanFactory 的擴展升級版。由于 ApplicationContext 的結構就決定了它與 BeanFactory 的不同,其主要區(qū)別有:

  1. 繼承 MessageSource ,提供國際化的標準訪問策略;
  2. 繼承 ApplicationEventPublisher,提供強大的事件機制;
  3. 擴展 ResourceLoader,可以用來加載多個 Resource,可以靈活訪問不同的資源;
  4. 對 Web 應用的支持。
image.png
image.png

上述提到的六個重要知識點是 Spring IoC 中最核心的部分,后續(xù)的學習也是針對這些內容進行詳細解讀。

IoC創(chuàng)建對象

無參構造器

當對象由無參構造器創(chuàng)建時,屬性是由該類的 set 方法寫入的。

User 類

public class User {    private String name;    public User() {        System.out.println("user無參構造方法");    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                '}';    }}

application_context.xml

<?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">    <bean id="user" class="com.msdn.bean.User">        <property name="name" value="hresh" />    </bean></beans>

測試代碼:

public class MyBeanTest {    @Test    public void MyBean(){        //解析application_context.xml文件 , 生成管理相應的Bean對象        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");        //在執(zhí)行getBean的時候, user已經創(chuàng)建好了,屬性是通過set方法寫入的        User user = (User) context.getBean("user");        System.out.println(user);    }}

執(zhí)行結果為:

user無參構造方法User{name='hresh'}

如果將 User 類中的 set 方法注釋掉,再次調用測試代碼,會報錯,說明對象是由無參構造器創(chuàng)建成功后,會調用 set 方法完成實例的初始化。

有參構造器

User 類

public class User {    private String name;    public User() {        System.out.println("user無參構造方法");    }    public User(String name) {        this.name = name;        System.out.println("user有參構造方法");    }    public String getName() {        return name;    }//    public void setName(String name) {//        this.name = name;//    }    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                '}';    }}

application_context.xml

<?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">    <bean id="user" class="com.msdn.bean.User">        <constructor-arg name="name" value="hresh" />    </bean></beans>

測試代碼:

public class MyBeanTest {    @Test    public void MyBean(){        //解析application_context.xml文件 , 生成管理相應的Bean對象        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");        User user = (User) context.getBean("user");        System.out.println(user);    }}

執(zhí)行結果為:

user有參構造方法User{name='hresh'}

結論:Spring 容器根據 XML 文件中的配置,調用 bean 類的有參構造器來創(chuàng)建對象。


Spring中XML配置

別名

alias 設置別名 , 為bean設置別名 , 可以設置多個別名 。

<!--設置別名:在獲取Bean的時候可以使用別名獲取--><alias name="userT" alias="userNew"/>

Bean的配置

<!--bean就是java對象,由Spring創(chuàng)建和管理--><!--    id 是bean的標識符,要唯一,如果沒有配置id,name就是默認標識符    如果配置id,又配置了name,那么name是別名    name可以設置多個別名,可以用逗號,分號,空格隔開    如果不配置id和name,可以根據applicationContext.getBean(.class)獲取對象;    class是bean的全限定名=包名+類名--><bean id="hello" name="hello2 h2,h3;h4" class="com.msdn.bean.Hello">    <property name="name" value="Spring"/></bean>

import

團隊的合作通過import來實現(xiàn) ,當有多個關于 bean 定義的文件,最后可以集中在一個文件中。

<import resource="{path}/beans.xml"/>

參考:

Spring之IoC理論

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容