封面画师:T5-茨舞(微博)     封面ID:73934916

1. 依赖注入的理解

我们已经知道Spring可以帮助我们创建对象,但是对其中的依赖注入一直无法理解,突然有一天开窍了,遂写下这篇文章。

1.1 准备

使用 IDEA 创建一个 Maven 项目,导入 Spring 依赖:

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>

创建一个名为 User 的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.yang.pojo;

public class User {

private String username;

public void setUsername(String username) {
this.username = username;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
'}';
}
}

使用 User 类创建一个对象,并打印这个对象:

1
2
3
4
public static void main(String[] args) {
User user = new User();
System.out.println(user);
}

1.2 测试

运行 main() 方法后,控制台打印以下信息(此次结果记为 运行 1 ):

SpringIoc_1

修改 main() 方法,使用 Set 方法为 username 属性赋值:

1
2
3
4
5
public static void main(String[] args) {
User user = new User();
user.setUsername("yang");
System.out.println(user);
}

再次运行 main() 方法,控制台打印以下信息(此次结果记为 运行 2 ):

SpringIoc_2

此时我们可以看到使用 Set 方法后可以为 username 设置一个值,那应该怎么用 Spring 的方式创建对象然后设置值呢?

1.3 使用 Spring 创建对象

在 resources 目录下创建一个名为 beans.xml(官方文档有其他的建议使用名,在此我们暂时不那么办)的文件,文件类型必须是xml,文件名可以随意。

然后在这个文件中追加 Spring 的约束(百度或 Spring 官网都可以找到):

1
2
3
4
5
6
7
<?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>

<beans> 标签内部追加以下信息:

1
<bean id="u" class="com.yang.pojo.User"></bean>

main() 方法进行如下改造:

1
2
3
4
5
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //解析配置文件
User user = (User) context.getBean("u"); //获取配置文件中id为u的bean
System.out.println(user);
}

运行 main() 方法后,控制台打印以下信息:

SpringIoc_3

可以发现这次操作与我们第一次运行的结果(运行 1)一样,也就是说 <bean> 标签是 Spring 提供给我们来创建对象的一种方式。

再对配置文件进行改造,使用 <property> 标签进行依赖注入:

1
2
3
<bean id="u" class="com.yang.pojo.User">
<property name="username" value="yang"></property>
</bean>

再次运行 main() 方法后,控制台打印以下信息:

SpringIoc_4

1.4 结论

我们再次发现这次运行结果与我们的第二次运行(运行 2)一样,这时我们可以简单地得出一个结论:

使用 Spring 配置文件中的 <property> 标签进行依赖注入与使用了 Set 方法最后得到的结果是一样的,都是为一个对象的某个属性进行赋值(或者说设置初值)。

那么,这种 注入是用的 Set 方法 吗?答案很显然,如果我们去除 Set 方法,这时候 <property> 标签就会报错,同时无法运行,更无法注入。

2. 依赖注入在实际开发中的使用

2.1 准备

创建 service 包,在其下创建接口 UserService

1
2
3
4
package com.yang.service;
public interface UserService {
public void hello();
}

在 service 包下,创建 impl 包并在包下创建 UserServiceImpl 实现类:

根据上面的结论,如果我们要在 UserServiceImpl 中使用 UserDao一定要记得添加对应的 Set 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.yang.service.impl;

import com.yang.dao.UserDao;
import com.yang.service.UserService;

public class UserServiceImpl implements UserService {

private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

public void hello() {
userDao.hi();
System.out.println("Service执行了。。。");
}
}

创建 dao 包,并在下创建接口 UserDao

1
2
3
4
5
package com.yang.dao;

public interface UserDao {
public void hi();
}

在 dao 包下,创建 impl 包并在包下创建 UserDaoImpl 实现类:

1
2
3
4
5
6
7
8
9
package com.yang.dao.impl;

import com.yang.dao.UserDao;

public class UserDaoImpl implements UserDao {
public void hi() {
System.out.println("dao执行了。。。");
}
}

在 beans.xml 配置文件中追加以下信息:

1
2
3
4
5
<bean id="userService" class="com.yang.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao1"></property>
</bean>

<bean id="userDao1" class="com.yang.dao.impl.UserDaoImpl"></bean>

使用 mian() 方法进行测试:

1
2
3
4
5
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.hello();
}

2.2 测试

运行 main() 方法后控制台打印出以下信息:

SpringIoc_5

3. IoC 理解

根据百度百科的定义:控制反转(Inversion of Control,缩写为 IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做 依赖注入(Dependency Injection,简称 DI)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

3.1 自我理解

如果用户的需求增加,按照传统思想,需要我们(程序员)更改 service 层的调用。程序是主动创建对象,此时控制权在 service 层,或者说程序员。比如:

1
private UserDao userDao = new UserDaoImpl();

如果此时 UserDao 的实现类发生改变,变为 UserDaoImpl_2,那么就需要在 service 层 手动修改 成:

1
private UserDao userDao = new UserDaoImpl_2();

很显然我们不应该手动去修改源代码,因为在实际开发中手动修改源代码很有可能会造成更大的风险。我们之所以需要去修改源代码,就是因为这里存在的代码耦合。

在 service 层这样使用:

1
2
UserService userService = new UserServiceImpl();
userService.hello();

为了不手动修改源代码,可以使用 Set 注入:

1
2
3
4
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

这样我们在调用的时候就不用每次更改代码了。程序不再具有主动性,而是变成了被动接收的对象。此时的控制权可以说已经在用户了。 这就是控制反转。 实际使用时:

1
2
3
UserService userService = new UserServiceImpl();
((UserServiceImpl) userService).setUserDao(new UserDaoImpl_2());
userService.hello();

3.2 总结

在没有 IoC 的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,而使用 IoC 后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。