前面说过IoC容器实现控制反转的核心就是DI——依赖注入。对象之间的依赖关系无非会通过以下几种方式来实现:构造器的参数、工厂方法的参数,或给由构造函数或者工厂方法创建的对象设置属性。所以,IoC容器的工作就是创建bean时注入那些依赖关系。
相对于由bean自己来控制其实例化、直接在构造器中指定依赖关系或则类似服务定位器(ServiceLocator)模式这3种自主控制依赖关系注入的方法来说,控制从根本上发生了倒转,这也正是控制反转(Inversionof Control, IoC) 名字的由来。
应用DI原则后,代码将更加清晰。而且当bean自己不再担心对象之间的依赖关系(以及在何时何地指定这种依赖关系和依赖的实际类是什么)之后,实现更高层次的松耦合将更加容易。
下面主要介绍DI的两种注入方式,即Setter注入和 构造器注入:
通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即可实现基于setter的DI:
package com.bjpowernode.spring.manager; import com.bjpowernode.spring.dao.UserDao; import com.bjpowernode.spring.dao.UserDao4MysqlImpl; import com.bjpowernode.spring.dao.UserDao4OracleImpl; public class UserManagerImpl implements UserManager { private UserDao userDao; //无参构造器 public UserManagerImpl() { super(); } //setter方式注入 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void addUser(String username, String password) { //原始的方式--由我们的程序负责服务(对象)管理 //UserDao userDao = new UserDao4MysqlImp(); //UserDao userDao = new UserDao4OracleImpl(); userDao.addUser(username, password); } }
配置--applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <bean id="UserDao4MysqlImpl" class="com.bjpowernode.spring.dao.UserDao4MysqlImpl"/> <bean id="UserDao4OracleImpl" class="com.bjpowernode.spring.dao.UserDao4OracleImpl"/> <bean id ="UserManager" class="com.bjpowernode.spring.manager.UserManagerImpl"> <!-- <constructor-arg ref="UserDao4OracleImpl"/> --> <property name="userDao" ref="UserDao4OracleImpl"/> </bean> </beans>
基于构造器的DI通过调用带参数的构造器来实现,每个参数代表着一个协作者。此外,还可通过给静态工厂方法传参数来构造bean:
package com.bjpowernode.spring.manager; import com.bjpowernode.spring.dao.UserDao; import com.bjpowernode.spring.dao.UserDao4MysqlImpl; import com.bjpowernode.spring.dao.UserDao4OracleImpl; public class UserManagerImpl implements UserManager { private UserDao userDao; //构造方法方式注入 public UserManagerImpl(UserDao userDao) { this.userDao = userDao; } @Override public void addUser(String username, String password) { //原始的方式--由我们的程序负责服务(对象)管理 //UserDao userDao = new UserDao4MysqlImp(); //UserDao userDao = new UserDao4OracleImpl(); userDao.addUser(username, password); } }
配置--applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <bean id="UserDao4MysqlImpl" class="com.bjpowernode.spring.dao.UserDao4MysqlImpl"/> <bean id="UserDao4OracleImpl" class="com.bjpowernode.spring.dao.UserDao4OracleImpl"/> <bean id ="UserManager" class="com.bjpowernode.spring.manager.UserManagerImpl"> <constructor-arg ref="UserDao4OracleImpl"/> <!-- <property name="userDao" ref="UserDao4OracleImpl"/> --> </bean> </beans>
无论是对象、还是属性,只要给入口(set方法),Spring就可以注入对象或属性。
Spring能够以String类型提供值转换成各种内置类型,比如int、long、String、boolean等。实例如下:
【class Bean1】
package com.bjpowernode.spring; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; public class Bean1 { private String strValue; private int intValue; private List listValue; private Set setValue; private String[] arrayValue; private Map mapValue; public String getStrValue() { return strValue; } public void setStrValue(String strValue) { this.strValue = strValue; } public int getIntValue() { return intValue; } public void setIntValue(int intValue) { this.intValue = intValue; } public List getListValue() { return listValue; } public void setListValue(List listValue) { this.listValue = listValue; } public Set getSetValue() { return setValue; } public void setSetValue(Set setValue) { this.setValue = setValue; } public String[] getArrayValue() { return arrayValue; } public void setArrayValue(String[] arrayValue) { this.arrayValue = arrayValue; } public Map getMapValue() { return mapValue; } public void setMapValue(Map mapValue) { this.mapValue = mapValue; } }
【配置--applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <bean id="bean1" class="com.bjpowernode.spring.Bean1"> <property name="strValue" value="Hello_Spring"/> <!-- <property name="intValue" value="123"/> --> <property name="intValue"> <value>456</value> </property> <property name="listValue"> <list> <value>list1</value> <value>list2</value> <value>list3</value> </list> </property> <property name="setValue"> <set> <value>set1</value> <value>set2</value> <value>set3</value> </set> </property> <property name="arrayValue"> <list> <value>array[0]</value> <value>array[1]</value> <value>array[2]</value> </list> </property> <property name="mapValue"> <map> <entry key="k1" value="v1"/> <entry key="k2" value="v1"/> <entry key="k3" value="v1"/> </map> </property> </bean> </beans>
【测试用例—InjectionTest】
需要引入测试用例的jar包-- junit.jar,目录:spring-framework\lib\junit\ junit.jar
package test; import junit.framework.TestCase; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.bjpowernode.spring.Bean1; public class InjectionTest extends TestCase { private BeanFactory factory; @Override protected void setUp() throws Exception { factory = new ClassPathXmlApplicationContext("applicationContext.xml");//读取一个配置文件 } @Override protected void tearDown() throws Exception { //结束时需要释放的资源 } public void testInjection1(){ Bean1 bean1=(Bean1)factory.getBean("bean1"); System.out.println("bean1.strValue="+ bean1.getStrValue()); System.out.println("bean1.IntValue="+ bean1.getIntValue()); System.out.println("bean1.listValue="+ bean1.getListValue()); System.out.println("bean1.setValue="+ bean1.getSetValue()); System.out.println("bean1.arrayValue="+ bean1.getArrayValue()); System.out.println("bean1.mapValue="+ bean1.getMapValue()); System.out.println("bean1.dateValue="+ bean1.getDateValue()); } }
输出结果:
可以看到Spring容器已将值注入到了Bean1的属性中。
上面提到了,Spring能够以String类型提供值转换int、long、String、boolean等兼容类型,但它无法直接转换为Date等不兼容的类型,这样的转换需要手动写属性编辑器来完成。
例如上面的例子中,给Bean1中加一个Date类型的属性,如下:
private Date dateValue; public Date getDateValue() { return dateValue; } public void setDateValue(Date dateValue) { this.dateValue = dateValue; }
那么直接在Spring配置中单纯的配置如下的属性映射,是会报类型转换错误的:
<property name="dateValue" value="2014-11-27"/>
还需要手动写一个属性编辑器,来完成字符串到Date类型之间的转换,如下【class UtilDatePropertyEditor】:
package com.bjpowernode.spring; import java.beans.PropertyEditorSupport; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * java.util.Date属性编辑器 */ public class UtilDatePropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { System.out.println("---UtilDatePropertyEditor.setAsText()--->"+text); try { Date date = new SimpleDateFormat("yyyy-MM-dd").parse(text); this.setValue(date); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); throw new IllegalArgumentException(); } } }
然后需要将属性编辑器注入到CustomEditorConfigurer类的customEditors属性(为Map类型)中。再添加一个Spring的核心配置文件【applicationContext-editor.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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <bean id="customEditors" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="java.util.Date"> <bean class="com.bjpowernode.spring.UtilDatePropertyEditor"/><!-- 内部bean(不用id属性) --> </entry> </map> </property> </bean> <!-- <bean id="utilDatePropertyEditor" class="com.bjpowernode.spring.UtilDatePropertyEditor"></bean> --> </beans>
然后更改测试用例即可,主要是构造BeanFactory时参数中添加刚添加的另一个Spring的配置文件:
String[] configLocations = new String[]{"applicationContext.xml","applicationContext-editor.xml"}; //factory = new ClassPathXmlApplicationContext("applicationContext.xml");//读取一个配置文件 factory = new ClassPathXmlApplicationContext(configLocations);//读取多个配置文件
然后就可以正确地将字符串注入到Date类型的属性中。
由于大量的构造器参数可能使程序变得笨拙,特别是当某些属性是可选的时候。因此通常情况下,Spring开发团队提倡使用setter注入。而且setter DI在以后的某个时候还可将实例重新配置(或重新注入)(JMX MBean就是一个很好的例子)。
尽管如此,构造器注入,在某些特定的场景下还是很受青睐的。对于注入类型的选择并没硬性的规定。根据实际的业务场景,只要能适合你的应用,无论使用何种类型的DI都可以。