IOC控制反转
什么是IOC?
IOC(Inverse Of Controll),意为控制反转,也就是将创建对象的权限交给Spring工厂,让Spring工厂来创建对象,解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健
IOC容器
Spring 提供了两种 IOC 容器,分别为 BeanFactory 和 ApplicationContext
BeanFactory
BeanFactory 是基础类型的 IoC 容器,它由 org.springframework.beans.facytory.BeanFactory 接口定义,并提供了完整的 IoC 服务支持。简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
ApplicationContext
ApplicationContext 是 BeanFactory 的子接口,也被称为 Spring 上下文。该接口的全路径为 org.springframework.context.ApplicationContext,它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。
实例测试
1、创建Maven项目
2、在pom.xml中导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>
3、在resources目录下创建配置文件: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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
4、创建接口com.robot.dao.UserDao和实现类com.robot.dao.impl.UserDaoImpl
5、配置applicationContext.xml文件,在beans标签中添加bean标签,每一个bean都是一个对象,id和name可以自己随意起,class为实现类,不能为接口,因为接口不能实例化
<bean id="userDao" name="userDao" class="com.robot.dao.impl.UserDaoImpl"></bean>
6、编写测试类,测试spring来创建对象
- 三种方式来创建对象
public class UserDaoTest {
@Test
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 根据id获得对象
UserDao userDao1 = (UserDao) applicationContext.getBean("userDao");
// 根据name获得对象
UserDao userDao2 = (UserDao) applicationContext.getBean("userDao");
// 根据class获得对象
UserDao userDao3 = applicationContext.getBean(UserDao.class);
System.out.println(userDao1);
System.out.println(userDao2);
System.out.println(userDao3);
}
}
执行测试类,打印结果输出三个对象,则代表创建成功
DI依赖注入
在Spring创建对象的同时,为其属性赋值,称之为依赖注入
setter方法注入
在配置好环境后,测试依赖注入
1、构建dao:UserDao和UserDaoImpl,并实现一个方法
public class UserDaoImpl implements UserDao {
public int insertUser() {
System.out.println("UserDaoImpl::insertUser()");
return 0;
}
}
2、构建service:UserService和UserServiceImpl,并实现一个方法,在方法中调用dao层对象
- 在以前会使用
UserDao userDao = new UserDaoImpl();
的方式去创建一个dao层对象,但这样实现方式耦合度太高,因为一个接口可能会对应多个实现类,当需要换一个实现类的时候,那么service层也需要更改,所以耦合度太高,需要解耦 - 现在使用spring的依赖注入来实现(setter方法实现注入),会自动根据配置文件实例化对应的实现类
public class UserServiceImpl implements UserService {
UserDao userDao;
// 使用setter方法注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public int insertUser() {
return userDao.insertUser();
}
}
3、配置applicationContext.xml文件
- 上面是userDao的bean,下面是userService的bean,其中userService的实现类中注入了userDao对象,所以需要添加property标签
- 使用setter方法注入,setter方法名为setUserDao,所以标签中的name为set后面的字符串首字母小写,也就是userDao,ref就是上面userDao中bean的id,以此将两者联系起来
<bean id="userDao" name="userDao" class="com.robot.dao.impl.UserDaoImpl"></bean>
<bean id="userService" name="userService" class="com.robot.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
4、最后在测试类中进行测试,调用userService的方法
public class UserDaoTest {
@Test
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.insertUser();
}
}
结果显示
UserDaoImpl::insertUser()
此条输出结果是dao层方法的输出结果,所以表示成功的将userDaoImpl注入到了userServiceImpl中,使用的是setter方法注入的
构造方法注入
单个参数
只有一个属性需要赋值,构造方法中只有一个参数
1、将service层中的UserServiceImpl方法中的setter方法去掉,添加带参的构造方法,将属性userDao注入
public class UserServiceImpl implements UserService {
// 原始方式
// UserDao userDao = new UserDaoImpl();
UserDao userDao;
// 使用setter方法注入
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }
// 使用构造方法注入
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
public int insertUser() {
return userDao.insertUser();
}
}
2、修改applicationContext.xml配置文件
- 其中constructor-arg标签中的ref属性值为要注入的属性的id,也就是上面dao层bean的id
<?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">
<bean id="userDao" name="userDao" class="com.robot.dao.impl.UserDaoImpl"></bean>
<!-- setter方法注入-->
<!-- <bean id="userService" name="userService" class="com.robot.service.impl.UserServiceImpl">-->
<!-- <property name="userDao" ref="userDao"></property>-->
<!-- </bean>-->
<!-- 构造方法注入-->
<bean id="userService" class="com.robot.service.impl.UserServiceImpl">
<constructor-arg ref="userDao"/>
</bean>
</beans>
3、测试
- 只需要修改上面两个地方即可,然后即可测试
- 最终结果还是和上面输出的结果相同,都输出了 UserDaoImpl::insertUser(),表明注入成功
多个参数
当只有一个参数的时候,可以直接注入,但是当有多个属性需要注入的时候,那么构造方法的多个参数就可能发生歧义
举个栗子
在service层的实现类中需要注入多个属性
1、再创建dao层的一个GoodsDao接口和GoodsDaoImpl实现类,并实现方法
public class GoodsDaoImpl implements GoodsDao {
public int deleteGoods() {
System.out.println("GoodsDaoImpl::deleteGoods()");
return 0;
}
}
2、创建service层的GoodsService接口和GoodsServiceImpl实现类,并实现方法
- 使用构造方法注入两个属性,构造方法中的这两个参数的类型不同
public class GoodsServiceImpl implements GoodsService {
// 需要注入的两个属性
UserDao userDao;
GoodsDao goodsDao;
// 构造方法注入
public GoodsServiceImpl(UserDao userDao, GoodsDao goodsDao) {
this.userDao = userDao;
this.goodsDao = goodsDao;
}
public int deleteGoods() {
userDao.insertUser();
goodsDao.deleteGoods();
return 0;
}
}
3、配置applicationContext.xml配置文件
- 其中接着上面配置的添加了goodsDao的bean和goodsService的bean
- 因为构造方法中两个参数的类型不同,所以在配置goodsService的时候,constructor-arg标签内需要添加type属性,来显示的指定参数的类型,从而定位到具体的参数
- (如果参数类型相同,则无需使用type属性)
<?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">
<bean id="userDao" name="userDao" class="com.robot.dao.impl.UserDaoImpl"></bean>
<bean id="goodsDao" class="com.robot.dao.impl.GoodsDaoImpl"></bean>
<!-- setter方法注入-->
<!-- <bean id="userService" name="userService" class="com.robot.service.impl.UserServiceImpl">-->
<!-- <property name="userDao" ref="userDao"></property>-->
<!-- </bean>-->
<!-- 构造方法注入-->
<bean id="userService" class="com.robot.service.impl.UserServiceImpl">
<constructor-arg ref="userDao"/>
</bean>
<!-- 多个参数时的构造方法注入 type-->
<bean id="goodsService" class="com.robot.service.impl.GoodsServiceImpl">
<constructor-arg type="com.robot.dao.UserDao" ref="userDao"></constructor-arg>
<constructor-arg type="com.robot.dao.GoodsDao" ref="goodsDao"></constructor-arg>
</bean>
</beans>
除了使用type属性来指定参数,还可以使用index索引,来指定参数,和上面的效果一样,使用其中一个方法即可
<!-- 多个参数时的构造方法注入 index-->
<bean id="goodsService" class="com.robot.service.impl.GoodsServiceImpl">
<constructor-arg index="0" ref="userDao"></constructor-arg>
<constructor-arg index="1" ref="goodsDao"></constructor-arg>
</bean>
4、测试
public class GoodsDaoTest {
@Test
public void deleteTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
GoodsService goodsService = (GoodsService) applicationContext.getBean("goodsService");
goodsService.deleteGoods();
}
}
结果为输出两条信息
UserDaoImpl::insertUser()
GoodsDaoImpl::deleteGoods()
因为在GoodsServiceImpl中注入了这两个属性,然后调用了这两个属性的方法,所以输出两条结果信息,说明注入成功
自动注入
不需要写setter方法,也不需要写构造方法,只需要在配置文件中配置即可,两种方式
1、基于类型自动注入值
<bean id="userService" class="com.robot.service.impl.UserServiceImpl" autowire="byType"></bean>
2、基于名字自动注入值
<bean id="userService" class="com.robot.service.impl.UserServiceImpl" autowire="byName"></bean>