前言
为什么需要 Spring? 什么是 Spring?
对于这样的问题, 大部分人都是处于一种朦朦胧胧的状态, 说的出来, 但又不是完全说的出来, 今天我们就以架构设计的角度尝试解开 Spring 的神秘面纱.
本篇文章以由浅入深的方式进行介绍, 大家不必惊慌, 我可以保证, 只要你会编程就能看懂.
本篇文章基于 Spring 5.2.8, 阅读时长大概需要 20 分钟
案例
我们先来看一个案例: 有一个小伙, 有一辆吉利车, 平常就开吉利车上班
代码实现:
- public class GeelyCar {
- public void run(){
- System.out.println("geely running");
- }
- }
- public class Boy {
- // 依赖 GeelyCar
- private final GeelyCar geelyCar = new GeelyCar();
- public void drive(){
- geelyCar.run();
- }
- }
有一天, 小伙赚钱了, 又买了辆红旗, 想开新车.
简单, 把依赖换成 HongQiCar
代码实现:
- public class HongQiCar {
- public void run(){
- System.out.println("hongqi running");
- }
- }
- public class Boy {
- // 修改依赖为 HongQiCar
- private final HongQiCar hongQiCar = new HongQiCar();
- public void drive(){
- hongQiCar.run();
- }
- }
新车开腻了, 又想换回老车, 这时候, 就会出现一个问题: 这个代码一直在改来改去
很显然, 这个案例违背了我们的依赖倒置原则(DIP): 程序不应依赖于实现, 而应依赖于抽象
优化后
现在我们对代码进行如下优化:
Boy 依赖于 Car 接口, 而之前的 GeelyCar 与 HongQiCar 为 Car 接口实现
代码实现:
定义出 Car 接口
- public interface Car {
- void run();
- }
将之前的 GeelyCar 与 HongQiCar 改为 Car 的实现类
- public class GeelyCar implements Car {
- @Override
- public void run(){
- System.out.println("geely running");
- }
- }
HongQiCar 相同
Person 此时依赖的为 Car 接口
- public class Boy {
- // 依赖于接口
- private final Car car;
- public Person(Car car){
- this.car = car;
- }
- public void drive(){
- car.run();
- }
- }
此时小伙想换什么车开, 就传入什么参数即可, 代码不再发生变化.
局限性
以上案例改造后看起来确实没有什么毛病了, 但还是存在一定的局限性, 如果此时增加新的场景:
有一天小伙喝酒了没法开车, 需要找个代驾. 代驾并不关心他给哪个小伙开车, 也不关心开的是什么车, 小伙就突然成了个抽象, 这时代码又要进行改动了, 代驾依赖小伙的代码可能会长这个样子:
private final Boy boy = new YoungBoy(new HongQiCar());
随着系统的复杂度增加, 这样的问题就会越来越多, 越来越难以维护, 那么我们应当如何解决这个问题呢?
思考
首先, 我们可以肯定: 使用依赖倒置原则是没有问题的, 它在一定程度上解决了我们的问题.
我们觉得出问题的地方是在传入参数的过程: 程序需要什么我们就传入什么, 一但系统中出现多重依赖的类关系, 这个传入的参数就会变得极其复杂.
或许我们可以把思路反转一下: 我们有什么, 程序就用什么!
当我们只实现 HongQiCar 和 YoungBoy 时, 代驾就使用的是开着 HongQiCar 的 YoungBoy!
当我们只实现 GeelyCar 和 OldBoy 时, 代驾自然而然就改变成了开着 GeelyCar 的 OldBoy!
而如何反转, 就是 Spring 所解决的一大难题.
Spring 介绍
Spring 是一个一站式轻量级重量级的开发框架, 目的是为了解决企业级应用开发的复杂性, 它为开发 Java 应用程序提供全面的基础架构支持, 让 Java 开发者不再需要关心类与类之间的依赖关系, 可以专注的开发应用程序(crud).
Spring 为企业级开发提供给了丰富的功能, 而这些功能的底层都依赖于它的两个核心特性: 依赖注入 (DI) 和面向切面编程(AOP).
Spring 的核心概念
IoC 容器
IoC 的全称为 Inversion of Control, 意为控制反转, IoC 也被称为依赖性注入(DI), 这是一个通过依赖注入对象的过程: 对象仅通过构造函数, 工厂方法, 或者在对象实例化在其上设置的属性来定义其依赖关系(即与它们组合的其他对象), 然后容器在创建 bean 时注入这些需要的依赖. 这个过程从根本上说是 Bean 本身通过使用直接构建类或诸如服务定位模式的机制, 来控制其依赖关系的实例化或位置的逆过程(因此被称为控制反转).
依赖倒置原则是 IoC 的设计原理, 依赖注入是 IoC 的实现方式.
容器
在 Spring 中, 我们可以使用 xml,Java 注解或 Java 代码的方式来编写配置信息, 而通过配置信息, 获取有关实例化, 配置和组装对象的说明, 进行实例化, 配置和组装应用对象的称为容器.
一般情况下, 我们只需要添加几个注解, 这样容器进行创建和初始化后, 我们就可以得到一个可配置的, 可执行的系统或应用程序.
Bean
在 Spring 中, 由 Spring IoC 容器进行实例化 ->组装管理 ->构成程序骨架的对象称为 Bean.Bean 就是应用程序中众多对象之一.
以上三点串起来就是: Spring 内部是一个放置 Bean 的 IoC 容器, 通过依赖注入的方式处理 Bean 之间的依赖关系.
AOP
面向切面编程 (Aspect-oriented Programming), 是相对面向对象编程(OOP) 的一种功能补充, OOP 面向的主要对象是类, 而 AOP 则是切面. 在处理日志, 安全管理, 事务管理等方面有非常重要的作用. AOP 是 Spring 框架重要的组件, 虽然 IoC 容器没有依赖 AOP, 但是 AOP 提供了非常强大的功能, 用来对 IoC 做补充.
AOP 可以让我们在不修改原有代码的情况下, 对我们的业务功能进行增强: 将一段功能切入到我们指定的位置, 如在方法的调用链之间打印日志.
Spring 的优点
1,Spring 通过 DI,AOP 来简化企业级 Java 开发
2,Spring 的低侵入式设计, 让代码的污染极低
3,Spring 的 IoC 容器降低了业务对象之间的复杂性, 让组件之间互相解耦
4,Spring 的 AOP 支持允许将一些通用任务如安全, 事务, 日志等进行集中式处理, 从而提高了更好的复用性
5,Spring 的高度开放性, 并不强制应用完全依赖于 Spring, 开发者可自由选用 Spring 框架的部分或全部
6,Spring 的高度扩展性, 让开发者可以轻易的让自己的框架在 Spring 上进行集成
7,Spring 的生态极其完整, 集成了各种优秀的框架, 让开发者可以轻易的使用它们
我们可以没有 Java, 但是不能没有 Spring~
用 Spring 改造案例
我们现在已经认识了什么是 Spring, 现在就尝试使用 Spring 对案例进行改造一下
原来的结构没有变化, 只需在 GeelyCar 或 HongQiCar 上增加 @Component 注解, Boy 在使用时加上 @Autowired 注解
代码样式:
@Componentpublic class GeelyCar implements Car { @Override public void run() { System.out.println("geely car running"); }}
HongQiCar 相同
在 Spring 中, 当类标识了 @Component 注解后就表示这是一个 Bean, 可以被 IoC 容器所管理
@Componentpublic class Boy { // 使用 Autowired 注解表示 car 需要进行依赖注入 @Autowired private Car car; public void driver(){ car.run(); }}
我们之前所说的: 我们实现什么, 程序就使用什么, 在这里就等同于我们在哪个类上标识了 Component 注解, 哪个类就会是一个 Bean,Spring 就会使用它注入 Boy 的属性 Car 中
所以当我们给 GeelyCar 标识 Component 注解时, Boy 开的车就是 GeelyCar, 当我们给 HongQiCar 标识 Component 注解时, Boy 开的车就是 HongQiCar
当然, 我们不可以在 GeelyCar 和 HongQiCar 上同时标识 Component 注解, 因为这样 Spring 就不知道用哪个 Car 进行注入了 --Spring 也有选择困难症(or 一 boy 不能开俩车?)
使用 Spring 启动程序
// 告诉 Spring 从哪个包下扫描 Bean, 不写就是当前包路径 @ComponentScan(basePackages = "com.my.spring.test.demo")public class Main { public static void main(String[] args) { // 将 Main(配置信息)传入到 ApplicationContext(IoC 容器)中 ApplicationContext context = new AnnotationConfigApplicationContext(Main.class); // 从 (IoC 容器) 中获取到我们的 boy Boy boy = (Boy) context.getBean("boy"); // 开车 boy.driver(); }}
这里就可以把我们刚刚介绍 Spring 的知识进行解读了
把具有 ComponentScan 注解 (配置信息) 的 Main 类, 传给 AnnotationConfigApplicationContext(IoC 容器)进行初始化, 就等于: IoC 容器通过获取配置信息进行实例化, 管理和组装 Bean.
而如何进行依赖注入则是在 IoC 容器内部完成的, 这也是本文要讨论的重点
思考
我们通过一个改造案例完整的认识了 Spring 的基本功能, 也对之前的概念有了一个具象化的体验, 而我们还并不知道 Spring 的依赖注入这一内部动作是如何完成的, 所谓知其然更要知其所以然, 结合我们的现有知识, 以及对 Spring 的理解, 大胆猜想推测一下吧(这是很重要的能力哦)
其实猜测就是指: 如果让我们自己实现, 我们会如何实现这个过程?
首先, 我们要清楚我们需要做的事情是什么: 扫描指定包下面的类, 进行实例化, 并根据依赖关系组合好.
步骤分解:
扫描指定包下面的类 -> 如果这个类标识了 Component 注解(是个 Bean) -> 把这个类的信息存起来
进行实例化 -> 遍历存好的类信息 -> 通过反射把这些类进行实例化
根据依赖关系组合 -> 解析类信息 -> 判断类中是否有需要进行依赖注入的字段 -> 对字段进行注入
方案实现
我们现在已经有了一个看起来像是那么一回事的解决方案, 现在就尝试把这个方案实现出来
定义注解
首先我们需要定义出需要用到的注解: ComponentScan,Component,Autowired
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ComponentScan {
- String basePackages() default "";
- }
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Component {
- String value() default "";
- }
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Autowired {
- }
扫描指定包下面的类
扫描指定包下的所有类, 听起来好像一时摸不着头脑, 其实它等同于另一个问题: 如何遍历文件目录?
那么存放类信息应该用什么呢? 我们看看上面例子中 getBean 的方法, 是不是像 Map 中的通过 key 获取 value? 而 Map 中还有很多实现, 但线程安全的却只有一个, 那就是 ConcurrentHashMap(别跟我说 HashTable)
定义存放类信息的 map
private final Map> classMap = new ConcurrentHashMap<>(16);
具体流程, 下面同样附上代码实现:
代码实现, 可以与流程图结合观看:
扫描类信息
- private void scan(Class configClass) {
- // 解析配置类, 获取到扫描包路径
- String basePackages = this.getBasePackages(configClass);
- // 使用扫描包路径进行文件遍历操作
- this.doScan(basePackages);
- }
- private String getBasePackages(Class configClass) {
- // 从 ComponentScan 注解中获取扫描包路径
- ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
- return componentScan.basePackages();
- }
- private void doScan(String basePackages) {
- // 获取资源信息
- URI resource = this.getResource(basePackages);
- File dir = new File(resource.getPath());
- for (File file : dir.listFiles()) {
- if (file.isDirectory()) {
- // 递归扫描
- doScan(basePackages + "." + file.getName());
- }
- else {
- // com.my.spring.example + . + Boy.class -> com.my.spring.example.Boy
- String className = basePackages + "." + file.getName().replace(".class", "");
- // 将 class 存放到 classMap 中
- this.registerClass(className);
- }
- }
- }
- private void registerClass(String className){
- try {
- // 加载类信息
- Class clazz = classLoader.loadClass(className);
- // 判断是否标识 Component 注解
- if(clazz.isAnnotationPresent(Component.class)){
- // 生成 beanName com.my.spring.example.Boy -> boy
- String beanName = this.generateBeanName(clazz);
- // car: com.my.spring.example.Car
- classMap.put(beanName, clazz);
- }
- } catch (ClassNotFoundException ignore) {}
- }
实例化
现在已经把所有适合的类都解析好了, 接下来就是实例化的过程了
定义存放 Bean 的 Map
private final Map beanMap = new ConcurrentHashMap<>(16);
具体流程, 下面同样给出代码实现:
代码实现, 可以与流程图结合观看:
遍历 classMap 进行实例化 Bean
- public void instantiateBean() {
- for (String beanName : classMap.keySet()) {
- getBean(beanName);
- }
- }
- public Object getBean(String beanName){
- // 先从缓存中获取
- Object bean = beanMap.get(beanName);
- if(bean != null){
- return bean;
- }
- return this.createBean(beanName);
- }
- private Object createBean(String beanName){
- Class clazz = classMap.get(beanName);
- try {
- // 创建 bean
- Object bean = this.doCreateBean(clazz);
- // 将 bean 存到容器中
- beanMap.put(beanName, bean);
- return bean;
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }
- private Object doCreateBean(Class clazz) throws IllegalAccessException { // 实例化 bean Object bean = this.newInstance(clazz); // 填充字段, 将字段设值 this.populateBean(bean, clazz); return bean;}
- private Object newInstance(Class clazz){ try { // 这里只支持默认构造器 return clazz.getDeclaredConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); }}
- private void populateBean(Object bean, Class clazz) throws IllegalAccessException { // 解析 class 信息, 判断类中是否有需要进行依赖注入的字段 final Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { Autowired autowired = field.getAnnotation(Autowired.class); if(autowired != null){ // 获取 bean Object value = this.resolveBean(field.getType()); field.setAccessible(true); field.set(bean, value); } }}
- private Object resolveBean(Class clazz){ // 先判断 clazz 是否为一个接口, 是则判断 classMap 中是否存在子类 if(clazz.isInterface()){ // 暂时只支持 classMap 只有一个子类的情况 for (Map.Entry> entry : classMap.entrySet()) { if (clazz.isAssignableFrom(entry.getValue())) { return getBean(entry.getValue()); } } throw new RuntimeException("找不到可以进行依赖注入的 bean"); }else { return getBean(clazz); }}
- public Object getBean(Class clazz){
- // 生成 bean 的名称
- String beanName = this.generateBeanName(clazz);
- // 此处对应最开始的 getBean 方法
- return this.getBean(beanName);
- }
组合
两个核心方法已经写好了, 接下把它们组合起来, 我把它们实现在自定义的 ApplicationContext 类中, 构造方法如下:
- public ApplicationContext(Class configClass) {
- // 1. 扫描配置信息中指定包下的类
- this.scan(configClass);
- // 2. 实例化扫描到的类
- this.instantiateBean();
- }
UML 类图:
测试
代码结构与案例相同, 这里展示一下我们自己的 Spring 是否可以正常运行
运行正常, 中国人不骗中国人
源码会在文末给出
回顾
现在, 我们已经根据设想的方案进行了实现, 运行的情况也达到了预期的效果. 但如果仔细研究一下, 再结合我们平常使用 Spring 的场景, 就会发现这一份代码的不少问题:
1, 无法支持构造器注入, 当然也没有支持方法注入, 这是属于功能上的缺失.
2, 加载类信息的问题, 加载类时我们使用的是 classLoader.loadClass 的方式, 虽然这避免了类的初始化(可千万别用 Class.forName 的方式), 但还是不可避免的把类元信息加载到了元空间中, 当我们扫描包下有不需要的类时, 这就浪费了我们的内存.
3, 无法解决 bean 之间的循环依赖, 比如有一个 A 对象依赖了 B 对象, B 对象又依赖了 A 对象, 这个时候我们再来看看代码逻辑, 就会发现此时会陷入死循环.
4, 扩展性很差, 我们把所有的功能都写在一个类里, 当想要完善功能 (比如以上 3 个问题) 时, 就需要频繁修改这个类, 这个类也会变得越来越臃肿, 别说迭代新功能, 维护都会令人头疼.
优化方案
对于前三个问题都类似于功能上的问题, 功能嘛, 改一改就好了.
我们需要着重关注的是第四个问题, 一款框架想要变得优秀, 那么它的迭代能力一定要好, 这样功能才能变得丰富, 而迭代能力的影响因素有很多, 其中之一就是它的扩展性.
那么应该如何提高我们的方案的扩展性呢, 六大设计原则给了我们很好的指导作用.
在方案中, ApplicationContext 做了很多事情, 主要可以分为两大块
1, 扫描指定包下的类
2, 实例化 Bean
借助单一职责原则的思想: 一个类只做一种事, 一个方法只做一件事.
我们把扫描指定包下的类这件事单独使用一个处理器进行处理, 因为扫描配置是从配置类而来, 那我们就叫他配置类处理器: ConfigurationCalssProcessor
实例化 Bean 这件事情也同样如此, 实例化 Bean 又分为了两件事: 实例化和依赖注入
实例化 Bean 就是相当于一个生产 Bean 的过程, 我们就把这件事使用一个工厂类进行处理, 它就叫做: BeanFactory, 既然是在生产 Bean, 那就需要原料(Class), 所以我们把 classMap 和 beanMap 都定义到这里
而依赖注入的过程, 其实就是在处理 Autowired 注解, 那它就叫做: AutowiredAnnotationBeanProcessor
我们还在知道, 在 Spring 中, 不仅仅只有这种使用方式, 还有 xml,mvc,SpringBoot 的方式, 所以我们将 ApplicationContext 进行抽象, 只实现主干流程, 原来的注解方式交由 AnnotationApplicationContext 实现.
借助依赖倒置原则: 程序应当依赖于抽象
在未来, 类信息不仅仅可以从类信息来, 也可以从配置文件而来, 所以我们将 ConfigurationCalssProcessor 抽象
而依赖注入的方式不一定非得是用 Autowried 注解标识, 也可以是别的注解标识, 比如 Resource, 所以我们将 AutowiredAnnotationBeanProcessor 抽象
Bean 的类型也可以有很多, 可以是单例的, 可以使多例的, 也可以是个工厂 Bean, 所以我们将 BeanFactory 抽象
现在, 我们借助两大设计原则对我们的方案进行了优化, 相比于之前可谓是 "脱胎换骨".
Spring 的设计
在上一步, 我们实现了自己的方案, 并基于一些设想进行了扩展性优化, 现在, 我们就来认识一下实际上 Spring 的设计
那么, 在 Spring 中又是由哪些 "角色" 构成的呢?
1,Bean: Spring 作为一个 IoC 容器, 最重要的当然是 Bean 咯
2,BeanFactory: 生产与管理 Bean 的工厂
3,BeanDefinition: Bean 的定义, 也就是我们方案中的 Class,Spring 对它进行了封装
4,BeanDefinitionRegistry: 类似于 Bean 与 BeanFactory 的关系, BeanDefinitionRegistry 用于管理 BeanDefinition
5,BeanDefinitionRegistryPostProcessor: 用于在解析配置类时的处理器, 类似于我们方案中的 ClassProcessor
6,BeanFactoryPostProcessor: BeanDefinitionRegistryPostProcessor 父类, 让我们可以再解析配置类之后进行后置处理
7,BeanPostProcessor: Bean 的后置处理器, 用于在生产 Bean 的过程中进行一些处理, 比如依赖注入, 类似我们的 AutowiredAnnotationBeanProcessor
8,ApplicationContext: 如果说以上的角色都是在工厂中生产 Bean 的工人, 那么 ApplicationContext 就是我们 Spring 的门面, ApplicationContext 与 BeanFactory 是一种组合的关系, 所以它完全扩展了 BeanFactory 的功能, 并在其基础上添加了更多特定于企业的功能, 比如我们熟知的 ApplicationListener(事件监听器)
以上说的类似其实有一些本末倒置了, 因为实际上应该是我们方案中的实现类似于 Spring 中的实现, 这样说只是为了让大家更好的理解
我们在经历了自己方案的设计与优化后, 对这些角色其实是非常容易理解的
接下来, 我们就一个一个的详细了解一下
BeanFactory
BeanFactory 是 Spring 中的一个顶级接口, 它定义了获取 Bean 的方式, Spring 中还有另一个接口叫 SingletonBeanRegistry, 它定义的是操作单例 Bean 的方式, 这里我将这两个放在一起进行介绍, 因为它们大体相同, SingletonBeanRegistry 的注释上也写了可以与 BeanFactory 接口一起实现, 方便统一管理.
BeanFactory
1,ListableBeanFactory: 接口, 定义了获取 Bean/BeanDefinition 列表相关的方法, 如 getBeansOfType(Class type)
2,AutowireCapableBeanFactory: 接口, 定义了 Bean 生命周期相关的方法, 如创建 bean, 依赖注入, 初始化
3,AbstractBeanFactory: 抽象类, 基本上实现了所有有关 Bean 操作的方法, 定义了 Bean 生命周期相关的抽象方法
4,AbstractAutowireCapableBeanFactory: 抽象类, 继承了 AbstractBeanFactory, 实现了 Bean 生命周期相关的内容, 虽然是个抽象类, 但它没有抽象方法
5,DefaultListableBeanFactory: 继承与实现以上所有类和接口, 是为 Spring 中最底层的 BeanFactory, 自身实现了 ListableBeanFactory 接口
6,ApplicationContext: 也是一个接口, 我们会在下面有专门对它的介绍
SingletonBeanRegistry
1,DefaultSingletonBeanRegistry: 定义了 Bean 的缓存池, 类似于我们的 BeanMap, 实现了有关单例的操作, 比如 getSingleton(面试常问的三级缓存就在这里)
2,FactoryBeanRegistrySupport: 提供了对 FactoryBean 的支持, 比如从 FactoryBean 中获取 Bean
BeanDefinition
BeanDefinition 其实也是个接口(想不到吧), 这里定义了许多和类信息相关的操作方法, 方便在生产 Bean 的时候直接使用, 比如 getBeanClassName
它的大概结构如下(这里举例 RootBeanDefinition 子类):
里面的各种属性想必大家也绝不陌生
同样的, 它也有许多实现类:
1,AnnotatedGenericBeanDefinition: 解析配置类与解析 Import 注解带入的类时, 就会使用它进行封装
2,ScannedGenericBeanDefinition: 封装通过 @ComponentScan 扫描包所得到的类信息
3,ConfigurationClassBeanDefinition: 封装通过 @Bean 注解所得到的类信息
4,RootBeanDefinition:ConfigurationClassBeanDefinition 父类, 一般在 Spring 内部使用, 将其他的 BeanDefition 转化成该类
BeanDefinitionRegistry
定义了与 BeanDefiniton 相关的操作, 如 registerBeanDefinition,getBeanDefinition, 在 BeanFactory 中, 实现类就是 DefaultListableBeanFactory
BeanDefinitionRegistryPostProcessor
插话: 讲到这里, 有没有发现 Spring 的命名极其规范, Spring 团队曾言 Spring 中的类名都是反复推敲才确认的, 真是名副其实呀, 所以看 Spring 源码真的是一件很舒服的事情, 看看类名方法名就能猜出它们的功能了.
该接口只定义了一个功能: 处理 BeanDefinitonRegistry, 也就是解析配置类中的 Import,Component,ComponentScan 等注解进行相应的处理, 处理完毕后将这些类注册成对应的 BeanDefinition
在 Spring 内部中, 只有一个实现: ConfigurationClassPostProcessor
BeanFactoryPostProcessor
所谓 BeanFactory 的后置处理器, 它定义了在解析完配置类后可以调用的处理逻辑, 类似于一个插槽, 如果我们想在配置类解析完后做点什么, 就可以实现该接口.
在 Spring 内部中, 同样只有 ConfigurationClassPostProcessor 实现了它: 用于专门处理加了 Configuration 注解的类
这里串场一个小问题, 如知以下代码:
- @Configuraiton
- public class MyConfiguration{
- @Bean
- public Car car(){
- return new Car(wheel());
- }
- @Bean
- public Wheel wheel(){
- return new Wheel();
- }
- }
问: Wheel 对象在 Spring 启动时, 被 new 了几次? 为什么?
BeanPostProcessor
江湖翻译: Bean 的后置处理器
该后置处理器贯穿了 Bean 的生命周期整个过程, 在 Bean 的创建过程中, 一共被调用了 9 次, 至于哪 9 次我们下次再来探究, 以下介绍它的实现类以及作用
1,AutowiredAnnotationBeanPostProcessor: 用于推断构造器进行实例化, 以及处理 Autowired 和 Value 注解
2,CommonAnnotationBeanPostProcessor: 处理 Java 规范中的注解, 如 Resource,PostConstruct
3,ApplicationListenerDetector: 在 Bean 的初始化后使用, 将实现了 ApplicationListener 接口的 bean 添加到事件监听器列表中
4,ApplicationContextAwareProcessor: 用于回调实现了 Aware 接口的 Bean
5,ImportAwareBeanPostProcessor: 用于回调实现了 ImportAware 接口的 Bean
ApplicationContext
ApplicationContext 作为 Spring 的核心, 以门面模式隔离了 BeanFactory, 以模板方法模式定义了 Spring 启动流程的骨架, 又以策略模式调用了各式各样的 Processor...... 实在是错综复杂又精妙绝伦!
它的实现类如下:
1,ConfigurableApplicationContext: 接口, 定义了配置与生命周期相关操作, 如 refresh
2,AbstractApplicationContext: 抽象类, 实现了 refresh 方法, refresh 方法作为 Spring 核心中的核心, 可以说整个 Spring 皆在 refresh 之中, 所有子类都通过 refresh 方法启动, 在调用该方法之后, 将实例化所有单例
3,AnnotationConfigApplicationContext: 在启动时使用相关的注解读取器与扫描器, 往 Spring 容器中注册需要用的处理器, 而后在 refresh 方法在被主流程调用即可
4,AnnotationConfigwebApplicationContext: 实现 loadBeanDefinitions 方法, 以期在 refresh 流程中被调用, 从而加载 BeanDefintion
5,ClassPathXmlApplicationContext: 同上
从子类的情况可以看出, 子类的不同之处在于如何加载 BeanDefiniton, AnnotationConfigApplicationContext 是通过配置类处理器 (ConfigurationClassPostProcessor) 加载的, 而 AnnotationConfigWebApplicationContext 与 ClassPathXmlApplicationContext 则是通过自己实现 loadBeanDefinitions 方法, 其他流程则完全一致
Spring 的流程
以上, 我们已经清楚了 Spring 中的主要角色以及作用, 现在我们尝试把它们组合起来, 构建一个 Spring 的启动流程
同样以我们常用的 AnnotationConfigApplicationContext 为例
图中只画出了 Spring 中的部分大概流程, 详细内容我们会在后面的章节展开
小结
所谓万事开头难, 本文初衷就是能让大家以由浅入深的方式认识 Spring, 初步建立 Spring 的认知体系, 明白 Spring 的内部架构, 对 Spring 的认知不再浮于表面.
现在头已经开了, 相信后面内容的学习也将水到渠来.
本篇文章既讲是 Spring 的架构设计, 也希望能成为我们以后复习 Spring 整体内容时使用的手册.
最后, 看完文章之后, 相信对以下面试常问的问题回答起来也是轻而易举
1, 什么是 BeanDefinition?
2,BeanFactory 与 ApplicationContext 的关系?
3, 后置处理器的分类与作用?
4,Spring 的主要流程是怎么样的?
如果小伙伴觉得没办法很好回答上来的话就再看看文章, 或者在评论区留下自己的见解吧
好啦, 我是敖丙, 你知道的越多, 你不知道的越多, 我们下期见.
来源: http://developer.51cto.com/art/202201/698795.htm