所谓的依赖注入就是指:在运行期,由外部容器动态的将依赖对象注入到组件中。
我们先来看看我们项目的结构(假设操作的是User对象):
数据库操作层接口IUserDao.java:
/** * 数据库操作层的接口 * @author Liao */ public interface IUserDao { /** * 根据id删除 * @param id */ public void delete(int id); }
数据库操作层接口实现类UserDaoImpl.java:
/** * 数据库操作层的实现类 * @author Liao */ public class UserDaoImpl implements IUserDao{ @Override public void delete(int id) { System.out.println("调用了删除方法!"); } }
业务逻辑层接口IUserService.java:
/** * 业务逻辑层接口 * @author Liao */ public interface IUserService { /** * 根据id删除 * @param id */ public void delete(int id); }
业务逻辑层接口实现类UserServiceImpl.java:
/** * 业务逻辑层的接口实现类 * @author Liao */ public class UserServiceImpl implements IUserService{ /*创建Dao*/ private IUserDao userDao; /** * 注入对象的setter方法必须提供 * @param userDao */ public void setUserDao(IUserDao userDao) { this.userDao = userDao; } @Override public void delete(int id) { /*通过数据库操作层的实例来操作数据库*/ userDao.delete(id); } }
这是我们在项目开发中一个大致的结构,为了实现解耦,在业务逻辑实现类中,定义的dao对象,并不会通过直接new dao实现类的方式来创建对象,采用的是Spring的依赖注入方式。
我们先来看第一种方式(在beans.xml文件中配置):
<!-- 注:这个bean是dao的id,可以随便取--> <bean id="userDaoImpl" class="com.lixue.dao.impl.UserDaoImpl"></bean> <!-- 在service实现类中注入到,property的name属性必须是该实现类中的一个变量,并且提供了setter方法,ref指向的是dao的id,不能随便取值--> <bean id="userServiceImpl" class="com.lixue.service.impl.UserServiceImpl"> <property name="userDao" ref="userDaoImpl"></property> </bean>
注:这种方式的思想是先定义dao,然后在往业务逻辑类中注入,这样做有一个好处,那就是我们事先定义了dao,那们可以给任何的业务逻辑类注入该dao,因为我们通过该dao的id就可以定位到这个dao。
第二种方式(在bens.xml文件中配置):
<!-- property的name属性不能随便取值,必须是在UserServiceImpl类中的变量相同 --> <bean id="userServiceImpl" class="com.lixue.service.impl.UserServiceImpl"> <property name="userDao"> <!-- 没有id属性,所以不能被其它service实现类引用 --> <bean class="com.lixue.dao.impl.UserDaoImpl"/> </property> </bean>
注:这种方式非常简洁,但是有一个问题,我们可以看出,它把dao定义在了service实现类的内部,是一个内部bean,这个内部bean是没有id属性,没有id属性就会导致一个问题,那就是只能被当前这个service实现类使用,而不能被其他service实现类直接使用。
注:其实上面这两种注入方式都属于同一种类型,即setter方式注入,这种方式注入必须满足两个条件,一是注入的对象必须提供了setter方法,而是必须有无参的构造函数。
现在我们来看看第二种注入类型,通过构造函数的方式来注入,改变业务逻辑实现类的代码如下:
/** * 业务逻辑层的接口实现类 * @author Liao */ public class UserServiceImpl implements IUserService{ /*创建Dao*/ private IUserDao userDao; /** * 使用构造函数的方式来注入 * @param userDao */ public UserServiceImpl(IUserDao userDao) { this.userDao = userDao; } @Override public void delete(int id) { /*通过数据库操作层的实例来操作数据库*/ userDao.delete(id); } }
构造函数注入的配置(beans.xml文件中配置):
<!-- 定义要注入的dao的id,这个可以随便取值 --> <bean id="userDao" class="com.lixue.dao.impl.UserDaoImpl"></bean> <!-- constructor-arg的ref属性必须和dao的id一致,不能乱来 --> <bean id="userServiceImpl" class="com.lixue.service.impl.UserServiceImpl"> <constructor-arg ref="userDao"></constructor-arg> </bean>
注:上述代码中dao的id是可以随便取值的,和下面的ref对应即可,想想也可以理解,因为它是通过注入到构造函数中的形参来设置值得,形参的值是可以随便改的,所以无所谓。另外还有一个很重的就是使用构造函数注入必须提供对应参数个数和类型的构造函数,这点要切记。
现在,需求又来了,我们要在service实现类中定义一个变量,通过注入的方式来设置值,先看service实现类:
/** * 业务逻辑层的接口实现类 * @author Liao */ public class UserServiceImpl implements IUserService{ /*定义name属性,通过注入的方式设置值*/ private String name; /*创建Dao*/ private IUserDao userDao; /** * 使用构造函数的方式来注入 * @param userDao */ public UserServiceImpl(IUserDao userDao,String name) { this.userDao = userDao; this.name = name; } @Override public void delete(int id) { /*通过数据库操作层的实例来操作数据库*/ userDao.delete(id); /*输出name变量的值*/ System.out.println(this.name); } }
在上述类中,我们没有提供变量的setter方法,所以只能通过构造方法注入:
<!-- 定义要注入的dao的id,这个可以随便取值 --> <bean id="userDao" class="com.lixue.dao.impl.UserDaoImpl"></bean> <!-- constructor-arg的ref属性必须和dao的id一致,不能乱来 --> <bean id="userServiceImpl" class="com.lixue.service.impl.UserServiceImpl"> <constructor-arg index="0" ref="userDao"></constructor-arg> <constructor-arg index="1" type="java.lang.String" value="习近平"></constructor-arg> </bean>
注:上述代码中的constructor-arg节点的index和type属性是可选的,只要我们自己按照构造函数中参数的顺序来写就不会有错,另外可以发现,我们注入的普通变量连名称都可以不用写,直接写上value就可以了。
通过构造函数的方式可以注入,那么使用stter方式可以注入普通变量吗?我们 来试试,首先setter方式注入必须提供setter方法并且提供无参构造函数,修改service实现类如下:
/** * 业务逻辑层的接口实现类 * * @author Liao */ public class UserServiceImpl implements IUserService { /* 定义name属性,通过注入的方式设置值 */ private String name; /* 创建Dao */ private IUserDao userDao; /** * 通过setter方式注入,必须提供对应变量的setter方法,并且提供默认的构造方法 * 这里默认就是无参构造方法 * @param name */ public void setName(String name) { this.name = name; } public void setUserDao(IUserDao userDao) { this.userDao = userDao; } @Override public void delete(int id) { /* 通过数据库操作层的实例来操作数据库 */ userDao.delete(id); /* 输出name变量的值 */ System.out.println(this.name); } }
通过setter方法来注入dao和普通属性:
<!-- 定义要注入的dao的id,这个可以随便取值 --> <bean id="userDaoImpl" class="com.lixue.dao.impl.UserDaoImpl"></bean> <bean id="userServiceImpl" class="com.lixue.service.impl.UserServiceImpl"> <!-- 注意name属性必须是service实现类中的变量,且该变量提供了setter方法 --> <property name="userDao" ref="userDaoImpl"></property> <property name="name" value="李克强"></property> </bean>
注:从上述代码可以看出注入普通属性是我们要写name属性。
到这里,基本上可以歇一口气,但现在需求又改了,话说service实现类中的普通变量也不能老是String类型吧,来点重口味的比如:List、Set、Map之类的。下面我们来一个重口味的实现类:
/** * 业务逻辑层的接口实现类 * * @author Liao */ public class UserServiceImpl implements IUserService { /*String类型的注入*/ private String strValue; /*int类型的注入*/ private int intValue; /*List集合类型的注入*/ private List listValue; /*Set集合类型的注入*/ private Set setValue; /*String[]集合类型的注入*/ private String[] arrayValue; /*Map集合类型的注入*/ private Map mapValue; /*Properties集合类型的注入*/ private Properties properties; /*Dao的注入*/ private IUserDao userDao; /*Date日期类型的注入(日期属性需要使用属性编辑器才可以实现注入)*/ private Date dateValue; /** * 通过setter方式注入,必须提供对应变量的setter方法,并且提供默认的构造方法 * 这里默认就是无参构造方法 * @param name */ public void setStrValue(String strValue) { this.strValue = strValue; } public void setIntValue(int intValue) { this.intValue = intValue; } public void setListValue(List listValue) { this.listValue = listValue; } public void setSetValue(Set setValue) { this.setValue = setValue; } public void setArrayValue(String[] arrayValue) { this.arrayValue = arrayValue; } public void setMapValue(Map mapValue) { this.mapValue = mapValue; } public void setProperties(Properties properties) { this.properties = properties; } public void setUserDao(IUserDao userDao) { this.userDao = userDao; } public void setDateValue(Date dateValue) { this.dateValue = dateValue; } @Override public void test() { /* 通过数据库操作层的实例来操作数据库 */ userDao.test(); /* 输出str变量的值 */ System.out.println(this.strValue); /* 输出int变量的值 */ System.out.println(this.intValue); /* 输出list变量的值 */ System.out.println(this.listValue.get(0)); /* 输出set集合的长度 */ System.out.println(this.setValue.size()); /* 输出str数组的值 */ System.out.println(this.arrayValue[0]); /* 输出Map变量的值 */ System.out.println(this.mapValue.get("key1")); /* 输出Properties变量的值 */ System.out.println(this.properties); /*输出Date类型变量*/ System.out.println(this.dateValue); } }
注:我把原来的delete()方法改成了test(),这个无伤大雅,不是重点。我们重点是看各种类型的属性如何注入。
<!-- 定义要注入的dao的id,这个可以随便取值 --> <bean id="userDaoImpl" class="com.lixue.dao.impl.UserDaoImpl"></bean> <bean id="userServiceImpl" class="com.lixue.service.impl.UserServiceImpl"> <!-- 注意name属性必须是service实现类中的变量,且该变量提供了setter方法 --> <property name="userDao" ref="userDaoImpl"></property> <!-- 注入String类型 --> <property name="strValue" value="廖钟民"></property> <!-- 注入int类型 --> <property name="intValue" value="168"></property> <!-- 注入List类型 --> <property name="listValue"> <list> <value>习近平</value> <value>李克强</value> </list> </property> <!-- 注入Set类型 --> <property name="setValue"> <set> <value>奥巴马</value> <value>安倍狗</value> </set> </property> <!-- 注入String数组类型 和List类型相似--> <property name="arrayValue"> <list> <value>萨科齐</value> <value>奥朗德</value> </list> </property> <!-- 注入Map类型 --> <property name="mapValue"> <map> <entry key="key1" value="马英九"/> <entry key="key2" value="曾荫权"/> </map> </property> <!-- 注入Properties类型 --> <property name="properties"> <props> <prop key="pk">小泉猪</prop> </props> </property> <!-- 注入Date日期类型 --> <property name="dateValue" value="1992-07-17"></property> </bean>
注:按照上面的注入方式基本上可以完成注入,但是有一个属性是会注入失败的,那就是Date日期类型,对于Date日期类型,我们要使用属性编辑来实现成功注入。
首先再定义一个配置文件,专门用于属性编辑器的配置:
<?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"> <!-- 这个id是自定义属性编辑器bean的id,可以随便取 --> <bean id="customEditors1" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <!-- 这个id必须为 customEditors因为是通过注入的方式来实现的,其内部是一个Map结构customEditors是Map类型的变量名--> <property name="customEditors"> <map> <entry key="java.util.Date"> <!-- 内部bean,这里把java.util.Date作为Map的key,这个bean作为Map的value --> <bean class="com.lixue.utils.DatePropertyEditorUtil"> <!-- 这个参数可配置 --> <property name="pattern" value="yyyy-MM-dd"/> </bean> </entry> </map> </property> </bean> </beans>
注:加载的时候,不要忘记加载这个配置文件。
属性编辑器类:
public class DatePropertyEditorUtil extends PropertyEditorSupport{ /*日期的格式,通过setter方式注入进来实现可配置的功能*/ private String pattern; public void setPattern(String pattern) { this.pattern = pattern; } @Override public void setAsText(String text) throws IllegalArgumentException { try { Date date = new SimpleDateFormat(pattern).parse(text); this.setValue(date); } catch (Exception e) { e.printStackTrace(); } } }
最后来看看我们的测试类:
public class SpringTest { public static void main(String[] args) { /*初始化Spring容器*/ ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"beans.xml","applicationContext-editor.xml"}); /*ctx.getBean("userServiceImpl")获取Bean*/ IUserService userService = (IUserService) ctx.getBean("userServiceImpl"); userService.test(); } }