通过以上的学习,对spring容器和DI的概念应该比较清晰了,DI(依赖注入)作为spring的核心,spring当然提供了一套完善的机制来进行依赖注入。前篇文章从概念上介绍了依赖注入,本篇着重学习spring依赖注入的方法,这里主要采用xml的方式。
构造器注入和设值注入是依赖注入的两种主要方式,spring对此有很完善的实现,下面首先以代码的形式进行简要的说明。
Spring容器通过调用bean的构造函数(可能带有几个参数,每个参数代表一个依赖)完成构造器注入。通过静态工厂方法和调用构造器及其类似,这里一并进行说明。
Spring中构造器依赖注入采用以下的形式,每个参数用bean标签的子标签 < constructor-arg >进行配置:
<bean id="..." class="com.test.wdi.ExampleBean">
<constructor-arg index="..." value="..." name="..." ref="..." type="..."></constructor-arg>
.. .. ..
</bean>Index:在构造函数中参数的位置,从0开始<constructor-arg >标签中的配置包含5个项,使用他们中的某些进行某个依赖的配置,简单的描述如下:
Value:参数值,通常是基本类型和 type或者index配合使用。
Name:构造函数中参数的名字
Ref:引用其他Spring管理的对象
Type:参数的类型,值为基本类型或者完全限定的类名。
下面采用代码加注释的形式分别对以上的配置项加以说明,引用的类库见以前的文章,首先是简单的代码结构截图:
Apple、Banana是用来演示ref配置项的,只是一个简单的类,没有任何字段。Fruit包含一个Apple和一个Banana,其代码如下:
package com.test.wdi;
/**
* @date 2015-2-27
* @Description:此类主要是为了说明构造器注入的ref项,包含了基本的构造函数,静态工厂方法,和实例工厂方法
*/
public class Fruit {
private Apple apple;
private Banana banana;
public Fruit() {
super();
}
/**
* 静态工厂方法
*/
public static Fruit newInstance(Apple apple, Banana banana){
return new Fruit(apple, banana);
}
/**
* 实例工厂方法
*/
public Fruit newInstance1(Apple apple, Banana banana){
return new Fruit(apple, banana);
}
/**
* 传统带参构造函数
*/
public Fruit(Apple apple, Banana banana) {
super();
this.apple = apple;
this.banana = banana;
}
public Apple getApple() {
return apple;
}
public void setApple(Apple apple) {
this.apple = apple;
}
public Banana getBanana() {
return banana;
}
public void setBanana(Banana banana) {
this.banana = banana;
}
@Override
public String toString() {
return hashCode()+ "Fruit [apple=" + apple + ", banana=" + banana + "]";
}
}ExampleBean是用来演示除ref之外的其他配置项的,它有连个基本类型的字段,其代码如下:
package com.test.wdi;
/**
* @date 2015-2-28
* @Description:此类用来说明构造器注入配置项中的 name index type value的,为了方便重写了toString方法
*/
public class ExampleBean {
private int years;
private String ultimateAnswer;
private boolean test;
public ExampleBean() {
super();
}
/**
* 构造函数2,和构造函数1一样具有两个参数,如果bean配置中不加入type的限制,
* 将会有和预期不一致的结果
*/
public ExampleBean(int years, boolean test) {
this.years = years;
this.test = test;
}
/**
* 构造函数1
*/
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
@Override
public String toString() {
return "ExampleBean [years=" + years + ", ultimateAnswer=" + ultimateAnswer + ", test=" + test + "]";
}
}Bean的配置,将依次说明<constructor-arg >标签中每个配置属性的简单用法和它们的组合。bean配置包含连个文件,allbean.xml是主配置文件,它通过import标签引入t1.xml,而t1.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
此配置文件是spring 元数据的配置文件,根标签为 beans标签。
在这个标签下可以定义由Spring 管理的 bean,其定义形式如下被注释的地方所示:
基本的配置包括一个容器全局唯一的字符串id,和一个完全限定的类名。
但在这个例子中,有两个import标签,分别引入了另外两个 xml元数据配置文件。
这么做的好处是,不同的 bean定义对应相应的程序体系架构。
import标签导入的配置文件路径是相对路径,相对于当前文件。
路径前面可以与斜杠,但不建议这么做。如下示例不推荐使用
<import resource="/com/test/dao/dao.xml"/>
类似../的路径也不推荐使用
-->
<!-- 测试spring依赖注入 -->
<import resource="com/test/wdi/t1.xml"/>
</beans><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义一个Apple Bean -->
<bean id="a" class="com.test.wdi.Apple">
</bean>
<!-- 定义一个Banana Bean -->
<bean id="b" class="com.test.wdi.Banana">
</bean>
<!--传统的构造函数注入,采用ref配置,引用spring管理的其他bean -->
<bean id="f1" class="com.test.wdi.Fruit">
<constructor-arg ref="a"></constructor-arg>
<constructor-arg ref="b"></constructor-arg>
</bean>
<!--静态工厂方法注入,将调用Fruit 的静态方法 newInstance, 采用ref配置,引用spring管理的其他bean -->
<bean id="f2" class="com.test.wdi.Fruit" factory-method="newInstance">
<constructor-arg ref="a"></constructor-arg>
<constructor-arg ref="b"></constructor-arg>
</bean>
<!--实例工厂方法注入,将调用对象 f2 的静态方法 newInstance, 采用ref配置,引用spring管理的其他bean -->
<bean id="f3" factory-bean="f2" factory-method="newInstance1">
<constructor-arg ref="a"></constructor-arg>
<constructor-arg ref="b"></constructor-arg>
</bean>
<!--直接采用 value配置项,在没有模糊性的前提下,spring能直接把value转化为正确的类型,但这里存在模糊性,因为ExampleBean中存在两个具有两个参数的构造函数
据测试,哪个构造函数在前,就会优先使用哪个构造函数,在不匹配的情况下,依次类推。
下面两个配置第一个使用 public ExampleBean(int years, boolean test)
第二个因为true1 不能转化为 bool类型,则使用构造函数
public ExampleBean(int years, String ultimateAnswer)
但是问题在于,假如第一个配置的初衷是把 ExampleBean.ultimateAnswer的值设为"true" ,那么就和预期不一样。
接下来的配置来解决以上问题
-->
<bean id="exam1a" class="com.test.wdi.ExampleBean">
<constructor-arg value="1111"></constructor-arg>
<constructor-arg value="true"></constructor-arg>
</bean>
<bean id="exam1b" class="com.test.wdi.ExampleBean">
<constructor-arg value="1111"></constructor-arg>
<constructor-arg value="true1"></constructor-arg>
</bean>
<!--直接采用 value配置项,采用type进行限定,使用正确的构造器型 -->
<bean id="exam2" class="com.test.wdi.ExampleBean">
<constructor-arg type="int" value="1"></constructor-arg>
<constructor-arg type="java.lang.String" value="true"></constructor-arg>
</bean>
<!--直接采用 value配置项,采用type进行限定,使用正确的构造器型 使用index,可以不按顺序-->
<bean id="exam3" class="com.test.wdi.ExampleBean">
<constructor-arg index="1" value="true" type="java.lang.String"></constructor-arg>
<constructor-arg index="0" value="2"></constructor-arg>
</bean>
<!--直接采用 value配置项,采用name进行限定,使用正确的构造器型 使用index,可以不按顺序
不过使用name 有一些限制,限制在于必须以debug的形式编译类,因为只有这样,才可以保留构造函数中参数的变量名。
或者使用在构造函数中使用注解 @ConstructorProperties
如下
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
-->
<bean id="exam4" class="com.test.wdi.ExampleBean">
<constructor-arg name="ultimateAnswer" value="true" />
<constructor-arg name="years" value="3" />
</bean>
</beans>
测试程序使用main函数,依次打印出来t1中定义的那些bean,代码如下:
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Description:
* 本示例例环境为eclipse
*/
public class TestMain {
public static void main(String[] args) {
/**
* ApplicationContext代表spring容器,而ClassPathXmlApplicationContext是它的一个实现,它从类路径下读取相应的
* xml 元数据配置,并初始化容器。其中allbean.xml是相应的元数据配置
*/
ApplicationContext context = new ClassPathXmlApplicationContext("allbean.xml");
//依次打印对象
System.out.println(context.getBean("f1"));
System.out.println(context.getBean("f2"));
System.out.println(context.getBean("f3"));
System.out.println(context.getBean("exam1a"));
System.out.println(context.getBean("exam1b"));
System.out.println(context.getBean("exam2"));
System.out.println(context.getBean("exam3"));
System.out.println(context.getBean("exam4"));
}
}
测试结果如下截图,均符合预期:
注:构造器注意需要注意循环依赖的问题。
设值注入是spring容器在调用过无参构造函数或者无参工厂方法后,然后通过调用其setter方法进行依赖的注入。
Set注入比较简单,之前的文章spring容器和配置初识所举的例子就是setter注入。下面是一段xml配置极其说明:
<!-- 通过 setter 进行注入,spring首先调用无参的构造函数,然后分别调用其相应的set方法
property 标签有三个属性 name ref 和 value
其中 name是将要被注入的属性的名称。
ref 是引用被spring管理的对象
value 是实际的值,被spring 根据属性的类型,转换为对应的值。
此转换由propertyEditor进行,这里暂且不提,value的值也可以是 spring表达式 spel
-->
<bean id="f4" class="com.test.wdi.Banana" >
<property name="apple" ref="a"></property>
<property name="banana" ref="b"></property>
</bean>依赖有多种类型,包含基本类型(int、double、string等)和复杂类型(系统类和自定义的类),下面依次说明它们在注入过程中需要注意的问题。
设置注入和构造器注入对应的标签分别是<property> 和<constructor-arg>它们的子标签的意义保持一致,下面的例子一般情况下,只做其中一种的说明。另外它们的属性除了 <constructor-arg> 单独拥有的type和 index 其他的也保持一致的意义。
直接类型直接使用value子标签或者属性配置即可。Spring的conversion service 会自动把字符串转化为真正的属性(属性或者构造函数参数)。
对应设置注入,由javabean规范和name属性可以准确的确定类型,故转换不存在问题。但是对于构造器注入,如果不对type或者name进行限制,可能会产生非预期结果。
如下一段配置实是上文中构造器中的一个片段,另外增加了设置注入加以比较。运行结果可以自行测试:
<!--直接采用 value配置项,在没有模糊性的前提下,spring能直接把value转化为正确的类型,但这里存在模糊性,因为ExampleBean中存在两个具有两个参数的构造函数
据测试,哪个构造函数在前,就会优先使用哪个构造函数,在不匹配的情况下,依次类推。下面两个配置第一个使用 public ExampleBean(int
years, boolean test) 第二个因为true1 不能转化为 bool类型,则使用构造函数 public ExampleBean(int
years, String ultimateAnswer) 但是问题在于,假如第一个配置的初衷是把 ExampleBean.ultimateAnswer的
值设为"true" ,那么就和预期不一样。解决的办法是加入type属性的限定,不要只使用参数的个数(对于基本类型会有模糊性) -->
<bean id="exam1a" class="com.test.wdi.ExampleBean">
<constructor-arg value="1111"></constructor-arg>
<constructor-arg value="true"></constructor-arg>
</bean>
<bean id="exam1b" class="com.test.wdi.ExampleBean">
<constructor-arg value="1111"></constructor-arg>
<constructor-arg value="true1"></constructor-arg>
</bean>
<!-- 设值注入有name 可以确定需要转换的类型 -->
<bean id="exam1c" class="com.test.wdi.ExampleBean">
<property name="years" value="1111"></property>
<property name="ultimateAnswer" value="true1"></property>
<property name="test" value="true"></property>
</bean>另外类型为java.util.properties可以采用类似下面的配置:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>Idref子标签只是一个把某个bean的id传递作为依赖传递给另外一个bean的防止错误的方式。通过只传递bean的id,可以构建构建更加灵活的程序。
基本的配置如下:
<!-- 以下两个配置等价 ,这两个配置的初衷在于把 "exam1c" 这个bean id 作为依赖传递给另一个bean
(在此例子中是传递给 id为 exam1d* 的bean ),从而构建更通用的方法。通用的方法比如说是直接从
spring容器中通过id获取相应的对象,执行相应的方法。
区别在于第一种方法 spring容器会验证id 或者name 为 exam1c 的bean在容器(当前容器或者父容器)中真的存在,如果不存在则会报出异常,及早发现,而第二种则不会,直道真正运行时才发现。
-->
<bean id="exam1d1" class="com.test.wdi.ExampleBean">
<property name="years" value="1111"></property>
<property name="ultimateAnswer" >
<idref bean="exam1c"/></property>
<property name="test" value="true"></property>
</bean>
<bean id="exam1d2" class="com.test.wdi.ExampleBean">
<property name="years" value="1111"></property>
<property name="ultimateAnswer" value="exam1c"> </property>
<property name="test" value="true"></property>
</bean>引用其他bean是spring中及其常见的场景,这种配置通过<constructor-arg/> 或<property/> 的子标签<ref>实现,或者通过ref属性(实质上是子标签的一种缩写)。而子标签的形式提供了更具体的用法和配置。
基本样式如下:
<ref bean="someBean"/>
或者
<ref parent="someBean"/>
以上两者的区别是:前者在当前spring容器和父容器中寻找id(name)为someBean 的对象,而后者只在父容器中寻找。
而ref属性是前者的简写。
下面看具体的例子:
<!-- 以下两种配置等同,不同点在于前者是使用标签配置,后者使用属性配置 -->
<bean id="f5a" class="com.test.wdi.Fruit">
<property name="apple">
<ref bean="a" />
</property>
<property name="banana">
<ref bean="b" />
</property>
</bean>
<bean id="f5b" class="com.test.wdi.Fruit">
<property name="apple" ref="a">
</property>
<property name="banana" ref="b"></property>
</bean>在<constructor-arg/> 或<property/>内部定义的bean称之为内部bean,spring会自动忽略其id、name 属性和 scope属性,配置如下:
<!-- 内部bean ,会自动忽略其id、name 属性和 scope属性-->
<bean id="f5c" class="com.test.wdi.Fruit">
<property name="apple">
<bean id="idIgnore" class="com.test.wdi.Apple" ></bean>
</property>
<property name="banana">
<bean class="com.test.wdi.Banana"></bean>
</property>
</bean>Java的强大之处之一在于jdk提供了强大的集合框架,spring当然提供了注入集合的方法。
自从java1.5开始,支持泛型,我们的集合尽量使用泛型,另外关于集合合并涉及到bean定义的继承,这里暂不讨论。
首先引入一个带集合属性的bean,省略了构造函数和get set方法。
public class FruitCollection {
private Set<Fruit> fruits;
private List<Fruit> fruitList;
private Map<String, Fruit> fruitMap;
private Properties ppp;
}具体见下面的配置代码:
<!-- 集合的注入,包括 set list map properties -->
<bean id="fruitSet" class="com.test.wdid.FruitCollection">
<property name="fruits">
<!-- 集合 set标签,可以定义内部bean,引用其他bean -->
<set>
<bean class="com.test.wdi.Fruit">
<property name="apple" ref="a">
</property>
<property name="banana" ref="b"></property>
</bean>
<ref bean="f1" />
<ref bean="f2" />
<ref bean="f3" />
<ref bean="f4" />
<ref bean="f5a" />
<ref bean="f5b" />
<ref bean="f5c" />
</set>
</property>
<property name="fruitList">
<!-- list标签,可以定义内部bean,引用其他bean -->
<list>
<bean class="com.test.wdi.Fruit">
<property name="apple" ref="a">
</property>
<property name="banana" ref="b"></property>
</bean>
<ref bean="f1" />
<ref bean="f2" />
<ref bean="f3" />
<ref bean="f4" />
<ref bean="f5a" />
<ref bean="f5b" />
<ref bean="f5c" />
</list>
</property>
<property name="fruitMap">
<!-- map标签 定义enrty 子标签,属性有 key value key-ref value-ref ,意思很明显-->
<map>
<entry key="f1" value-ref="f1" ></entry>
<entry key="f2" value-ref="f2"></entry>
<entry key="f3" value-ref="f3"></entry>
<entry key="f4" value-ref="f4"></entry>
</map>
</property>
<property name="ppp">
<!-- props标签 定义prop 子标签,本质是字符串类型的键值对-->
<props>
<prop key="1">t1</prop>
<prop key="2">t2</prop>
<prop key="3">t3</prop>
<prop key="4">t4</prop>
</props>
</property>
</bean>Spring支持注入null和字符串的空串,见如下的配置:
<!-- 空串和null -->
<bean id="exam5Null" class="com.test.wdi.ExampleBean">
<property name="years" value="1111"></property>
<property name="ultimateAnswer">
<null/>
</property>
<property name="test" value="true"></property>
</bean>
<bean id="exam5Empty" class="com.test.wdi.ExampleBean">
<property name="years" value="1111"></property>
<property name="ultimateAnswer" value="">
</property>
<property name="test" value="true"></property>
</bean>实际相当于分别执行了如下方法:
setUltimateAnswer(null);
setUltimateAnswer("");Spring的bean配置文件的格式除了以上用了多次的标准形式,还有一些简单的缩略形式,这些缩略形式是基于xml命名空间,分别是p-nameplace和c-nameplace,依次对应seter注入和 构造器注入。
这里不再赘述。直接贴一个spring官方文档的示例来说明p命名空间:
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</beans>
嵌套的注入,fruit 有个apple属性,假设apple 有个 color属性。那么可以在fruit的定义中直接为color注入属性,前提是apple不为null:
<!-- 缩写复合属性名 假设apple 有个 color属性 -->
<bean id="fwithColor" class = "com.test.wdi.Fruit">
<property name="apple" ref="a"></property>
<property name="apple.color" value="yellow"></property>
</bean>
本文从spring两种依赖注入讲起,然后举出具体的配置例子依次讲解到了大部门的注入实例,本文中大部分的例子均通过测试,环境为spring4.1.5。但本人水平有限,肯定有很多不当和不完整支持,期待共同进步,本文中的实例用到的代码地址为:本文资源下载地址(免积分)
原文地址:http://blog.csdn.net/windsunmoon/article/details/44019579