Spring的依赖注入

1.  概念简介

  前面说过IoC容器实现控制反转的核心就是DI——依赖注入。对象之间的依赖关系无非会通过以下几种方式来实现:构造器的参数、工厂方法的参数,或给由构造函数或者工厂方法创建的对象设置属性。所以,IoC容器的工作就是创建bean时注入那些依赖关系。

  相对于由bean自己来控制其实例化、直接在构造器中指定依赖关系或则类似服务定位器(ServiceLocator)模式这3种自主控制依赖关系注入的方法来说,控制从根本上发生了倒转,这也正是控制反转(Inversionof Control, IoC) 名字的由来。

  应用DI原则后,代码将更加清晰。而且当bean自己不再担心对象之间的依赖关系(以及在何时何地指定这种依赖关系和依赖的实际类是什么)之后,实现更高层次的松耦合将更加容易。

  下面主要介绍DI的两种注入方式,即Setter注入和 构造器注入:

2.  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>

3.  构造器注入

  基于构造器的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>

4.  Setter注入实例

  无论是对象、还是属性,只要给入口(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的依赖注入_第1张图片


  可以看到Spring容器已将值注入到了Bean1的属性中。


5.  属性编辑器

  上面提到了,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类型的属性中。


6.  总结

  由于大量的构造器参数可能使程序变得笨拙,特别是当某些属性是可选的时候。因此通常情况下,Spring开发团队提倡使用setter注入。而且setter DI在以后的某个时候还可将实例重新配置(或重新注入)(JMX MBean就是一个很好的例子)。

  尽管如此,构造器注入,在某些特定的场景下还是很受青睐的。对于注入类型的选择并没硬性的规定。根据实际的业务场景,只要能适合你的应用,无论使用何种类型的DI都可以。

你可能感兴趣的:(Spring的依赖注入)