第 5 节 Spring IOC容器的设计与初始化流程

1 Spring IOC 容器的使用

1.1 IOC 的概念

​ SpringBoot 框架提供了一套解决方案来简化 Spring 应用的开发,不过 SpringBoot 框架在内部还是基于 Spring 框架来实现对 Java bean 对象的管理的。

​ 具体为基于 Spring 的 IOC 容器来完成 Java bean 对象的创建和对象间的依赖注入,所以称为 IOC 容器。IOC 的英文全称为 Inverse Of Control,意思是控制反转,即对象的创建与管理工作不在应用自身来维护,而是交给 Spring 框架来完成。所以在讲解 SpringBoot 框架的启动实现之前,在本节先介绍 Spring IOC 容器的相关概念与设计。

1.2 IOC 容器的使用

​ Spring IOC 容器体现在 Spring 框架的源码中就是 Application 接口,Application 接口的体系结构比较复杂,不过根据应用中所使用的 Spring 框架的配置方式的不同,即根据是基于 xml 文件配置还是 Java 类配置,包含两个常见的实现类,分别为基于 xml 文件的配置对应的 ClassPathXmlApplicationContext 和基于 Java 类对象配置的 AnnotationConfigApplicationContext。

​ 如下代码演示基于 Application 接口的实现类 AnnotationConfigApplicationContext 来完成 Java 对象的创建和依赖注入。

拓展知识:Spring 框架一般用在 Java Web 项目中,不过根据 Spring 的 IOC 容器的定义,Spring 框架本身是与 Java Web 没有直接关系的,即可以单独使用 Spring 框架来对普通 Java 项目的 Java 对象的创建和依赖进行管理。

  1. Java 对象依赖关系:对象 A 包含一个类型为 ObjectB 的属性,在 A 对象的 showB 方法中打印属性对象 B 的 name 属性:

    类型 ObjectA 定义:包含一个类型 ObjectB 的对象属性

    @Component
    public class ObjectA {
    
        @Autowired
        private ObjectB objectB;
    
        public ObjectB getObjectB() {
            return objectB;
        }
    
        public void setObjectB(ObjectB objectB) {
            this.objectB = objectB;
        }
    }
    

    类型 ObjectB 定义:包含一个类型为 String 的成员属性 name

    @Component
    public class ObjectB {
        private String name = "B";
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
  2. 在一个 Java 配置类中配置 Java 类的组件自动扫描,从而实现 A 对象与 B 对象的创建与依赖注入。如下配置类 ApplicationConfig 是一个空类,主要是通过 @Configuration 注解来表明这是一个配置类,通过 @ComponentScan 注解来启用类对象的自动扫描注入功能,关于这两个注解的更多知识在后面小节详细分析。

    @Configuration
    @ComponentScan
    public class ApplicationConfig {
    
    }
    
  3. 通过 AnnotationConfigApplicationContext 类来创建 Spring IOC 容器,并从 IOC 容器中获取 A 对象和调用 A 对象的 showB 方法。如下可以看到打印出了 B 对象的 name,所以实现了 Java 对象的创建与依赖注入:

    public class BootApplication {
    
        public static void main(String[] args) {
            // IOC容器对象
            AnnotationConfigApplicationContext iocContainer = new AnnotationConfigApplicationContext();
            iocContainer.register(ApplicationConfig.class);
            // 创建IOC容器与初始化IOC容器
            iocContainer.refresh();
    
            // 验证IOC容器对Java对象的创建与依赖注入
            ObjectA objectA = iocContainer.getBean(ObjectA.class);
            System.out.println("ObjectB's name is " + objectA.getObjectB().getName());
        }
    }
    

    打印结果如下:

    ObjectB's name is B

1.3 SpringBoot 框架的 IOC 容器实现

​ SpringBoot 框架在内部创建 Spring 的 IOC 容器的流程与以上代码基于 AnnotationConfigApplicationContext 类来创建 IOC 容器类似。不同之处是 SpringBoot 框架对 AnnotationConfigApplicationContext 类进行了进一步拓展与实现,在内部嵌套了 Servlet 引擎的实现,如 Tomcat 或 Jetty 等功能组件的嵌入,从而实现了 SpringBoot 应用以 jar 包方式的 ”自启动“。关于 SpringBoot 框架的这些机制的实现原理在后续章节详细分析,敬请期待。

tips:在 SpringBoot 框架的 Application 接口拓展实现类中,对于 Spring IOC 容器的 Java 对象的创建与依赖注入的流程还是与 Spring 框架保持一致的,SpringBoot 并没有对主流程进行修改,而是通过拓展实现来加入自身框架工作所需的相关功能,如以上所说的嵌入 Servlet 引擎组件等。

2 Spring IOC 容器的初始化流程

2.1 AbstractApplicationContext 的 refresh 方法:定义 IOC 容器的初始化流程

​ 以上讲解了基于 Spring 框架的 Application 接口实现类 AnnotationConfigApplicationContext 来创建 Spring IOC 容器以及进行 Java 对象的管理。

​ 由 Spring 框架的内部源码实现可知,Spring IOC 容器的创建和初始化是在抽象类 AbstractApplicationContext 的 refresh 方法中定义的,所以在以上例子中,在 BootApplication 的 main 方法中调用了 AnnotationConfigApplicationContext 的 refresh 方法。

​ Spring 框架在 AbstractApplicationContext 的 refresh 方法中定义了 Spring IOC 容器的初始化流程,refresh 方法的具体定义如下:

public void refresh() throws BeansException, IllegalStateException {
   // 加互斥锁,保证在任何时候,只有一个线程对一个 Application 实现类对象进行操作
   synchronized (this.startupShutdownMonitor) {
      // IOC容器相关周边属性的加载,如环境属性properties的加载等
      prepareRefresh();

      // 创建Bean对象工厂,加载Java对象的元数据并保存到对应的BeanDefinition对象
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 对Bean对象工厂进行预处理
      prepareBeanFactory(beanFactory);

      try {
         // Bean对象工厂的后置处理
         postProcessBeanFactory(beanFactory);
         invokeBeanFactoryPostProcessors(beanFactory);
        
         // 注册部分特殊的Bean对象后置处理器
         registerBeanPostProcessors(beanFactory);

         // i18相关国际化Message处理
         initMessageSource();

         // 初始化IOC容器事件广播器
         initApplicationEventMulticaster();

         // 提供给ApplicationContext接口的不同实现类来拓展实现,
         // 在该方法中自定义该实现类自身具有特殊含义 bean 对象的初始化方式
         onRefresh();

         // 调用IOC容器监听器
         registerListeners();

         // 实例化所有的单例对象,即此处完成单例对象的创建以及相关依赖注入
         finishBeanFactoryInitialization(beanFactory);

         // IOC容器初始化完成,分发相关的事件
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }
         // 删除已经创建的bean对象
         destroyBeans();
				 // 取消相关已经执行过的初始化流程
         cancelRefresh(ex);

         throw ex;
      }

      finally {
         // 重置相关内存缓存
         resetCommonCaches();
      }
   }
}

​ 在 refesh 方法的内部实现当中,首先需要使用 synchronized 关键字来保证在任何时候,只能存在一个线程调用当前的 Application 接口实现类对象的 refresh 方法,即不能存在两个线程同时调用 refresh 方法来初始化同一个 IOC 容器。其次与 Java bean 对象的的创建相关的方法的调用依次如下:

  1. prepareRefresh 方法:完成外部属性的加载。如 properties 文件定义的属性值的加载,从而可以在后续对 Java 对象的属性进行赋值操作,如对 Java 对象中使用了 @Value 注解的属性进行赋值;
  2. obtainFreshBeanFactory 方法:完成 Java 对象对应的元数据的加载。对于每个 Java 对象都使用一个对应的 BeanDefinition 类的对象来维护,如从 XML 配置文件的 bean 标签解析,或者通过组件自动扫描的方式来加载,最终使用一个 BeanFactory 接口实现类对象,即 Bean 工厂来维护;
  3. postProcessBeanFactory 方法:对步骤 2 中创建的 Bean 工厂进行后置处理,如处理 @Configuration 注解的配置类,从该配置类中获取更多的 Java 对象的元数据,如在该配置类内部使用 @Bean 注解的方法会被加载和对应到一个 Java 对象;
  4. onRefresh 方法:该方法主要是提供了一种方式来给 Applicaiton 接口的不同实现类来拓展实现,自定义自身的具有特殊含义的 Java 对象的初始化过程,从而实现拓展性,这个也是 SpringBoot 框架自定义自身功能组件 对象的核心实现方法;
  5. finishBeanFactoryInitialization:以上过程只是完成了当前应用的相关 Java 对象的元数据的加载,但是并没有实际进行 Java 对象的创建,即没有调用如 new 关键字创建一个 Java 对象。所以这个方法是根据 Java 对象的元数据信息 BeanDefinition,完成单例 Java 对象的创建。

tips:由于 Spring 框架源码模块非常多,导致很多想学习 Spring 源码的同学不知道从哪个模块看起。根据我个人阅读 Spring 源码的经验,以上这个 refresh 方法是我们学习 Spring 框架源代码时需要重点关注的一个方法,也是 Spring 框架源码阅读的入口,即可以从这个方法展开对 Spring 框架各个模块的源代码的学习。

可以是说搞懂了以上介绍的 refresh 方法内部的各个调用的实现,那对 Spring 框架的学习也就差不多了_

3 总结

​ 在本小节我们分析了解 Spring IOC 容器的使用和设计对学习 SpringBoot 框架的重要性,即 SpringBoot 框架简化了 Spring 应用的开发难度。不过在 SpringBoot 的内部实现层面还是依赖 Spring 框架的 IOC 容器来进行 Java 对象的管理。所以为了更好的理解 SpringBoot 框架的整体工作原理,需要先了解 Spring 框架的 IOC 容器的使用和工作原理。

​ 其次我们进一步分析了 Spring IOC 容器的创建和初始化流程。具体为对 Application 接口的抽象实现类 AbstractApplication 的 refresh 方法的分析。该方法定义了 IOC 容器的整体初始化流程。在该方法从上到下依次完成了环境属性的加载、Bean 对象工厂的创建和后置处理、Bean 对象元数据的加载、具有特殊含义 Bean 对象的处理,以及最终完成单例 bean 对象的创建与后置处理,如实现依赖注入。

代码仓库

1.2 节:https://github.com/yzxie/java-framework-demo/tree/master/spring-demo

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值