1)什么是控制反转
传统的编程思路是,当我需要某个对象时,我便自己去实例化调用它 。而控制反转则是,当我需要某个对象时,自然有人帮我们实例化它 。简单的来说,这是一种衣来张口,饭来伸手式的控制模式,这也符合spring最根本的使命--简化java开发 。
2)什么是依赖注入
依赖注入,即是SpringIOC的一种实现方式,在我们完成业务开发的过程中,需要引入的依赖,都由交由spring容器管理注入 。简单的说,就是由spring容器替我们实例化对象 。
3)依赖注入解决了什么问题
在传统的业务实现中,一个类是解决不了所有问题的,在一个类中我们通常会引入许多其它类来满足我们的业务需求,这个时候我们避免不了去实例化不同的对象,而面临需求变更时,相关的改动往往是牵一发而动全身,这时候我们希望有一种能让类与类之间的联系变得不那么密切的方式来修改我们的代码 。这也就是依赖注入给我们带来的最大收益--松耦合 。
4)依赖注入的实现
首先新建一个maven项目,引入spring核心依赖和其他相关依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ice-maple</groupId>
<artifactId>ice.maple</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- Spring依赖 -->
<!-- 1.Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 4.Spring test依赖:方便做单元测试和集成测试 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
</dependencies>
</project>
假设我们的同事给我们提供了一个获取用户的接口,我们需要调用它进行显示,传统意义上我们可能需要这么做:
public class UserService implements IUserService {
private IUserDao userDao = new UserDao();
public List<User> getUsers(){
return userDao.getUsers();
}
}
而测试的时候我们会这么做:
@Test
public void getUsers() {
// ContextConfiguration contextConfiguration = new ContextConfiguration().locations("classpath:spring-config.xml");
IUserService userService = new UserService();
userService.getUsers().forEach(n -> {
System.out.println(n.getName());
System.out.println(n.getSex());
});
}
当我们采用这种方式完成业务,此时若有一个需求导致同事更换了实现类,那么在他新建类的同时,不论是测试还是我们的接口调用,都要做同步修改,由于我们在编写代码时类之间的紧密联系,导致我们可能在一个小小的业务改动上可能要进行多方的代码改动,而大多数时候这些类是多个不同的人写的,依赖注入能很好的解决我们这个问题 。
我们在resource下新建spring-config配置文件,并对service类进行修改:
spring-config.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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--<context:component-scan base-package="ice.maple"/>-->
<bean id="UserService" class="ice.maple.service.impl.UserService">
<property name="userDao">
<ref bean="UserDao"></ref>
</property>
</bean>
<bean id="UserDao" class="ice.maple.dao.impl.UserDao"></bean>
</beans>
UserService
public class UserService implements IUserService {
private IUserDao userDao;
public List<User> getUsers(){
return userDao.getUsers();
}
public IUserDao getUserDao() {
return userDao;
}
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
//@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
UserService userService;
@Test
public void getUsers() {
userService.getUsers().forEach(n -> {
System.out.println(n.getName());
System.out.println(n.getSex());
});
}
}
运行我们可能看到得到了想要的结果,说明注入成功:
当然我们也可以用构造方法的方式注入,service类增加构造方法并对配置文件做部分改动:
UserService
public UserService(IUserDao userDao) {
this.userDao = userDao;
}
spring-config.xml
<bean id="UserService" class="ice.maple.service.impl.UserService">
<constructor-arg name="userDao">
<ref bean="UserDao"></ref>
</constructor-arg>
</bean>
测试类不做改动,运行的效果是相同的 。
我们也可以不使用这种注册bean的方式,因为这种注册方式事实上并没有给我们简化代码,只是方便了我们的同意管理,我们可以换一种方式实现它,在dao和service上分别加上@Repository和@Service的注解,可以去掉构造方法和getter,setter方法,修改配置文件
spring-config.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="ice.maple"/>
</beans>
UserService
@Service
public class UserService implements IUserService {
@Autowired
private IUserDao userDao;
public List<User> getUsers(){
return userDao.getUsers();
}
}
运行测试类,正常启动 。
当然我们也可以不需要配置文件,直接采用配置类的方式做这件事情
新建SpringConfig类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("ice.maple")
public class SpringConfig {
}
修改测试类:
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:spring-config.xml")
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
UserService userService;
@Test
public void getUsers() {
userService.getUsers().forEach(n -> {
System.out.println(n.getName());
System.out.println(n.getSex());
});
}
}
运行测试类,一切正常 。
接下来,我们看看如果spring扫描到了两个实现类会发生什么:
我们新建一个PersonDao并加上@Repository注解,启动测试类 。
运行发现并没有报错,spring选择了UserDao作为实现类,我们再修改一下UserDao的类名 。
再次运行发现,当实现类为两个类名与接口无关无关的类时,将会报错,原因是找到了两个不同的实现类 。这时候我们可以修改注解指定这个实现类:
@Repository("userDao")