依賴注入(DI)是對象之間定義依賴關系的過程,即協作對象,通過構造參數,工廠方法參數,或在對象實例化之后通過設置屬性的方式注入.容器在創建bean的時候會注入這些依賴,這和bean自己控制本身的實例化,或通過類的構造指定依賴位置或服務定位模式創建對象的過程是相反的,因此叫控制反轉(IoC).
依賴注入的原則使代碼更清晰,而且依賴的對象之間是解耦的.對象不用查看它的依賴關系,也無需知道依賴關系的類和位置.因此你的類更容易測試,尤其當類實現了接口或抽象類,在單元測試中可以使用存根或模擬實現.
依賴注入主要有兩種形式,構造方式注入和set方法注入
[TOC]
## Constructor-based dependency injection
基于構造方法注入由容器調用帶參的構造方法完成,每一個參數代表一個依賴.調用靜態工廠方法指定參數構造對象也是類似的.下面示例通過構造方法注入依賴,主意,這就是一個普通的pojo類,沒有實現接口,沒有注解,
~~~java
public class SimpleMovieLister {
// 依賴MovieFinder
private MovieFinder movieFinder;
// 構造方法,需要一個參數 MovieFinder,spring容器可以注入依賴
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
~~~
### Constructor argument resolution
構造參數的解析是按類型匹配的,如果參數之間不存在混淆的情況,參數的順序和bean定義順序保持一致.如下:
~~~java
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
~~~
這兩個類Bar和Baz不存在混淆,沒有相同的繼承關系,所以,如下配置就行,不需要在元素`<constructor-arg/>`中特別指定參數的位置或類型:
~~~xml
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
~~~
當參數是引用了一個已知的bean,那么可以正確匹配(如上示例),當參數為基本數據類型,如<value>true</value>時,spring無法確定這個值的類型,還需要更多幫助的才能正確匹配.如下:
~~~java
package examples;
public class ExampleBean {
// 計算最終的年數
private int years;
// 生活,學習等
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
~~~
### Constructor argument type matching
在上面的場景,容器能用`type`屬性來匹配基本數據類型,例如:
~~~xml
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
~~~
### Constructor argument index
使用`index`明確指定參數的索引,例如
~~~xml
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
~~~
如果是是兩個相同類型的構造參數,使用索引來避免混淆.注意索引是從0開始的
### Constructor argument name
也可以使用參數的名稱來避免混淆
~~~xml
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
~~~
請記住,為了保證這種方式正確工作,必須在debug模式下通過編譯,讓spring通過構造方法找到參數,如果debug模式沒有通過,你可以使用java注解@ConstructorProperties 指定參數名稱,如下:
~~~java
package examples;
public class ExampleBean {
// 屬性忽略
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
~~~
## Setter-based dependency injection
基于setter方法的依賴注入是由容器在實例化bean之后,調用setter方法來完成的.
下面示例純setter方法注入,就是個普通的java類
~~~java
public class SimpleMovieLister {
// 依賴 MovieFinder
private MovieFinder movieFinder;
// setter方法,容器用來注入依賴
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
~~~
ApplicationContext支持基于構造參數和setter方法的依賴注入.也支持在構造參數注入之后再使用setter方式注入.結合`PropertyEditor `實例改變屬性格式,以`BeanDefinition`的形式配置依賴關系.然而大多數spring用戶不使用這些類(即編程方式),而是用xml定義bean,注解組件(@Component, @Controller等注解的類),或者java注解@Configuration類中的@Bean方法.這些資源在內部轉換為`BeanDefinition `,并用于加載整個spring容器實例.
> :-: Constructor-based or setter-based DI?
>
> 基于構造參數和setter方式的依賴注入可以混合使用,較好的經驗是,構造參數用于基礎依賴,setter方法用于可選的依賴.注意使用`@Required`注解的setter方法是必須的.
> spring提倡構造參數是不變的且不是null.此外,構造參數對于代碼調用者是完全初始化狀態.從側面說明,太多構造參數的代碼是糟糕的,表示這個類有太多責任,應該考慮重構把相關的內容分割到合適的地方.
> setter主要應用于可選的依賴,這些依賴在類中分配合適的默認值.另外,使用依賴的地方需要做非空堅持.使用setter注入的一個好處就是可以重新配置或注入對象.`JMX MBeans`的管理就是setter的注入方式
>依賴注入的風格對一些特殊類很有意義,當處理第三方類且沒有源碼,你沒有可選的.例如第三方類庫,沒有暴露出任何setter方法,那么構造注入的方式是依賴注入的最后一句話.
>
## Dependency resolution process
容器處理依賴的過程表現為以下幾步:
* ApplicationContext創建并初始化,且包含所有配置的bean,配置信息可以使用xml,java 代碼或注解.
* 每一個bean,它的依賴表現為屬性,構造參數,靜態方法的參數(當做構造參數),只有當bean實際創建的時候才提供這些依賴給它.
* 每一個屬性或構造參數實際上定義的一個需要設置的值,或引用容器中的另一個bean
* 作為值的屬性或構造參數,從指定格式轉為實際類型.默認spring可以吧字符串的值轉為所有內置類型,如int,long,String等.
容器創建的時候會校驗所有bean的配置信息.然而,bean的屬性在bean創建的時候才會設置.bean默認是單例且預先實例化的,隨容器創建而創建.bean的范圍定義參考[Scope](https://docs.spring.io/spring/docs/5.0.6.RELEASE/spring-framework-reference/core.html#beans-factory-scopes).另外一種就是在需要的時候才創建bean,然后在依次創建依賴,這種機制在第一次使用的時候回表現的會慢一點.
>:-: 循環依賴
>
>如果你主要使用構造方法注入,可能導致無法解決的循環依賴.
>比如,class A需要通過構造方法注入一個class B的實例,同時class B也需要通過構造方法注入一個class A的實例,通過配置使A和B之間相互注入,spring容器在運行時會檢測到循環引用,然后拋出異常`BeanCurrentlyInCreationException`
>一種解決方案就是,把構造注入方式改為setter方式,換句話說,可以用setter方式配置循環依賴
>
你要相信spring會做正確的事情.在容器加載時,校驗配置問題,如引用了不存在的bean和循環依賴.bean創建時,spring會盡可能晚的設置屬性解決依賴.這意味著容器正確加載之后,當你需要一個創建失敗的bean或依賴也會產生異常.例如,找不到這個bean的異常或無效的屬性.ApplicationContext默認實現預先實例化單例bean的機制會把配置問題的暴露向后推遲.想要在創建ApplicationContext的時候就發現配置問題,在實際需要對象之前就花費時間和內存預先把對象創建好.你還可以重寫默認的行為使單例bean懶加載替換預先實例化.
如果沒有循環依賴,當協作bean注入依賴bean之前,協作bean優先完全配置好.就是說A依賴B,spring會優先配置好B之后再調用set方法把B注入A
總之,bean先實例化,然后設置依賴,再調用相關生命周期方法.
## Examples of dependency injection
下面是setter方式注入
~~~xml
<bean id="exampleBean" class="examples.ExampleBean">
<!--使用子元素ref-->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!--使用屬性ref-->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
~~~
~~~java
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
~~~
接下來是構造方法的注入
~~~xml
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 子元素ref-->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- ref屬性 -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
~~~
~~~java
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
~~~
接下來是靜態工廠方法
~~~xml
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
~~~
~~~java
public class ExampleBean {
// 私有的構造方法
private ExampleBean(...) {
...
}
//靜態工廠方法,參數可 認為是這個bean的依賴
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
~~~
靜態工廠方法的參數使用`<constructor-arg/>`元素,等同于構造參數,但這里不一定就返回這個bean本身,也可以是其他bean.實例工廠方法(非靜態)的使用和這里是一樣的,就不細說了.
- 正確打開本書的姿勢
- 第一部分 Core
- 1. Ioc container
- 1.1. Introduction to the Spring IoC container and beans
- 1.2. Container overview
- 1.2.1. Configuration metadata
- 1.2.2. Instantiating a container
- 1.2.3. Using the container
- 1.3. Bean overview
- 1.3.1. Naming beans
- 1.3.2. Instantiating beans
- 1.4. Dependencies
- 1.4.1. Dependency Injection
- 1.4.2. Dependencies and configuration in detail
- 1.4.3. Using depends-on
- 1.4.4. Lazy-initialized beans
- 1.4.5. Autowiring collaborators
- 1.4.6. Method injection
- 1.5 Bean Scopes
- 1.6. Customizing the nature of a bean TODO
- 1.7. Bean definition inheritance TODO
- 1.8. Container Extension Points TODO
- 1.9. Annotation-based container configuration
- 1.9.1. @Required
- 1.9.2. @Autowired
- 1.9.3. Fine-tuning annotation-based autowiring with @Primary
- 1.9.4. Fine-tuning annotation-based autowiring with qualifiers TODO
- 1.9.5. Using generics as autowiring qualifiers TODO
- 1.9.6. CustomAutowireConfigurer TODO
- 1.10. Classpath scanning and managed components
- 1.10.1. @Component and further stereotype annotations
- 1.11. Using JSR 330 Standard Annotations TODO
- 1.12. Java-based container configuration
- 1.12.1. Basic concepts: @Bean and @Configuration
- 1.12.2. Instantiating the Spring container using AnnotationConfigApplicationContext
- 2. Resources
- 2.1. Introduction
- 2.2. The Resource interface
- 2.3. Built-in Resource implementations
- 2.3.1. UrlResource
- 2.3.2. ClassPathResource
- 2.3.3. FileSystemResource
- 2.3.4. ServletContextResource
- 2.3.5. InputStreamResource
- 2.3.6. ByteArrayResource
- 2.4. The ResourceLoader
- 2.5. The ResourceLoaderAware interface
- 2.6. Resources as dependencies
- 2.7. Application contexts and Resource paths
- 2.7.1. Constructing application contexts
- 2.7.2. Wildcards in application context constructor resource paths
- 2.7.3. FileSystemResource caveats
- 3. Validation, Data Binding, and Type Conversion
- 4. Spring Expression Language (SpEL)
- 5. Aspect Oriented Programming with Spring
- 5.1. Introduction
- 5.1.1. AOP concepts
- 5.1.2. Spring AOP capabilities and goals
- 5.1.3. AOP Proxies
- 5.2. @AspectJ support
- 5.2.1. Enabling @AspectJ Support
- 5.2.2. Declaring an aspect
- 5.2.3. Declaring a pointcut
- 5.2.4. Declaring advice
- 5.2.5. Introductions TODO
- 5.2.6. Aspect instantiation models TODO
- 5.2.7. Example
- 5.3. Schema-based AOP support TODO
- 5.4. Choosing which AOP declaration style to use TODO
- 5.5. Mixing aspect types TODO
- 5.6. Proxying mechanisms
- 5.6.1. Understanding AOP proxies
- 5.7. Programmatic creation of @AspectJ Proxies
- 5.8. Using AspectJ with Spring applications
- 5.8.1. Using AspectJ to dependency inject domain objects with Spring
- 5.8.2. Other Spring aspects for AspectJ
- 第二部分 Testing
- 第三部分 Data Access
- 1. Transaction Management
- 1.1. Introduction to Spring Framework transaction management
- 1.2 Advantages of the Spring Framework’s transaction support model
- 1.2.1. Global transactions
- 1.2.2. Local transactions
- 1.2.3. Spring Framework’s consistent programming model
- 1.3. Understanding the Spring Framework transaction abstraction
- 1.4. Synchronizing resources with transactions
- 1.4.1. High-level synchronization approach
- 1.4.2. Low-level synchronization approach
- 1.4.3. TransactionAwareDataSourceProxy
- 1.5. Declarative transaction management
- 1.5.1. Understanding the Spring Framework’s declarative transaction implementation
- 1.5.2. Example of declarative transaction implementation
- 1.5.3. Rolling back a declarative transaction
- 1.5.4. Configuring different transactional semantics for different beans
- 1.5.5. tx:advice元素的 settings
- 1.5.6. Using @Transactional
- 1.5.7. Transaction propagation
- 1.5.8. Advising transactional operations
- 1.5.9. Using @Transactional with AspectJ TODO
- 第四部分 web servlet
- 第五部分 Web Reactive
- 第六部分 Integration
- 第七部分 Languages