1. 程式人生 > >深入理解依賴注入

深入理解依賴注入

本文主要講解依賴注入(設值注入、構造注入),作用是可以使Spring將各層的物件以鬆耦合的方式組織在一起,Action物件無須關心Service物件的具體實現,Service物件無須關心持久層物件的具體實現,各層物件的呼叫完全面向介面。當系統需要重構時,程式碼的改寫量將大大減少。上面所說的一切都得益於Spring的核心機制,依賴注入。依賴注入讓bean與bean之間以配置檔案組織在一起,而不是以硬編碼的方式耦合在一起。

一、理解依賴注入

依賴注入(Dependency Injection)和控制反轉(Inversion of Control)是同一個概念。具體含義是:當某個角色(可能是一個Java例項,呼叫者)需要另一個角色(另一個Java例項,被呼叫者)的協助時,在傳統的程式設計過程中,通常由呼叫者來建立被呼叫者的例項。但在Spring裡,建立被呼叫者的工作不再由呼叫者來完成,因此稱為控制反轉;建立被呼叫者例項的工作通常由Spring容器來完成,然後注入呼叫者,因此也稱為依賴注入。

不管是依賴注入,還是控制反轉,都說明Spring採用動態、靈活的方式來管理各種物件。物件與物件之間的具體實現互相透明。在理解依賴注入之前,看如下這個問題在各種社會形態裡如何解決:一個人(Java例項,呼叫者)需要一把斧子(Java例項,被呼叫者)。

(1)原始社會裡,幾乎沒有社會分工。需要斧子的人(呼叫者)只能自己去磨一把斧子(被呼叫者)。對應的情形為:Java程式裡的呼叫者自己建立被呼叫者。

(2)進入工業社會,工廠出現。斧子不再由普通人完成,而在工廠裡被生產出來,此時需要斧子的人(呼叫者)找到工廠,購買斧子,無須關心斧子的製造過程。對應Java程式的簡單工廠的設計模式(簡單工廠的設計模式可以研究下

)。

(3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家裡發出一個簡單指令:需要斧子。斧子就自然出現在他面前。對應Spring的依賴注入。

第一種情況下,Java例項的呼叫者建立被呼叫的Java例項,必然要求被呼叫的Java類出現在呼叫者的程式碼裡。無法實現二者之間的鬆耦合。

第二種情況下,呼叫者無須關心被呼叫者具體實現過程,只需要找到符合某種標準(介面)的例項即可使用。此時呼叫的程式碼面向介面程式設計,可以讓呼叫者和被呼叫者解耦,這也是工廠模式大量使用的原因。但呼叫者需要自己定位工廠,呼叫者與特定工廠耦合在一起。

第三種情況下,呼叫者無須自己定位工廠,程式執行到需要被呼叫者時,系統自動提供被呼叫者例項。事實上,呼叫者和被呼叫者都處於Spring的管理下,二者之間的依賴關係由Spring提供。

所謂依賴注入,是指程式執行過程中,如果需要呼叫另一個物件協助時,無須在程式碼中建立被呼叫者,而是依賴於外部的注入。Spring的依賴注入對呼叫者和被呼叫者幾乎沒有任何要求,完全支援對POJO之間依賴關係的管理。

二、依賴注入方式

1. 設值注入:設值注入是指通過setter方法傳入被呼叫者的例項。這種注入方式簡單、直觀,因而在Spring的依賴注入裡大量使用。

demo程式:

package test.di;

/**
 * 定義Person介面
 * 
 * @author quyang.ybb
 *
 */
public interface Person {

    // Person接口裡定義一個使用斧子的方法
    public void useAxe();

}

package test.di;

/**
 * 定義Axe的介面
 * 
 * @author quyang.ybb
 *
 */
public interface Axe {

    // Axe接口裡有個砍的方法
    public String chop();

}

/**
 * 
 */
package test.di.impl;

import test.di.Axe;
import test.di.Person;

/**
 * Chinese實現Person介面
 * 
 * @author quyang.ybb
 *
 */
public class Chinese implements Person {

    // 面向Axe介面程式設計,而不是具體的實現類
    private Axe axe;

    // 預設的構造器
    public Chinese() {

    }

    public void setAxe(Axe axe) {
        this.axe = axe;
    }

    @Override
    public void useAxe() {
	System.out.println(axe.chop());
    }

}

/**
 * 
 */
package test.di.impl;

import test.di.Axe;

/**
 * @author quyang.ybb
 *
 */
public class StoneAxe implements Axe {

    @Override
    public String chop() {
	return "石斧砍柴好慢";
    }

}
xml配置檔案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" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:webflow="http://www.springframework.org/schema/webflow-config"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
	default-autowire="byName ">

	<bean id="axe" class="test.di.impl.StoneAxe" />
	<bean id="chinese" class="test.di.impl.Chinese">
		<property name="axe" ref="axe"></property>
	</bean>

</beans>

從配置檔案中,可以看到Spring管理bean的靈巧性。bean與bean之間的依賴關係放在配置檔案裡組織,而不是寫在程式碼裡。通過配置檔案的指定,Spring能精確地為每個bean注入屬性。因此,配置檔案裡的bean的class元素,不能僅僅是介面,而必須是真正的實現類。

Spring會自動接管每個bean定義裡的property元素定義。Spring會在執行無引數的構造器後、建立預設的bean例項後,呼叫對應的setter方法為程式注入屬性值。property定義的屬性值將不再由該bean來主動建立、管理,而改為被動接收Spring的注入。

每個bean的id屬性是該bean的惟一標識,程式通過id屬性訪問bean,bean與bean的依賴關係也通過id屬性完成。

測試程式1:

package test.di.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import test.di.Person;

/**
 * @author quyang.ybb
 *
 */
public class MainTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("test/di/main/context.xml");
	Person person = (Person) applicationContext.getBean("chinese");
	person.useAxe();
    }

}

程式的執行結果如下:
七月 26, 2015 9:30:26 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
資訊: Refreshing org[email protected]1edad6d0: startup date [Sun Jul 26 09:30:26 CST 2015]; root of context hierarchy
七月 26, 2015 9:30:26 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
資訊: Loading XML bean definitions from class path resource [test/di/main/context3.xml]
七月 26, 2015 9:30:27 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
資訊: Pre-instantiating singletons in org.s[email protected]26d82faa: defining beans [axe,chinese3]; root of factory hierarchy
鋼斧砍柴真快
七月 26, 2015 9:30:26 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
資訊: Refreshing org[email protected]1edad6d0: startup date [Sun Jul 26 09:30:26 CST 2015]; root of context hierarchy
七月 26, 2015 9:30:26 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
資訊: Loading XML bean definitions from class path resource [test/di/main/context3.xml]
七月 26, 2015 9:30:27 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
資訊: Pre-instantiating singletons in org.s[email protected]26d82faa: defining beans [axe,chinese3]; root of factory hierarchy
鋼斧砍柴真快

主程式呼叫Person的useAxe()方法時,該方法的方法體內需要使用Axe的例項,但程式裡沒有任何地方將特定的Person例項和Axe例項耦合在一起。或者說,程式裡沒有為Person例項傳入Axe的例項,Axe例項由Spring在執行期間動態注入。

Person例項不僅不需要了解Axe例項的具體實現,甚至無須瞭解Axe的建立過程。程式在執行到需要Axe例項的時候,Spring建立了Axe例項,然後注入給需要Axe例項的呼叫者。Person例項執行到需要Axe例項的地方,自然就產生了Axe例項,用來供Person例項使用。呼叫者不僅無須關心被呼叫者的實現過程,連工廠定位都可以省略(真是按需分配啊!)。

如果需要改寫Axe的實現類。或者說,提供另一個實現類給Person例項使用。Person介面、Chinese類都無須改變。只需提供另一個Axe的實現,然後對配置檔案進行簡單的修改即可。

Axe的另一個實現如下:

/**
 * 
 */
package test.di.impl;

import test.di.Axe;

/**
 * @author quyang.ybb
 *
 */
public class SteelAxe implements Axe {

    @Override
    public String chop() {
	return "鋼斧砍柴真快";
    }

}
然後,修改原來的Spring配置檔案,配置檔案context2.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" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:webflow="http://www.springframework.org/schema/webflow-config"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
	default-autowire="byName ">

	<bean id="axe" class="test.di.impl.SteelAxe" />
	<bean id="chinese" class="test.di.impl.Chinese">
		<property name="axe" ref="axe"></property>
	</bean>

</beans>
測試程式2:
package test.di.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import test.di.Person;

/**
 * @author quyang.ybb
 *
 */
public class MainTest2 {

    /**
     * @param args
     */
    public static void main(String[] args) {
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("test/di/main/context2.xml");
	Person person = (Person) applicationContext.getBean("chinese");
	person.useAxe();
    }

}
程式執行結果:
七月 26, 2015 9:54:52 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
資訊: Refreshing org[email protected]1edad6d0: startup date [Sun Jul 26 09:54:51 CST 2015]; root of context hierarchy
七月 26, 2015 9:54:52 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
資訊: Loading XML bean definitions from class path resource [test/di/main/context2.xml]
七月 26, 2015 9:54:52 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
資訊: Pre-instantiating singletons in org.s[email protected]57083eaa: defining beans [axe,chinese]; root of factory hierarchy
鋼斧砍柴真快

Person與Axe間沒有任何程式碼耦合,bean與bean間的依賴關係由Spring管理。採用setter方法為目標bean注入屬性的方式,稱為設值注入。
業務物件的更換變得相當簡單,物件與物件之間的依賴關係從程式碼裡分離出來,通過配置檔案動態管理。

2. 構造注入:所謂構造注入,指通過建構函式來完成依賴關係的設定,而不是通過setter方法。對前面程式碼Chinese類做簡單修改為Chinese3類,修改後的程式碼如下:

/**
 * 
 */
package test.di.impl;

import test.di.Axe;
import test.di.Person;

/**
 * Chinese1實現Person介面
 * 
 * @author quyang.ybb
 *
 */
public class Chinese3 implements Person {

    // 面向Axe介面程式設計,而不是具體的實現類
    private Axe axe;

    // 預設的構造器
    public Chinese3() {
    }

    // 構造注入所需的帶引數的構造器
    public Chinese3(Axe axe) {
	this.axe = axe;
    }

    // 實現Person介面的useAxe方法
    @Override
    public void useAxe() {
	System.out.println(axe.chop());
    }

}

不需要原來Chinese類裡的setAxe方法,構造Person例項時,Spring為Person例項注入所依賴的Axe例項。構造注入的配置檔案也需做簡單的修改,修改後的配置檔案context3.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" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:webflow="http://www.springframework.org/schema/webflow-config"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
	default-autowire="byName ">

	<bean id="axe" class="test.di.impl.SteelAxe" />
	<bean id="chinese3" class="test.di.impl.Chinese3">
		<constructor-arg ref="axe">
		</constructor-arg>
	</bean>

</beans>
測試程式3:
package test.di.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import test.di.Person;

/**
 * @author quyang.ybb
 *
 */
public class MainTest3 {

    /**
     * @param args
     */
    public static void main(String[] args) {
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("test/di/main/context3.xml");
	Person person = (Person) applicationContext.getBean("chinese3");
	person.useAxe();
    }

}
程式執行結果:
七月 26, 2015 9:57:33 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
資訊: Refreshing org[email protected]1edad6d0: startup date [Sun Jul 26 09:57:33 CST 2015]; root of context hierarchy
七月 26, 2015 9:57:33 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
資訊: Loading XML bean definitions from class path resource [test/di/main/context3.xml]
七月 26, 2015 9:57:33 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
資訊: Pre-instantiating singletons in org.s[email protected]26d82faa: defining beans [axe,chinese3]; root of factory hierarchy
鋼斧砍柴真快
執行效果與使用steelAxe設值注入時的執行效果完全一樣。區別在於:建立Person例項中Axe屬性的時機不同——設值注入是現建立一個預設的bean例項,然後呼叫對應的構造方法注入依賴關係。而構造注入則在建立bean例項時,已經完成了依賴關係的設定。

PS:Spring使用spring-beans.dtd檔案來定義BeanFactory的XML配置規範。可以在http://www.springframework.org/dtd/spring-beans.dtd找到該dtd檔案

demo程式附圖:


相關推薦

深入理解依賴注入

本文主要講解依賴注入(設值注入、構造注入),作用是可以使Spring將各層的物件以鬆耦合的方式組織在一起,Action物件無須關心Service物件的具體實現,Service物件無須關心持久層物件的具體實現,各層物件的呼叫完全面向介面。當系統需要重構時,程式碼的改寫量將大大

[PHP] 理解依賴注入

兩個類有依賴關係的時候 使用者通過建構函式引數,方法或屬性等方式將具體元件,傳給自己 $storage=new Storge(); //建構函式注入 class User{ public function __construct($storage){ $this->

php 簡單理解依賴注入和自動載入

       因為在學習tp5框架,經常看到一種寫法__construct(Request $request){$this->request=$request}的寫法,不知道這是什麼意思,後來百度了一下,這個叫做依賴注入,具體的含義找了很多文章來看,有了一些比較

理解依賴注入(Dependency Injection)

我們通過一個示例來解釋為什麼使用使用者服務定位器和依賴注入。首先,假設我們正在開發一個元件,叫SomeComponent。它執行的內容現在還不重要,我們的元件需要依賴資料庫的連線。 在下面第一個例子中,資料庫的連線是在元件內部建立的。這種方法是不實用的;事實上這

PHP程式設計師如何理解依賴注入容器(dependency injection container)

背景知識 傳統的思路是應用程式用到一個Foo類,就會建立Foo類並呼叫Foo類的方法,假如這個方法內需要一個Bar類,就會建立Bar類並呼叫Bar類的方法,而這個方法內需要一個Bim類,就會建立Bim類,接著做些其它工作。 // 程式碼【1】 class Bim {

深入淺出理解依賴注入

容器,字面上理解就是裝東西的東西。常見的變數、物件屬性等都可以算是容器。一個容器能夠裝什麼,全部取決於你對該容器的定義。當然,有這樣一種容器,它存放的不是文字、數值,而是物件、物件的描述(類、介面)或者是提供物件的回撥,通過這種容器,我們得以實現許多高階的功能,其中最常提

理解依賴注入(IOC)和學習Unity

IOC:英文全稱:Inversion of Control,中文名稱:控制反轉,它還有個名字叫依賴注入(Dependency Injection)。 作用:將各層的物件以鬆耦合的方式組織在一起,解耦,各層物件的呼叫完全面向介面。當系統重構的時候,程式碼的改寫量將大大減少。

深入理解Spring核心技術---Spring中的依賴注入

         在前面的幾篇部落格中給大家介紹了Spring中的IOC容器,現在大家應該都知道IOC容器的概念和實現的原理了吧,IOC容器是Spring的核心,他的功能就是幫助開發者去儲存物件以及管理物件之間的關係。不用讓開發者自己去管理物件之間的關係,使開發者只需要專注於

深入理解spring容器中的控制反轉(IOC)和依賴注入(DI)

首先在開篇之前我們一定一定要明確的就是:DI和IOC並不是兩個概念,DI是IOC思想在應用上的具體例子。 什麼是控制反轉(IOC)? “控制”就是指一件事物對另一件事物進行一種管理,而另一件事物處在一件事物的管理之下,這就叫控制。 在面向物件程式設計的時候,每一個程式的

關於Spring中的[控制反轉]和[依賴注入]的深入理解

第一段說明 對於一個Bean 來說,如果你依賴別的Bean , 只需要宣告即可, spring 容器負責把依賴的bean 給“注入進去“, 起初大家稱之為控制反轉(IoC) 後來 Ma

laravel服務容器-----深入理解控制反轉(IoC)和依賴注入(DI)

首先大家想一想什麼是容器,字面意思就是盛放東西的東西,常見的變數,物件屬性都是容器,一個容器能夠裝什麼東西,完全在於你對這個容器的定義。有的容器不僅僅只是存文字,變數,而是物件,屬性,那麼我們通過這種容器就可以進行很多高階的功能。 IoC容器 IoC容器是larave

Laravel 學習筆記:深入理解控制反轉(IoC)和依賴注入(DI)

目的:解耦和減少依賴關係; 控制反轉(IoC):由外部負責其依賴行為; 例如“超人”類不需要在其內部固化它的“超能力”,而是由外部來產生、組裝“超能力”,再通過“超人”的某個介面中植入; 只要“超能力”滿足某個介面,就能被超人所使用; 依賴注入(DI

Spring4深入理解IOC&DI04----Bean配置方式(全類名,工廠方法,FactoryBean),配置形式(基於XML和註解),泛型依賴注入

一、Bean的配置方式     Bean 的配置方式:通過全類名(反射)、通過工廠方法(靜態工廠方法 & 例項工廠方法)、FactoryBean 1-1.通過靜態工廠方式配置Bean   --•呼叫靜態工廠方法建立Bean是將物件建立的過程封裝到靜態方法中.當客

深入理解Angular依賴注入

AngularJS依賴注入非常有用,並且是建立可測試元件的關鍵。本文解釋了AngularJS依賴注入如何工作的。 The Provider ($provide) $provide負責告訴Angular如何建立新的可注入的事物,即服務。服務是被pr

深入理解ASP.NET Core依賴注入

概述        ASP.NET Core可以說是處處皆注入,本文從基礎角度理解一下原生DI容器,及介紹下怎麼使用並且如何替換官方提供的預設依賴注入容器。 什麼是依賴注入        百度百科中對於依賴注入的定義:控制反轉(Inversion of Control,縮寫為IoC),是面向物件程式設

laravel服務容器-----深入理解控制反轉(IoC)和依賴註入(DI)

outer 十分 綁定 之間 module 還需 true 更多 我們 首先大家想一想什麽是容器,字面意思就是盛放東西的東西,常見的變量,對象屬性都是容器,一個容器能夠裝什麽東西,完全在於你對這個容器的定義。有的容器不僅僅只是存文本,變量,而是對象,屬性,那麽我們通過這種容

深入研究Spring-IoC:原始碼分析依賴注入

1.前言 對於容器建立的過程已經闡述過一遍了,下面是依賴注入的問題。Spring提供的依賴注入的方法主要由兩種:一種是通過getBean的方法;另一種是通過註解@Autowaire。 需要指出的是依賴注入的過程是使用者第一次向ioc容器索要Bean的時候開始生產的,也可以通過設定

spring 依賴注入理解

先看一段程式碼假設你編寫了兩個類,一個是人(Person),一個是手機(Mobile)。 人有時候需要用手機打電話,需要用到手機的dialUp方法。 傳統的寫法是這樣: Java code public class Person{ public boolean makeCall(long nu

輕鬆理解-中高階java開發必知必會之 依賴注入和控制反轉

關於Ioc和DI在網上隨便一搜都有很多,但是很多人的講解呢,就是讓人感覺深奧,看多了反而有些摸不清了,下面是我找到的一個利於理解的講解可以看下 Ioc (控制反轉)   首先想說說Ioc(Inversion of Control,控制反轉)。這是spring的核心,貫穿始終。所謂Ioc,對於

Spring中的IOC(控制反轉)與DI(依賴注入)理解由淺入深-適用於小白

IOC(Inversion of Control )與 DI(Dependency injection)是sping的主要思想,不是技術! IOC控制反轉:一般我們在一個類中呼叫其它的類(非靜態類),需要新建一個該類的例項或者從其他途徑獲取該類的例項,然後對通過該例項進行操