依賴注入
依賴注入就是在Spring創建Bean的時候,去實例化該Bean構造函數所需的參數,或者通過Setter方法去設置該Bean的屬性。
Spring的依賴注入有兩種基于構造函數的依賴注入和基于setter的依賴注入。
基于構造函數的依賴注入
構造函數的注入是通過構造函數的參數來實現的。如下所示:
public class ExampleBean {// Number of years to calculate the Ultimate Answerprivate int years;// The Answer to Life, the Universe, and Everythingprivate String ultimateAnswer;public ExampleBean(int years, String ultimateAnswer) {this.years = years;this.ultimateAnswer = ultimateAnswer;}}?
該Bean有一個兩個參數的構造函數,那么怎么注入這些參數呢?
有三種方法。
方法1:按構造函數的類型匹配:
這里通過指定參數的類型,即可以指定哪個參數是years,哪個參數是ultimateAnswer。
方法2:構造函數索引:
Spring中可以通過構造函數的索引來指定特定的參數。要注意Spring的索引是從0開始的。
方法3:構造函數名字匹配:
如果通過構造函數的名字來匹配,需要注意必須在編譯的時候開啟調試標志,要不然Spring不能在構造函數中找到參數名。
如果不想啟用調試標志,則必須使用@ConstructorProperties JDK注解顯式命名構造函數參數。
public class ExampleBeanWithConstructorProperties {// Number of years to calculate the Ultimate Answerprivate int years;// The Answer to Life, the Universe, and Everythingprivate String ultimateAnswer;@ConstructorProperties ({"years", "ultimateAnswer"})public ExampleBeanWithConstructorProperties (int years, String ultimateAnswer) {this.years = years;this.ultimateAnswer = ultimateAnswer;}}
基于Setter的注入
Setter注入主要用來無參構造器或者獲得對象實例之后才設置對象的屬性。下面是Setter的例子:
public class SimpleMovieLister {// the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder;// a setter method so that the Spring container can inject a MovieFinderpublic void setMovieFinder (MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted...}
對于的XML文件如下:
除了XML配置,也可以使用注解:@Component、@Controller。或者使用@Configuration注解中的@Bean方法。
如何選擇?
既然有這樣兩種注入方式,我們怎么選擇呢?
通常來說,對于必須的屬性,我們通過構造函數來注入。對于可選屬性,我們通過Setter注入。當然你也可以在Setter方法中使用@Required注解。
當然如果第三方類不公開任何setter方法,那么構造函數注入可能是DI的唯一可用形式。
循環依賴
循環依賴主要出現在構造函數注入的情況。
類A通過構造函數注入需要類B的實例,類B通過構造函數注入需要類A的實例。如果為要相互注入的類A和類B配置bean,那么SpringIOC容器在運行時檢測到這個循環引用,會拋出BeanCurrentlyInCreationException。
解決方式就是使用Setter注入。
依賴注入的配置詳解
基本類型,字符串或者其他
如果< property/>元素的value屬性是基本類型,Spring會將其轉換為類需要的類型,配置如下:
這是一個常見的Setter注入。為了簡潔,也可以使用p-namespace,如下:
<?xml version="1.0" encoding="UTF-8"?>
Spring容器使用JavaBeans屬性編輯器機制將< value/>元素中的文本轉換為java.util.properties實例。這是一個很好的快捷方式,如下所示:
jdbc.driver.className= com.mysql.jdbc.Driverjdbc.url=jdbc: mysql://localhost:3306/mydb
注意上面例子中的value里面的值。
ref
通過< ref/>標記的bean屬性允許在同一容器或父容器中創建對任何bean的引用,而不管它是否在同一XML文件中。bean屬性的值可以與目標bean的id屬性相同,也可以與目標bean的name屬性中的一個值相同。以下示例顯示如何使用ref元素:
內部bean
在< property/> 或者 < constructor-arg/>元素內部的< bean/>元素可以定義一個內部bean,下面是個例子:
內部bean定義不需要ID或名稱。如果指定,容器也不會使用這個值作為標識符。容器在創建時也忽略作用域標志,因為內部bean總是匿名的,并且總是用外部bean創建的。不可能單獨訪問內部bean,也不可能將它們注入到除封閉bean之外的協作bean中。
集合
< list/>, < set/>, < map/>,和 < props/> 分別被用來設置Java Collection類型List, Set, Map,和 Properties 類型的屬性和參數。 下面是個例子:
administrator@example.org support@example.org development@example.org"a list element followed by a reference"just some string
強類型集合
通過在Java 5中引入泛型類型,可以使用強類型集合。也就是說,可以聲明集合類型,使其只能包含(例如)字符串元素。如果使用Spring將強類型集合注入bean,則可以利用Spring的類型轉換支持,以便在將強類型集合實例的元素添加到集合之前將其轉換為適當的類型。下面的Java類和bean定義的例子:
public class SomeClass {private Map accounts;public void setAccounts (Map accounts) {this.accounts = accounts;}}
Null和Empty字符串值
Null和空字符串是不一樣的,如下:
上面的例子相當于:
exampleBean.setEmail("");
下面是怎么設置null:
上面的例子相當于:
exampleBean.setEmail(null);
c-namespace
上面講到了p-namespace專門是設置bean的屬性用的,同樣的c-namespace是用來設置構造函數參數的,如下所示:
depends-on
通常一個bean依賴另一個bean,我們會在XML中使用< ref/>來引用,但是這種依賴關系并不直接。我們可以使用depends-on屬性來顯式強制一個或多個bean在使用此元素的bean初始化之前進行初始化,如下所示:
lazy-init
正常來說ApplicationContext中配置成單例模式的bean都會隨Spring啟動而初始化,如果有特殊的需要,想延長初始化該bean,則可以使用 lazy-init 。一個lazy-initialized bean告訴IOC容器在第一次請求bean實例時創建它,而不是在啟動時。
但是,當一個惰性初始化bean是一個非惰性初始化的singleton bean的依賴項時,ApplicationContext會在啟動時創建惰性初始化bean,因為它必須滿足singleton的依賴項。
您還可以通過在< beans/>元素上使用默認的lazy init屬性在容器級別控制lazy初始化,下面的示例顯示:
自動裝載
如果你想讓Spring自動幫你注入bean的依賴bean時候,可以使用Spring的autowiring功能。autowiring 有4種模式:

自動注入的限制和缺陷
雖然自動注入用起來很爽,但是它也有如下的缺陷:
property和constructor-arg的顯示設置會覆蓋自動注入,并且自動注入不能注入簡單類型。
自動注入不如顯示配置精確。
自動注入可能和容器中的很多bean相匹配。可能會出現問題。
從自動裝載中排除Bean
使用autowire-candidate屬性設置為false,可以防止bean被自動注入。該屬性只會影響按類型注入的方式。如果按name注入,則不受影響。
下面是自動注入的例子:
SimpleMovieLister如下:
package com.flydean.beans;import lombok.Data;@Datapublic class SimpleMovieLister {// the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder;// a setter method so that the Spring container can inject a MovieFinderpublic void setMovieFinder (MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted...}
在上面的例子中,movieFinder屬性將會被自動注入。
方法注入
在bean的生命周期不同的時候,如果一個bean要依賴于另外一個bean則可能出現問題。 比如一個單例模式的beanA 依賴于多例模式的beanB。 因為beanA是單例模式的,所以在創建beanA的時候就已經將其依賴的beanB創建了,不可能在每次beanA需要beanB的時候都創建一個新的beanB的實例。
解決方法有很多種,我們一一進行講解。
方法1:實現ApplicationContextAware
如果實現了ApplicationContextAware,則可以通過getBean方法在每次需要beanB的時候,請求他的新的實例,如下:
public class CommandManager implements ApplicationContextAware {private ApplicationContext applicationContext;public Object process (Map commandState) {// grab a new instance of the appropriate CommandCommand command = createCommand();// set the state on the (hopefully brand new) Command instancecommand.setState(commandState);return command.execute();}protected Command createCommand() {// notice the Spring API dependency!return this.applicationContext.getBean ("command", Command.class);}public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}}
這種方法并不可取的,因為業務代碼和Spring框架產生了耦合。方法注入是Spring IoC 容器的一個高級特性,它可以很好的處理這種情況。
查找方法注入
查找方法注入是指容器重寫container-managed bean上的方法,并返回容器中另一個命名bean。查找通常涉及一個原型bean,如前一節中描述的場景中所示。Spring框架通過使用cglib庫中的字節碼動態生成覆蓋該方法的子類來實現該方法注入。
因為使用了cglib,所以bean不能是final類,方法也不能是final類型。
查找方法不適用于工廠方法,尤其不適用于配置類中的@Bean方法,因為在這種情況下,容器不負責創建實例,因此無法動態創建運行時生成的子類。
下面是一個查找方法的例子:
public abstract class CommandManagerB {public Object process (Map commandState) {// grab a new instance of the appropriate Command interfaceAsyncCommand command = createCommand();return null;}// okay... but where is the implementation of this method?public abstract AsyncCommand createCommand();}
這里我們定義了一個抽象類,要查找的方法就是createCommand。返回的對象類,如下:
public class AsyncCommand {}
下面是XML配置文件:
CommandMangerB每次調用createCommand,都會返回一個新的AsyncCommand實例。
在基于注解的情況下,也可以這樣使用:
public abstract class CommandManagerC {public Object process (Object commandState) {Command command = createCommand();return command.execute();}@Lookup("myCommand")protected abstract Command createCommand();}
其中@Lookup(“myCommand”) 中的名字也可以省略,則按照默認的類型來解析。
任意方法替換
我們甚至可以使用replaced-method替換bean的方法實現。我們有個MyValueCalculator類,有一個我們想重寫的方法computeValue。
public class MyValueCalculator {public String computeValue(String input) {// some real code...return "abc";}// some other methods...}
一個實現了org.springframework.beans.factory.support.MethodReplacer接口的類提供了新的方法,如下所示:
public class ReplacementComputeValue implements MethodReplacer {public Object reimplement (Object o, Method m, Object[] args) throws Throwable {// get the input value, work with it, and return a computed resultString input = (String) args[0];return "def";}}
bean的定義如下:
String
本章的例子可以參考bean-di中的bean-di部分。