1 DI 简介
DI(Dependency Injection)
即依赖注入,提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过 JavaBean 属性或者构造函数传递给需要的对象。
注意:通常,我们会将 IoC 与 DI 作为同一个概念理解,其实 IoC 是一个很大的概念,DI 只是其中一个比较流行的实现策略。只是对于 Spring 而言,其 IoC 容器就是通过 DI 实现的,所以也就不再严格区分。
2 DI 的方式
在 Spring 中,主要有以下两种方式:
- 设值注入:通过JavaBean属性注射依赖关系
- 构造器注入:将依赖关系作为构造函数参数传入
2.1 设值注入
JavaBean
package com.jyhuang.spring.DI.setter_based;
public class People {
private String name;
private int age;
// 使用设值注入,必须提供对应属性的 setter 方法
// getter...setter
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
applicationContext.xml
使用 <property>
标签进行属性的注入,其中标签属性 name
指定 JavaBean 中对应的属性名,标签属性 value
指定需要注入的值,这两个标签都是必须的。
<bean id="people" class="com.jyhuang.spring.DI.setter_based.People">
<property name="name" value="Tom"/>
<property name="age" value="20"/>
</bean>
测试方法
@Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/jyhuang/spring/DI/setter_based/applicationContext.xml");
People people = (People) ctx.getBean("people");
System.out.println(people.toString());
}
测试结果
People{name='Tom', age=20}
2.2 构造器注入
JavaBean
package com.jyhuang.spring.DI.constructor_based;
public class People {
private String name;
private int age;
public People() {}
// 使用构造器注入,必须提供以对应属性为参数的构造方法
public People(String name, int age) {
this.name = name;
this.age = age;
}
// getter...setter
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
applicationContext.xml
使用 <constructor-arg>
标签进行构造器注入。
其中,除了可以通过最常用的标签属性 name
和 value
进行注入外,index
和 type
属性同样可以,这两个属性分别对应构造器中传参的索引和参数类型。三个属性可单独或组合使用,但必须保证最终能精确定位到需要注入值的那个参数。
与 <property
不同,此标签的 name
属性不是必须的。默认情况下,会根据参数类型自动注入对应的属性,只需要指定 value
即可,即使配置的顺序与构造器的传参顺序不一致,一样能正确注入,但前提是能通过参数类型唯一定位;如果参数类型一致,Spring 会根据配置顺序依次注入。为了避免麻烦,建议配置时手动定位。
<bean id="people" class="com.jyhuang.spring.DI.constructor_based.People">
<!--<constructor-arg name="name" value="Tom"/>-->
<!--<constructor-arg name="age" value="20"/>-->
<constructor-arg index="0" type="java.lang.String" name="name" value="Tom"/>
<constructor-arg index="1" type="int" name="age" value="20"/>
</bean>
测试方法
@Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/jyhuang/spring/DI/constructor_based/applicationContext.xml");
People p = (People) ctx.getBean("people");
System.out.println(p);
}
测试结果
People{name='Tom', age=20}
3 DI 的类型
除了之前例子中提及到的基本数据类型和 String 等直接值以外,依赖注入的类型还有很多,下面作简要说明。
3.1 其他 bean
注入其他 JavaBean,也叫合作 bean 或者协同 bean,主要有以下两种方式:idref
和 ref
,这两个元素都可以作为 <constructor-arg>
和 <property>
标签的子元素来指定需要注入的 bean。
3.1.1 idref 元素
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean" />
</property>
</bean>
上面的配置,在运行时完全等价于下面的配置
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean" />
</bean>
idref bean
和 property value
注入的值都是字符串类型,只是 idref
注入的是容器中已经存在的 bean 的 id 值,该元素在容器发布时,会对注入的值进行校验,判断容器中是否真的存在这样一个 bean,该 bean 的 id 与 idref bean
配置值相同,而 property value
则会直接把值作为字符串注入。
注意:idref
注入的是字符串类型的值,只是这个值必须对应某个 bean 的 id 值,它并不是真地注入一个 bean,所以,接收该值的属性也必须是字符串。
另外,在 4.0 版本之前,idref
有个 local
属性,但从 4.0 的 beans xsd 开始已经不再被支持。当升级版本时,只需要简单地将 idref local
修改成 idref bean
即可。
3.1.2 ref 元素
与 idref
元素不同,ref
元素注入的是 bean 实例,而且,ref bean
的值不仅可以是其他 bean 的 id 属性值,还可以是 name 属性值。
3.1.2.1 ref bean
<ref bean="someBean"/>
3.1.2.2 ref local
这个和 idref
一样,从 4.0 的 beans xsd 开始,Spring 就不再支持,此处不再说明。
3.1.2.3 ref parent
通过使用 ref parent
来引用当前容器的父容器中的 bean。使用 parent
属性的主要用途是为了用某个与父容器中的 bean 同名的代理来包装父容器中的一个 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>
另外,ref
不仅可以作为 <constructor-arg>
和 <property>
的子标签,还可以作为属性进行 bean 的注入,此时,就等价于 ref bean
。
<property name="target" ref="someBean"/>
3.2 内部 bean
使用 <bean/>
标签在 <constructor-arg>
和 <property>
中定义内部 bean。此时,无须为该内部 bean 指定 id/name 属性,即使指定,容器也会忽略,因为该内部 bean 只能给它对应的外部 bean 使用。另外,对于内部 bean 的 scope 属性,容器也会忽略,因为内部 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>
3.3 集合
Spring 主要提供了如下类型的集合注入
- List:
<list/>
- Set:
<set/>
- Map:
<map/>
- Properties:
<props/>
3.3.1 常规使用
<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>
其中,map 的 key/value 和 set 的 value 还可以通过下面任意一种元素指定:
bean | ref | idref | list | set | map | props | value | null
3.3.2 集合整合
集合整合实际就是集合继承,Spring 是支持一个应用定义父类型的集合元素,然后使用子类型的集合元素去继承和覆盖父类型集合元素中的值,只需要在子集合定义时显示的指定 merge=true
,适用于上述列举的四种集合类型。
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意:在整合中,如果父子集合类型不一致,是不能整合的。
3.3.3 强类型集合
从 Java 5 开始引入了泛型,所以,对于指定了元素类型的集合注入,需要注意类型匹配问题。由于 Spring 自己提供了基本类型转换支持,所以,默认情况下,Spring 会根据目标类型对注入的值进行自动转换。
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>
此例中,当注入 9.99
,2.75
和 3.99
时,都会自动转换成 Float 类型。
3.4 Null 或空字符串
3.4.1 空字符串
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的配置等价于
exampleBean.setEmail("")
3.4.2 Null
null 值的注入比较特殊,需要使用 <null/>
元素来完成。
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上面的配置等价于
exampleBean.setEmail(null)
3.5 复合/嵌套属性名
Spring 支持 对象.属性
这种嵌套的方式进行属性值的注入,但必须保证在嵌套过程的对象/属性必须存在且不能为 null,否则,会抛出异常。
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
上例中,Bar
必须有 fred
对象作为属性,fred
对象必须有 bob
对象作为属性,bob
对象必须有 sammy
对象作为属性;在注入 123
之前,Bar
,fred
和 bob
对象必须已经初始化,不能为 null,否则会抛出 NullPointerException
。