Spring之IoC理論

概述爲何須要 IoCIoC本質實戰分析編寫代碼思考IoC涉及到的組件IoC建立對象無參構造器有參構造器Spring中XML配置參考文獻php

概述

上一篇spring概述咱們搭建完基於 Spring 框架的環境, 這篇咱們開始真正的閱讀 Spring 的源碼,分析 Spring 的源碼以前咱們先來簡單回顧下 Spring 核心功能的簡單使用。java


爲何須要 IoC

假若有這麼一個業務場景:dao 層從不一樣的地方獲取用戶數據,service 層用來調用獲取用戶的方法,如何控制從想要的地方獲取用戶數據?mysql

一、先寫一個 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 + '\'' +
                '}';
    }
}
複製代碼

二、寫一個 UserDao 接口web

public interface UserDao {
    public void getUser();
}
複製代碼

三、再去寫 Dao 的實現類spring

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

四、寫 UserService 的接口sql

public interface UserService {
    public void getUser();
}
複製代碼

五、最後寫 UserService 的實現類數據庫

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();

    public void getUser() {
        userDao.getUser();
    }
}
複製代碼

六、測試一下編程

public class UserGetTest {

    @Test
    public void getUser(){
        UserService userService = new UserServiceImpl();
        userService.getUser();
    }
}
複製代碼

這樣就實現了一種讀取用戶信息的方式,接下來咱們再增長一種從 Mysql 數據庫中讀取用戶信息的方法。bash

再增長 UserDao 的實現類

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

緊接着咱們要去使用 MySql 的話 , 咱們就須要去 service 實現類裏面修改對應的實現。

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoMySqlImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}
複製代碼

一樣若是咱們須要從 Oracle 數據庫中讀取數據,還須要構建一個 UserDao 的實現類,而後修改 UserServiceImpl 類。 假設咱們的這種需求很是大 , 這種方式就根本不適用了,每次變更 , 都須要修改大量代碼 . 這種設計的耦合性過高了, 牽一髮而動全身 。

那咱們如何去解決?

咱們能夠在調用 UserDao 實現類的地方,不去實例化該對象,而是留出一個接口 ,利用 set 方法,代碼以下:

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

    @Override
    public void getUser() {
        userDao.getUser();
    }
}
複製代碼

如今在測試類裏,進行測試:

public class UserGetTest {

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

        userService.setUserDao(new UserDaoMysqlImpl());
        userService.getUser();
    }
}
複製代碼

執行結果爲:

從bean中獲取到的用戶數據爲User{name='hresh'}
從MySQL數據庫中獲取到的用戶數據爲User{name='acorn'}
複製代碼

雖然只是 UserServiceImpl 類中的代碼作了修改,看起來變更不大,甚至你可能會說測試類中還變複雜了。可是仔細想一下,以前全部的 Dao 實現類都是在 UserServiceImpl 中控制建立,而如今由更接近用戶的測試類中控制建立對象,把主動權交給了調用者,程序不用去管怎麼建立,怎麼實現了,它只負責提供一個接口便可。

這種思想 ,從本質上解決了問題 , 咱們程序員再也不去管理對象的建立了,更多的去關注業務的實現 ,耦合性大大下降 。這也就是 IoC 的原型 !


IoC本質

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

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

  • 高層模塊不該該依賴於底層模塊,二者應該依賴於其抽象。
  • 抽象不該該依賴具體實現,具體實現應該依賴抽象。

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

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

將對象之間的相互依賴關係交給 IoC 容器來管理,並由 IoC 容器完成對象的注入。這樣能夠很大程度上簡化應用的開發,把應用從複雜的依賴關係中解放出來。IoC 容器就像是一個工廠同樣,當咱們須要建立一個對象的時候,只須要配置好配置文件/註解便可,徹底不用考慮對象是如何被建立出來的。在實際項目中一個 Service 類可能有幾百甚至上千個類做爲它的底層,假如咱們須要實例化這個 Service,你可能要每次都要搞清楚這個 Service 全部底層類的構造函數,這可能會把人逼瘋。若是利用 IoC 的話,你只須要配置好,而後在須要的地方引用就好了,這大大增長了項目的可維護性且下降了開發難度。

IoC 在 Spring 中有多種實現方式,可使用 XML 配置,也可使用註解,新版本的 Spring 也能夠零配置實現 IoC。

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

採用 XML 方式配置 Bean 的時候,Bean 的定義信息是和實現分離的,而採用註解的方式能夠把二者合爲一體,Bean 的定義信息直接以註解的形式定義在實現類中,從而達到了零配置的目的。


實戰分析

編寫代碼

定義一個 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);
    }
}
複製代碼

執行結果爲:

User{name='hresh'}
複製代碼

思考

  • User 對象是誰建立的?【user 對象是由 Spring 建立的】

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

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

  • 控制:誰來控制對象的建立,傳統應用程序的對象是由程序自己控制建立的,使用 Spring 後,對象是由 Spring 來建立的。

  • 反轉:程序自己不建立對象,而變成被動地接收對象。

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

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

    關於 ClassPathXmlApplicationContext 的學習後續會單獨介紹,有興趣的朋友能夠去看一下。

按照上述的方式咱們對以前提到的業務場景進行修改。首先新增 一個 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>
複製代碼

測試代碼以下:

@Test
public void MyBean(){
    ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
    UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl");
    serviceImpl.getUser();
}
複製代碼

以後咱們不須要再去程序中改動了,要實現不一樣的操做,只須要在 XML 配置文件中進行修改。所謂的 IoC 就是對象由 Spring 來建立、管理和裝配。


IoC涉及到的組件

在上文測試代碼中咱們用到的是 ApplicationContext,具體實現是 ClassPathXmlApplicationContext。因此接下來咱們簡單分析一下在此過程當中涉及到的組件。

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

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

Resource 主要負責對資源的抽象,它的每個實現類都表明了一種資源的訪問策略,如 ClasspathResource 、 URLResource ,FileSystemResource 等。

有了資源,就須要有資源加載模塊,Spring 利用 ResourceLoader 來進行統一資源加載,關係圖以下:

資源加載完畢以後就須要 BeanFactory 來進行加載解析,它是一個 bean 容器,其中 BeanDefinition 是它的基本結構,它內部維護着一 個 BeanDefinition map ,並可根據 BeanDefinition 的描述進行 bean 的建立和管理。

BeanFacoty 有三個直接子類 ListableBeanFactoryHierarchicalBeanFactoryAutowireCapableBeanFactoryDefaultListableBeanFactory 爲最終默認實現,它實現了全部接口。

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

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

ApplicationContext 是個 Spring 容器,也叫作應用上下文。它繼承 BeanFactory,同時也是 BeanFactory 的擴展升級版。因爲 ApplicationContext 的結構就決定了它與 BeanFactory 的不一樣,其主要區別有:

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

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

IoC建立對象

無參構造器

當對象由無參構造器建立時,屬性是由該類的 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");

        //在執行getBean的時候, user已經建立好了,屬性是經過set方法寫入的
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}
複製代碼

執行結果爲:

user無參構造方法
User{name='hresh'}
複製代碼

若是將 User 類中的 set 方法註釋掉,再次調用測試代碼,會報錯,說明對象是由無參構造器建立成功後,會調用 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);
    }
}
複製代碼

執行結果爲:

user有參構造方法
User{name='hresh'}
複製代碼

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


Spring中XML配置

別名

alias 設置別名 , 爲bean設置別名 , 能夠設置多個別名 。

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

Bean的配置

<!--bean就是java對象,由Spring建立和管理-->

<!--
    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來實現 ,當有多個關於 bean 定義的文件,最後能夠集中在一個文件中。

<import resource="{path}/beans.xml"/>
複製代碼

參考文獻

https://blog.kuangstudy.com/index.php/archives/518/

相關文章
相關標籤/搜索