Spring源码阅读系列之初始化容器整体流程介绍

上篇文章提到,因为 Spring 源码非常庞大,所以阅读 Spring 源码的最直接有效的办法就是 Debug,后续源码也是基于这种方式去读。接下来看看一个最简单的例子

  1. 创建一个UserService接口和其实现类

public interface UserService {
    void save();
}

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("执行user信息保存操作");
    }
}

  1. 创建一个applicationContext.xml文件

<?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">

    <bean id="userService" class="UserServiceImpl"></bean>
</beans>

  1. 编写一个 JUnite 测试类

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JuniteTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 1.通过byType的方式获取bean实例
        UserService service = context.getBean(UserService.class);
        service.save();
        System.out.println("-----------");
        // 2.通过byName的方式获取bean实例
        UserService service1 = (UserService) context.getBean("userService");
        service1.save();
    }
}

  1. 从 Junite 测试类 创建Context开始,debug 进去可以看到下面这段代码

public ClassPathXmlApplicationContext(
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
      throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
      // 这里就是真正初始化Spring容器的地方,也就是我在上一篇文章最后提供的那张图里的流程
      // 有同学会问,那Tomcat启动的时候是怎么初始化Spring容器的呢?
      // 其实也比较简单,一般在Tomcat里面的web.xml文件配置了一个监听器,大概长这个样子
      //<listener>
      //  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      //</listener>
      // 该监听器调用contextInitialized()方法,一路点进去可以看到最后也是调用了AbstractApplicationContext.refresh()方法
      refresh();
    }
}

  1. 找到了 Spring 容器初始化的入口,接下来就来详细看看我们单测里面用到的userService实例是怎么被创建出来的

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      // Step1:其主要作用就是为当前的context上下文初始化一些基础参数
      // 比如说:初始化时间,活动状态,日志信息等等,暂时不重要,不需要关注其太多的实现细节
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      // Step2: 从注释上看是通知子类去刷新内部bean工厂。解释的好像也特别抽象不知道它到底想干啥
      // **重要** 其实在这个方法里面做了几个非常重要的事情,创建bean工厂,并将BeanDefinitions对象(bean实例的定义,这个对象也会贯穿整个Spring源码)存到bean工厂中
      // 创建出来的ConfigurableListableBeanFactory,这个bean工厂会贯穿整个Spring源码
      // 它功能是存放beanDefinition定义,后续创建出来的bean实例也存放这这个工厂里面
      // 暂时先理解到这里就可以了,后续我们会展开其里面的具体实现
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      // Step3:配置bean工厂的上下文,
      // 比如在这个里面配置了ClassLoader,
      // 配置了BeanPostProcessor(bean后置处理器,这玩意儿会在bean实例初始化前后做一些增强工作,后面会详细介绍)
      // 注册了三个默认bean实例,分别是 “environment”、“systemProperties”、“systemEnvironment” 了解即可
      prepareBeanFactory(beanFactory);

      try {
        // Allows post-processing of the bean factory in context subclasses.
        // Step4:本方法没有具体实现,是一个扩展点,开发人员可以根据自己的情况做具体的实现
        postProcessBeanFactory(beanFactory);

        // Invoke factory processors registered as beans in the context.
        // Step5:bean工厂后置处理器,主要是对beanFactory对象做一些后置操作
        // 和registerBeanPostProcessor名字有点像,只不过后者操作的对象bean实例
        // 后续会深入了解其实现原理
        invokeBeanFactoryPostProcessors(beanFactory);

        // Register bean processors that intercept bean creation.
        // Step6:注册所有实现了BeanPostProcessor接口的对象,
        // 注意:这里只是注册实例,并没有执行其方法,方法具体调用时机是在bean实例初始化前后执行的
        registerBeanPostProcessors(beanFactory);

        // Initialize message source for this context.
        // Step7:初始化message资源,做一些国际化相关操作,了解即可
        initMessageSource();

        // Initialize event multicaster for this context.
        // Step8:初始化事件广播器,用于发送相关事件
        // 比如注册了一个bean实例的时候就会发送一个事件
        initApplicationEventMulticaster();

        // Initialize other special beans in specific context subclasses.
        // Step9:在一些特定的context中初始化一些特殊的bean,没有具体实现,留给开发者自定义一些操作
        onRefresh();

        // Check for listener beans and register them.
        // Step10:注册监听器,了解即可
        registerListeners();

        // Instantiate all remaining (non-lazy-init) singletons.
        // Step11:初始化所有非懒加载的bean实例,注意是非懒加载的bean
        //**非常重要** 我们代码中需要用到的bean实例几乎都是从这里创建出来的,后面会展开其具体实现
        finishBeanFactoryInitialization(beanFactory);

        // Last step: publish corresponding event.
        // Step12:完成上线文刷新,发布上下文初始化完毕的事件
        finishRefresh();
      }

      catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
          logger.warn("Exception encountered during context initialization - " +
              "cancelling refresh attempt: " + ex);
        }

        // Destroy already created singletons to avoid dangling resources.
        // 当创建bean发生异常时,先销毁已经创建出来的单例bean实例,从而避免资源一直被占用。
        // 这样的做法有点像关闭资源链接,比如当程序联系MySQL或Redis的时候,程序发生异常时需要将链接断开一样
        destroyBeans();

        // Reset 'active' flag.
        // 重置上下文的标识
        cancelRefresh(ex);

        // Propagate exception to caller.
        throw ex;
      }

      finally {
        // Reset common introspection caches in Spring's core, since we
        // might not ever need metadata for singleton beans anymore...
        // Step13:重置公共的一些缓存数据
        resetCommonCaches();
      }
    }
}

总结

Spring 容器初始化,共经历了 13 步;其中尤其需要重点关注的是:

  • Step2,初始化 Spring 容器,并构建了BeanDefinition定义

  • Step5BeanFactoryPostProcessor,对BeanFactory做一些后置操作

  • Step7BeanPostProcessor,对 bean 实例在初始化前后做一些增强工作Step11,对剩余所有的非懒加载的BeanDefinition(bean 定义)执行 bean 实例化操作

下一篇文章我们来讲讲 Step2,obtainFreshBeanFactory()实现原理。


欢迎关注我,共同学习

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章