控制反转

控制

要阐明控制反转的概念,先要明白什么是控制。举例有两个对象,如电池Battery和它的规格参数Parameter,他们可以有如下关系:

1
2
3
4
5
class Battery {
Parameter parameter = new Parameter();
Battery() {}
...
}

从代码中这样的关系可以看出:

  • Battery类对象的构成是依赖于Parameter类对象的
  • 在创建一个Battery类对象时,会随之创建一个Parameter类对象
  • 在删除Battery类对象时,其Parameter成员对象也会随之删除
  • 从前两点可以看出,Battery对象的生命周期决定了Parameter成员对象的生命周期,即控制

控制反转

控制反转是一种面向对象编程中的一种设计原则,用来降低计算机代码之间的耦合度,即解开对象与对象之间的控制关系,可以让Battery交出Parameter的创建权给外部,将上面的代码改成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Battery{
Parameter parameter = new Parameter();
Battery(Parameter parameter) {
this.parameter = parameter;
}
...
}

class Main {
public static void main(String[] args) {
Parameter parameter = new parameter();
Battery battery = new Battery(parameter); //在构造方法中将parameter传入battery
...
}
}

这样写可以在外部创建Parameter对象,再传入Battery对象,当Battery对象生命周期结束时,Parameter对象可以继续存在,即实现了控制反转,控制反转可以实现对象之间的解耦,减少代码的重复

依赖注入

将所依赖的对象,传递给将使用的从属对象,就是依赖注入,而依赖注入是控制反转的具体实现方法

在前面的代码中,虽然已经成功实现了Battery对象与Parameter对象的解耦,将Parameter对象的控制权转移到了外部,但是Battery对象对Parameter对象的依赖依然存在,需要Parameter对象的传递才能完成构造,因此依赖注入是控制反转的具体实现方法

通过控制反转,把B的控制权转移到A的外部,但是A仍然依赖于B,需要使用B的对象去完成一些事情,那问题来了:我们如何把在外部创建的B的对象交给A呢?

  • 在A的构造方法里吧B作为一个参数传给A——更加清晰
  • 使用Setter方法把B传给A的成员变量——更加灵活

这就是依赖注入,依赖注入实际上是控制反转的一个实现

Spring组件和依赖注入

依赖注入逻辑:抽象出一些类和对象,然后处理对象之间的交互

在具体的场景中,我们会有一些常用的对象来处理一些常见的逻辑结构,比如说特定本地数据的访问,数据库的访问、认证,这种我们会称之为组件或者模块,在Spring里称之为Bean,同一个组件通常可能会在程序中很多地方需要使用,比如说UerController可能会使用到UserDao这个组件,ManageController也会用到UserDao这个组件

为避免代码重复,统一管理组件的创建, 然后在需要使用的地方把这个组件的实例注入

Spring里的一些逻辑

1
2
3
@Component //告诉Spring,这是一个组件,可以用来注入到其他的对象里
Controller本身也是一个Component
SpringApplication.run(Application.class,args); //初始化所有的组件,并且根据依赖关系,把每个组件所依赖的其他组件也初始化,然后注入!

运行顺序
1.创建UserController –> new UserController(UserMockDao);–>需要另外一对象(组件)UserMockDao
2.先创建UserMockDao –> 创建UserController

问题

怎么告诉Spring选择性地选择哪个Component?

常用的两种注入方式

  • 组件直接标记注入:如果需要被注入的组件,是我们能够控制的,那用直接标记注入简洁优雅
  • 工厂模式注入

工厂模式

工厂模式是一种常用的创建型设计模式,提供了一种创建对象的最佳模式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

使用场景

1.需要注入的组件, 我们需要更复杂的控制创建过程, 比如说动态根据输入内容创建组件
2.需要注入的组件是一个外部库的类, 我们没法给它加上@Component这样的标记
3.控制组件的数量, 如果是只需要一个组件的实例, 则使用直接注入或者工厂模式都可以. 需要使用多个实例时(每次使用都创建一个新的实例),选择工厂模式,同一套代码可以创建多个对象

通用工厂类代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class Factory implements FactoryBean<UserDao> {
// 返回一个UserMockDao对象
@Override
public UserDao getObject() {
return new UserMockDao();
}

// 返回一个UserMockDao类型
@Override
public Class<?> getObjectType() {
return UserMockDao.class;
}

// 多个Controller用到UserMockDao时,只需要创建一个,共享
@Override
public boolean isSingleton() {
return true;
}
}