经过我们改造,我们现在的依赖关系树变为下面的样子
2.2 依赖注入框架
随着我们不断引入更多的依赖关系,这种复杂性将继续增长。为了解决这个复杂问题,我们需要基于依赖关系树抽取对象的创建过程。这就是依赖注入框架。
一般来说,我们可以把这个过程分为三个部分:
- 声明需要创建的对象需要哪些依赖对象
- 注册创建这些依赖对象所需要的类
- 提供一种使用1和2两点思想创建对象的机制
通过反射,我们可以查看 Car
类的构造函数,并且知道它需要一个 Engine
参数。因此为了创建Car对象,我们必须创建至少一个Engine
接口的实现类用作依赖项来使用。在这里,我们创建一个CombustionEngine
对象(为了方便,暂时当做只有一个实现类,bean冲突问题待会再说)来声明它作为依赖项来使用,就满足Car
对象创建时的需求.
其实,这个过程是递归的,因为CombustionEngine
依赖于其他对象,我们需要不断重复第一个过程,直到把所有依赖对象声明完毕,然后注册创建这些依赖对象所需要的类。
第三点其实就是将前面两点思想付诸实施,从而形成一种创建对象的机制
举个例子:比如我们需要一个Car
对象,我们必须遍历依赖关系树并检查是否存在至少一个符合条件的类来满足所有依赖关系。例如,声明CombustionEngine
类可满足Engine
节点要求。如果存在这种依赖关系,我们将实例化该依赖关系,然后移至下一个节点。
如果有一个以上的类满足所需的依赖关系,那么我们必须显式声明应该选择哪一种依赖关系。稍后我们将讨论 Spring 是如何做到这一点的。
一旦我们确定所有的依赖关系都准备好了,我们就可以从终端节点开始创建依赖对象。对于 Car
对象,我们首先实例化 Camshaft
和Crankshaft
ーー因为这些对象没有依赖关系ーー然后将这些对象传递给 CombustionEngine
构造函数,以实例化 CombunstionEngine
对象。最后,我们将 CombunstionEngine
对象传递给 Car
构造函数,以实例化所需的 Car
对象。
了解了 DI
的基本原理之后,我们现在可以继续讨论 Spring
如何执行 DI
。
2.3 Spring的依赖注入
Spring
的核心是一个DI
框架,它可以将DI
配置转换为Java
应用程序。
在这里我们要阐述一个问题:那就是库和框架的区别。库只是类定义的集合。背后的原因仅仅是代码重用,即获取其他开发人员已经编写的代码。这些类和方法通常在域特定区域中定义特定操作。例如,有一些数学库可让开发人员仅调用函数而无需重做算法工作原理的实现。
框架通常被认为是一个骨架,我们在其中插入代码以创建应用程序。许多框架保留了特定于应用程序的部分,并要求我们开发人员提供适合框架的代码。在实践中,这意味着编写接口的实现,然后在框架中注册实现。
2.4 ApplicationContext
在 Spring
中,框架围绕 ApplicationContext
接口实现上一节中概述的三个 DI
职责。通常这个接口代表了一个上下文。因此,我们通过基于 java
或基于 xml
的配置向 ApplicationContext
注册合适的类,并从 ApplicationContext
请求创建 bean
对象。然后 ApplicationContext
构建一个依赖关系树并遍历它以创建所需的 bean
对象
Applicationcontext
中包含的逻辑通常被称为 Spring
容器。通常,一个 Spring
应用程序可以有多个 ApplicationContext
,每个 ApplicationContext
可以有单独的配置。例如,一个 ApplicationContext
可能被配置为使用
CombustionEngine
作为其引擎实现,而另一个容器可能被配置为使用 ElectricEngine
作为其实现。
在本文中,我们将重点讨论每个应用程序的单个 ApplicationContext
,但是下面描述的概念即使在一个应用程序有多个 ApplicationContext
实例时也适用。
三 基于 java 的配置
Spring为我们提供了两种基于 java
的配置方式
- 基本配置
- 自动配置
3.1 基于 java 的基本配置
基于java
的基本配置的核心,其实是下面两个注解:
@Configuration
: 定义配置类
@Bean
: 创建一个bean
例如,给出我们之前定义的 Car
, CombustionEngine
, Camshaft
, 和Crankshaft
类,我们可以创建一个下面 的配置类:
/** * @author milogenius * @date 2020/5/17 20:52 */ @Configuration public class AnnotationConfig { @Bean public Car car(Engine engine) { return new Car(engine); } @Bean public Engine engine(Camshaft camshaft, Crankshaft crankshaft) { return new CombustionEngine(camshaft, crankshaft); } @Bean public Camshaft camshaft() { return new Camshaft(); } @Bean public Crankshaft crankshaft() { return new Crankshaft(); } }
接下来,我们创建一个 ApplicationContext
对象,从 ApplicationContext
对象获取一个 Car
对象,然后在创建的 Car
对象上调用 start
方法:
ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class); Car car = context.getBean(Car.class); car.start();
执行结果如下:
Started combustion engine
虽然@Configuration
和@Bean
注解的组合为 Spring
提供了足够的信息来执行依赖注入,但我们仍然需要手动手动定义每个将被注入的 bean
,并显式地声明它们的依赖关系。为了减少配置 DI
框架所需的开销,Spring 提供了基于java
的自动配置。
3.2 基于 java 的自动配置
为了支持基于 java
的自动配置,Spring
提供了额外的注解。虽然我们平时可能加过很多这种类型的注解,但是有三个最基本的注解:
@Component
: 注册为由 Spring 管理的类@Autowired
: 指示 Spring 注入一个依赖对象@ComponentScan
: 指示Spring在何处查找带有@Component
注解的类
3.2.1 构造函数注入
@Autowired
注解用来指导 Spring
,我们打算在使用注解的位置注入一个依赖对象。例如,在 Car
构造函数中,我们期望注入一个 Engine
对象,因此,我们给 Car
构造函数添加@Autowired
注解。通过使用@Component
和@Autowired
注解改造我们Car
类,如下所示:
@Component public class Car { private Engine engine; @Autowired public Car(Engine engine) { this.engine = engine; } public void start() { engine.turnOn(); } }