通过以上的学习,对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:参数的类型,值为基本类型或者完全限定的类名。
基本Bean
下面采用代码加注释的形式分别对以上的配置项加以说明,引用的类库见以前的文章,首先是简单的代码结构截图:
Fruit
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
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 + "]"; } }
BeanXMl配置
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标签
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
引用其他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>
内部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>
Null和空串
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="[email protected]"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="[email protected]"/> </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。但本人水平有限,肯定有很多不当和不完整支持,期待共同进步,本文中的实例用到的代码地址为:本文资源下载地址(免积分)