依赖注入 - Dependency Injection
当容器创建bean的时候会注入这些依赖。
当为对象提供依赖对象时,代码会比较整洁同时会解耦。对象不需要查找它们的依赖,也不需要知道这些依赖所对应的类或位置。
(1)基于构造函数的依赖注入
基于构造函数的依赖通过容器调用构造函数来实现,构造函数可能包含若干个参数,每一个代表一个依赖。
例如:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
当使用参数类型匹配时,如果不存在歧义,默认的,bean定义中构造函数参数的顺序就是实际传入参数时的顺序。
假设Bar 和 Baz是不相关的类型,则不存在类型歧义。因此,下列配置是合法的,不需要在<constructor-arg>元素中显式的指定构造函数参数索引或者类型,
<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>
当使用简单类型时,例如<value>true</value>,Spring不能确定该值的类型,此时需要指定其他的属性,例如:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
在这个例子中,可以使用 type 属性来指定构造函数参数的类型,
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
或者使用 Index 属性显式地指定构造函数参数的索引,
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
注意,index是从 0 开始计数的。
同样,也可以使用构造函数参数名来避免歧义,例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
注意,当使用参数名来指定依赖时,需要开启debug编译选项,否则Spring无法找到该构造函数的参数名。如果没有开启 debug 编译选项,可以使用 @ConstructProperties 注解显式的指定构造函数的参数名,例如:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
(2)基于Setter方法的依赖注入
该方法在使用无参数的构造函数或者无参数的厂方法实例化bean之后,通过容器调用该bean的 setter 方法来完成依赖注入。
例如:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext处理支持基于构造函数的注入和基于 setter 方法的注入外,也支持在使用构造函数注入部分依赖后,再使用基于 setter 方法来注入其余的依赖。
依赖解析过程
容器如下完成bean的依赖解析:
- ApplicationContext被创建,并使用配置元数据来初始化
- 对于每一个bean, 它的依赖可能是属性,构造函数的参数,静态厂方法的参数等形式;当bean实际创建的时候,这些依赖被提供给bean
- 每一个属性或者构造函数的参数都是一个实际的值,或者是存在于容器中的另一个bean
- 每一个属性或者构造函数的参数,如果其是值类型,那么该值被转换为属性或参数所需的实际的类型
当容器被创建的时候,Spring容器会验证每一个 bean 的配置;但是,对于bean的属性,直到bean被创建的时候才会被设置。
单例范围的bean 和提前初始化的bean在容器被创建的时候就会被创建。否则,直到该bean被请求的时候才会被创建。
当创建 bean 的时候,其依赖即依赖的依赖都会被创建并赋值。
依赖注入示例
- 基于 setter 方法的依赖注入:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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;
}
}
- 基于构造函数的依赖注入:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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;
}
}
- 使用厂方法的依赖注入:
<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"/>
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
Bean依赖的配置细节
Spring可以定义bean的属性和构造函数的参数为其他beans的引用; Spring的基于XML格式的配置元数据支持在 <property/> 和 <construct-arg/>元素中添加子元素:
字面量(primitive, Strings等)
<property/>元素包含一个属性 value,用来以可读的字符串形式指定bean的属性或者构造函数的参数。
Spring的conversion service用来将这些值由String类型转换为属性或参数的实际类型。
例如:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
idref 元素
dref元素用来将容器内其它bean的id传给<constructor-arg/>或<property/>元素,同时提供错误验证功能.
引用其他的beans
ref 元素位于 <construct=arg/> 或者 <property/> 元素定义的内部。用来设置一个bean的指定属性为引用容器中的其他bean。
通过使用<ref/>元素的 bean 属性指定目标bean是最常见的情形,该属性可以用来创建一个位于同一个或父容器(不同XML文件)中的bean的引用。bean属性的值可以是引用bean的id属性,或者是引用bean的name的值。
<ref bean="someBean"/>
通过parent属性来指定引用的bean,该引用bean位于当前容器的父容器。引用bean必须位于当前容器的父容器,例如:
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
内部beans
位于<property/>或者<constructor-arg/>元素内部的<bean/>元素称作内部bean:
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部不需要id 或者 name, 容器会忽略这些值。也会忽略 scope 属性。 内部bean通常是匿名bean,通常被外部bean所创建。
集合Collections
使用<list>, <set>, <map>, <props>元素,设置Java集合类型的属性或构造函数参数(List, Set, Map, Properties):
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
强类型集合
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="foo" class="x.y.Foo">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当bean foo的属性 accounts准备注入的时候,字符串值 9.99, 2.75, 3.99 被转换成实际的Float类型。
Null 和空字符串值
Spring将为空值的属性看作是空的字符串,例如:
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
等价与下列的代码:
exampleBean.setEmail("")
<null/>元素表示 null, 例如:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
其等价于:
exampleBean.setEmail(null)
depends-on
如果一个bean是另一个bean的依赖,意味着这个bean是另一个bean的属性。通常,使用<ref/>元素进行设置。但,有时bean之间的依赖不是非常直接,例如,一个类内部的静态初始化器,depends-on属性可以显式的另这些bean在该bean初始化之前被初始化。
例如:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
或:
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
惰性初始化beans
默认,ApplicationContext的实现类会在初始化的过程中创建并配置全部的单件singleton beans。
我们可以使用惰性初始化来禁止单件bean的提前初始化过程。
惰性初始化通知容器在请求这个bean的时候再进行初始化。
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
但是,当一个惰性初始化bean是一个非惰性初始化bean的依赖时, ApplicationContext仍然会在启动的时候创建这个惰性初始化bean,应为需要满足单件bean的依赖注入。
Autowiring collaborators自动装配
Spring容器可以在bean之间进行自动装配。Spring可以通过检测ApplicationContext的内容来自动解析相互合作的beans。
自动装配的优点:
- 减少属性或构造函数参数的显式指定
- 当对象的配置发生改变时,可以自动识别
当使用基于XML的配置元数据时,通过指定<bean/>元素的 autowire属性可以指定bean的自动装配模式。
自动装配功能有5中模式,可以为每个bean的定义指定单独的自动装配属性:
- no - 不自动装配,默认为 no
- byName - 根据属性名称自动装配;Spring查找相同属性名称的bean;例如,一个bean定义设置自动装配模式为by name,其包含一个 master 属性,Spring会查找一个名为 master 的bean定义,并使用它作为属性
- byType - 使用容器中存在的相同类型的bean作为属性进行自动装配。如果存在多个,将抛出异常;如果没有匹配的bean,该属性将不会被设置
- constructor -
自动装配的缺点:
- 显式的依赖常常会覆盖自动装配
- 自动装配的准确性差
方法注入