Spring之IoC(控制反轉)和DI(依賴注入)

1.IoC的概念

IoC:經過容器去控制業務對象之間的依賴關係。控制權由應用代碼中轉到了外部容器,控制權的轉移就是反轉。控制權轉移的意義是下降了類之間的耦合度。java

Spring中將IoC容器管理的對象稱爲Bean,這個和JavaBean並無什麼關係,就跟Java和JavaScript同樣。web

Spring IoC容器

爲了實現IoC功能,Spring提供了兩個類spring

BeanFactory:Bean工廠,藉助於配置文件可以實現對JavaBean的配置和管理,用於向使用者提供Bean的實例。數組

ApplicationContext:ApplicationContext構建在BeanFactory基礎之上,提供了更多的實用功能。緩存

BeanFactory的初始化和ApplicationContext的初始化有一個很大的區別:ApplicationContext初始化時會實例化全部單實例(注意是單實例)的bean,後面調用getBean方法的時候,就能夠直接從緩存中進行讀取;而BeanFactory初始化時不會實例化Bean,直到第一次訪問某個Bean時纔會進行實例化。所以初始化ApplicationContext比BeanFactory慢,但後面調用Bean實例對象的時候則ApplicationContext比BeanFactory快。session

2.IoC底層原理(源碼後面慢慢分析)

(1)xml配置文件
(2)dom4j解析xml
(3)工廠模式
(4)反射app

3.IoC入門案例

(1)基本的jar包
1.pngdom

(2)建立Bean,在類裏添加方法ide

public class User {
    public void add() {
        System.out.println("666666");
    }
}

(3)在xml裏配置類函數

在xml文件引入schema約束

<?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">
   <!-- 配置IoC -->
   <bean id="user" class="com.codeliu.entity.User"></bean>
</beans>

(4)寫代碼測試對象建立

@Test
    public void testIoC() {
        // 加載spring配置文件,建立對象
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 獲得建立的對象,參數爲配置文件中bean標籤的id
        User user = (User)context.getBean("user");
        System.out.println(user);
        user.add();
    }

4.實例化Bean的三種方式

(1)使用類的無參數構造函數建立(經常使用)

這種方式記得得有無參構造方法

<!-- 使用類的無參構造函數實例化 -->
<bean id="user1" class="com.codeliu.entity.User"></bean>

(2)使用靜態工廠建立

public class StaticFactory {
    public static User getUser() {
        return new User();
    }
}
<!-- 使用靜態工廠方法實例化User -->
<bean id="staticFactory" class="com.codeliu.bean.StaticFactory" factory-method="getUser"></bean>
@Test
    public void testStaticFactory() {
        // 加載spring配置文件,建立對象
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 獲得建立的對象,參數爲配置文件中bean標籤的id
        User user = (User)context.getBean("staticFactory");
        System.out.println(user);
    }

(3)使用實例工廠建立

public class Factory {
    public User getUser() {
        return new User();
    }
}
<!-- 使用實例工廠實例化User -->
<!-- 首先實例化bena3 -->
<bean id="factory" class="com.codeliu.bean.Factory"></bean>
<bean id="user2" factory-bean="factory" factory-method="getUser"></bean>
@Test
    public void testFactory() {
        // 加載spring配置文件,建立對象
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 獲得建立的對象,參數爲配置文件中bean標籤的id
        User user = (User)context.getBean("user2");
        System.out.println(user);
    }

5.bean標籤經常使用屬性

(1)id
惟一,必須以字母開頭,不能包含一些特殊字符。Spring根據class屬性建立對應類的實例後,會以id爲鍵key,實例對象爲值value放入一個Map中,當調用getBean方法時,則會根據id的值從Map中把實例取出來。

(2)class
Bean類的全路徑,不能是接口

(3)name
能夠出現特殊符號,當沒有設置id的時候,name也能夠做爲id

(4)scope

Bean的做用範圍

  • singleton(經常使用):默認值,單例的,每次調用getBean方法建立的是同一個對象
<bean id="user1" class="com.codeliu.entity.User" scope="singleton"></bean>

進行測試

User user = (User)context.getBean("user1");
User user2 = (User)context.getBean("user1");
// true
System.out.println(user == user2);

能夠看到,建立一個實例後,這個惟一實例會被緩存起來,下一次要請求使用就直接返回緩存中的實例。

  • prototype(經常使用):多例的,每次調用getBean方法建立的是不一樣的對象
<bean id="user1" class="com.codeliu.entity.User" scope="prototype"></bean>

進行測試

User user = (User)context.getBean("user1");
User user2 = (User)context.getBean("user1");
// false
System.out.println(user == user2);

每次請求都會建立一個新的實例,這樣就會出現頻繁的建立和銷燬對象,形成很大的開銷,所以,若是不是必要,不設置成prototype。

  • request:web項目中,Spring建立一個Bean的對象,將對象存入request域中

  • session:web項目中,Spring建立一個Bean的對象,將對象存入session域中

  • globalSession:web項目中,應用在Porlet環境,若是沒有Porlet環境,那麼globalSession至關於session。

6.屬性注入

建立對象的時候,向類裏面的屬性設置值。

三種方式實現屬性注入
(1)使用set方法(經常使用)
(2)使用帶參的構造函數
(3)使用接口

spring只支持前兩張方式的注入

(1)使用帶參的構造函數
好比我下面的類

public class PropertyDemo1 {
    private String name;
    public PropertyDemo1(String name) {
        this.name = name;
    }
    public void add() {
        System.out.println("PropertyDemo1" + name);
    }
}

裏面有一個帶參數的構造方法,咱們能夠經過xml配置進行賦值

<!-- 使用帶參的構造方法爲Bean中的屬性設值 -->
   <bean id="property1" class="com.codeliu.entity.PropertyDemo1">
        <!-- name屬性表示Bean類中屬性的名字, value表示要設置的值-->
        <constructor-arg name="name" value="CodeTiger"></constructor-arg>
   </bean>

測試一下

@Test
    public void testProperty1() {
        // 加載spring配置文件,建立對象
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 獲得建立的對象,參數爲配置文件中bean標籤的id
        PropertyDemo1 p1 = (PropertyDemo1)context.getBean("property1");
        p1.add();
    }

(2)使用set方法(重點)
咱們定義下面這樣一個類

public class PropertyDemo2 {
    private String book;
    public PropertyDemo2() {}

    public void setBook(String book) {
        this.book = book;
    }

    public void add() {
        System.out.println(book);
    }
}

有一個屬性book並帶有相應的set方法

<!-- 使用set方法爲Bean中的屬性設值 -->
   <bean id="property2" class="com.codeliu.entity.PropertyDemo2">
        <!-- 使用property標籤 -->
        <property name="book" value="Think in java"></property>
   </bean>

進行測試

@Test
    public void testProperty2() {
        // 加載spring配置文件,建立對象
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 獲得建立的對象,參數爲配置文件中bean標籤的id
        PropertyDemo2 p2 = (PropertyDemo2)context.getBean("property2");
        p2.add();
    }

咱們這裏只是使用了一個String類型的屬性,但實際中,咱們應該會用其餘類做爲一個類的屬性,獲取一些更復雜的類型好比Map、List等,這時候該怎麼注入呢?

咱們寫一個service類,一個dao類,而後使用service類去調用dao類的方法,這樣service類中確定會有一個dao類的實例對象,看看這是應該怎麼賦值呢?

public class UserDao {
    public void add() {
        System.out.println("dao.......");
    }
}
public class UserService {
    private UserDao dao;
    public void setDao(UserDao dao) {
        this.dao = dao;
    }
    public void add() {
        System.out.println("service.....");
        dao.add();
    }
}

在service中,其實仍是和上面set方法賦值同樣的原理,只是配置文件變了,來看看怎麼配置

<!-- 注入對象類型的屬性 -->
   <!-- 先實例化UserDao -->
   <bean id="userDao" class="com.codeliu.dao.UserDao"></bean>
   <bean id="userService" class="com.codeliu.service.UserService">
        <!-- name表示 UserService類中屬性的名稱  ref表示配置UserDao的bean標籤的id值-->
        <property name="dao" ref="userDao"></property>
   </bean>

只是此次是賦值一個對象,因此咱們得先實例化dao類,才能給service類的dao屬性賦值。注意property 沒有使用value屬性,而是使用了ref屬性。

@Test
    /**
     * 經過set方法注入對象類型的屬性
     */
    public void testProperty3() {
        // 加載Spring配置文件,建立對象
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 獲得建立的對象,參數爲配置文件中bean標籤的id
        UserService service = (UserService)context.getBean("userService");
        service.add();
    }

7.p名稱空間注入

首先在xml文件的最外層bean標籤中加上這麼一句

xmlns:p="http://www.springframework.org/schema/p"

這樣纔可使用p名稱空間。

<bean id="property2" class="com.codeliu.entity.PropertyDemo2" p:book="Think in java"></bean>
<!-- 上下兩句等價 -->
<bean id="property2" class="com.codeliu.entity.PropertyDemo2"> -->
    <!-- 使用property標籤 -->
    <property name="book" value="Think in java"></property>
</bean>

上面的配置文件中,上下兩句等價。

那若是我要注入一個對象類型的屬性呢?那就得這麼寫

<bean id="userDao" class="com.codeliu.dao.UserDao"></bean>
   <bean id="userService" class="com.codeliu.service.UserService">
        <!-- name表示 UserService類中屬性的名稱  ref表示配置UserDao的bean標籤的id值-->
        <property name="dao" ref="userDao"></property>
   </bean>
   <!-- 和 上面的等價,注意p:dao-ref,ref必須加上,表示後面的是引用的另外一個bean-->
   <bean id="userService2" class="com.codeliu.service.UserService" p:dao-ref="userDao"></bean>

注意p後面的,必須帶上-Ref表示是引用另外一個bean

這時候又有問題,若是咱們的屬性名稱叫xxRef,那怎麼辦?寫成

p:daoRef-ref="userDao"

這樣會出錯的。因此p命名空間也不能亂用。

8.一些複雜類型的注入

(1)數組類型

(2)List類型

(3)Map類型

(4)java.util.Properties類型

首先在User類中添加上面四種類型的屬性,並添加相應的set方法。直接看配置

<bean id="user1" class="com.codeliu.entity.User">
        <!-- 爲數組類型的屬性賦值 -->
        <property name="arr">
            <list>
                <value>xu</value>
                <value>liu</value>
                <value>li</value>
                <value>guo</value>
            </list>
        </property>
        <!-- 爲List類型的屬性賦值 -->
        <property name="list">
            <list>
                <value>1</value>
                <value>2</value>
                <value>3</value>
                <value>4</value>
            </list>
        </property>
        <!-- 爲Map類型的屬性賦值 -->
        <property name="map">
            <map>
                <entry key="1" value="liu"></entry>
                <entry key="2" value="xu"></entry>
                <entry key="3" value="guo"></entry>
            </map>
        </property>
        <!-- 爲Properities類型的屬性賦值 -->
        <property name="properities">
            <props>
                <prop key="1">liu</prop>
                <prop key="2">xu</prop>
            </props>
        </property>
   </bean>

9.Bean之間的關係

Spring容許在配置Bean時爲Bean指定繼承依賴兩種關係。

(1)繼承

有時咱們可能兩個類之間大多數的屬性都相同,這是若是對每一個Bean都重複編寫注入信息就很繁瑣了,這時咱們能夠經過bean標籤的parent屬性重用已有的Bean元素的配置信息。

<!-- Bean之間的繼承。。。不是指類之間的繼承,只是配置信息的複用 -->
   <bean id="class1" class="com.codeliu.Class1">
        <property name="name" value="liu"></property>
        <property name="age" value="22"></property>
        <property name="address" value="NJUPT"></property>
   </bean>
   <bean id="class2" class="com.codeliu.Class2" parent="class1">
        <property name="name" value="xu"></property>
   </bean>

這裏的繼承指的是配置信息的複用,和傳統的Java類的繼承沒有半毛錢關係。

(2)依賴

IoC能保證在實例化一個Bean時,它所依賴的其餘Bean已經實例化完畢。但有時候咱們有這樣的需求,咱們想要類A比類B先實例化,若是類A是做爲類B的屬性,這還好辦,但關鍵是A不是B的屬性啊,這時咱們能夠經過bean標籤的depends-on屬性進行指定前置依賴的Bean,即便沒有關聯關係。

<!-- 設置兩個Bean的依賴關係,userDao要先於userService實例化 -->
<bean id="userDao" class="com.codeliu.dao.UserDao"></bean>
<bean id="userService" class="com.codeliu.service.UserService" depends-on="userDao"></bean>

10.自動裝配

Spring IoC容器能夠自動裝配相互協做Bean之間的關聯關係。能夠經過設置bean標籤的autowire屬性進行設置,該屬性有5種取值分別以下:

(1)no:不使用自動裝配,默認值。必須經過ref進行指定依賴。

(2)byName:根據屬性名自動匹配。若是某個Bean設置了此選項,那麼IoC將根據名字查找與屬性徹底一致的Bean,並將其與屬性自動匹配。如某個Bean設置成byName,該Bean中有一個屬性叫dao(同時要提供set方法),那麼IoC就會查找名爲dao的Bean,用它來裝配dao屬性。

(3)byType:若是容器中存在一個與指定屬性類型相同的Bean,那麼將該屬性自動裝配。若是存在多個該類型的Bean,則拋出異常,並指出不能使用byType方式進行裝配。若是沒有找到相同類型的,則什麼事都不會發生,屬性也不會被設置。若是你但願在沒找到時發出提示信息,能夠設置dependency-check屬性的值爲objects,這樣在找不到的時候就會拋出異常。

(4)constructor:與byType相似,不一樣之處在於它應用於構造器參數,若是屬性在容器中沒有找到與構造器參數類型一致的Bean,則拋出異常。

(5)autodetect:經過Bean的自省機制(introspection)來決定是否使用constructor仍是byType方式進行自動裝配。若是發現默認的構造器,將使用byType方式。


下面摘抄網上的一段話做爲文章的結尾

IOC是一種叫作「控制反轉」的設計思想。

一、較淺的層次——從名字上解析
「控制」就是指對 對象的建立、維護、銷燬等生命週期的控制,這個過程通常是由咱們的程序去主動控制的,如使用new關鍵字去建立一個對象(建立),在使用過程當中保持引用(維護),在失去所有引用後由GC去回收對象(銷燬)。
「反轉」就是指對 對象的建立、維護、銷燬等生命週期的控制由程序控制改成由IOC容器控制,須要某個對象時就直接經過名字去IOC容器中獲取。

二、更深的層次——提到DI,依賴注入,是IOC的一種重要實現
一個對象的建立每每會涉及到其餘對象的建立,好比一個對象A的成員變量持有着另外一個對象B的引用,這就是依賴,A依賴於B。IOC機制既然負責了對象的建立,那麼這個依賴關係也就必須由IOC容器負責起來。負責的方式就是DI——依賴注入,經過將依賴關係寫入配置文件,而後在建立有依賴關係的對象時,由IOC容器注入依賴的對象,如在建立A時,檢查到有依賴關係,IOC容器就把A依賴的對象B建立後注入到A中(組裝,經過反射機制實現),而後把A返回給對象請求者,完成工做。

三、IOC的意義何在? IOC並無實現更多的功能,但它的存在使咱們不須要不少代碼、不須要考慮對象間複雜的耦合關係就能從IOC容器中獲取合適的對象,並且提供了對 對象的可靠的管理,極大地下降了開發的複雜性。

相關文章
相關標籤/搜索