本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试。

记录日期:2022.1.4

大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习。

框架原理 - Spring(二)之Spring IOC 源码引入refresh()

IOC容器

本文所有Spring 版本 都为 5.3.9。

引入

写一个最基本的启动Spring容器的例子:

public static void main(String[] args) {
   
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationfile.xml");
}

以上代码就可以利用配置文件来启动一个 Spring 容器了,在maven的pom文件中直接在 dependencies 中加上以下依赖。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.9</version>
</dependency>

spring-context 会自动将 spring-core、spring-beans、spring-aop、spring-expression 这几个基础 jar 包带进来,如下:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.9</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.3.9</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.3.9</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.3.9</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

Spring 是渐进式的工具,并不具有很强的侵入性,它的模块也划分得很合理,即使你的应用不是 web 应用,或者之前完全没有使用到 Spring,而你就想用 Spring 的依赖注入这个功能,其实完全是可以的,它的引入不会对其他的组件产生冲突。

简而言之,它并不是和web应用开发强绑定的框架。

ApplicationContext context = new ClassPathXmlApplicationContext(…)其实很好理解,从名字上就能看出,就是在 ClassPath 中寻找 xml 配置文件,根据 xml 配置文件内容来构建 ApplicationContext。当然,除了 ClassPathXmlApplicationContext 以外,我们也还有其他构建 ApplicationContext 的方案可供选择,我们可以来看看的继承结构如下:

因为 Spring 为了适应各种使用场景,提供的各个接口都可能有很多的实现类。对于我们来说,就是梳理一个完整的分支看完。

我们可以看到,ClassPathXmlApplicationContextApplicationContext 接口非常下面的实现;同样的,和它并级的还有FileSystemXmlApplicationContext

  • FileSystemXmlApplicationContext 的构造函数需要一个 xml 配置文件在系统中的路径,其他和 ClassPathXmlApplicationContext 基本上一样。
  • AnnotationConfigApplicationContext 是基于注解来使用的,它不需要配置文件,采用 java 配置类和各种注解来配置,是比较简单的方式,未来是大势所趋。

这里应该主要针对ClassPathXmlApplicationContext来进行说明。

我们先来编写示例代码:

// message 接口
public interface MessageService {
   
    String getMessage();
}
// message 接口实现类
public class MessageServiceImpl implements MessageService {
   
    public String getMessage() {
   
        return "hello world";
    }
}

接下来我们要在resources 目录新建一个配置文件,文件名随意,通常叫 application.xml 或 application-xxx.xml。

public class App {
   
    public static void main(String[] args) {
   
        // 用我们的配置文件来启动一个 ApplicationContext
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
 
        System.out.println("ApplicationContext 启动成功");
 
        // 从 context 中取出我们的 Bean,而不是用 new MessageServiceImpl() 这种方式
        MessageService messageService = context.getBean(MessageService.class);
        // 这句将输出: hello world
        System.out.println(messageService.getMessage());
    }
}

怎么样通过配置文件来启动 Spring 的 ApplicationContext?也就是本文要分析的 IOC 的核心内容。

ApplicationContext 启动过程中,会负责创建实例 Bean,往各个 Bean 中注入依赖等。

源码分析开始

BeanFactory介绍

前面说的 ApplicationContext 其实就是一个 BeanFactory。我们来看下和 BeanFactory 接口相关的主要的继承结构:

需要关注的几个父类和接口如下:

  1. ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的。
  2. ApplicationContext 继承了 HierarchicalBeanFactory,Hierarchical 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系。
  3. AutowireCapableBeanFactory 这个名字中的 Autowire 大家都非常熟悉,它就是用来自动装配 Bean 用的,但是仔细看上图,ApplicationContext 并没有继承它,不过不用担心,不使用继承,不代表不可以使用组合,如果你看到 ApplicationContext 接口定义中的最后一个方法 getAutowireCapableBeanFactory() 就知道了。
  4. ConfigurableListableBeanFactory 也是一个特殊的接口,看图,特殊之处在于它继承了第二层所有的三个接口,而 ApplicationContext 没有。这点之后会用到。

上面五个框出来的类可以去源码中自己先阅读一遍。

IOC容器初始化入口

首先从 ClassPathXmlApplicationContext 的构造方法开始看:

// org.springframework.context.support.ClassPathXmlApplicationContext
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
   
    // 将配置文件作为资源都存放到这个数组中
    @Nullable
    private Resource[] configResources;
    
    public ClassPathXmlApplicationContext() {
   }
 
    // 如果已经有 ApplicationContext 并需要配置成父子关系,那么调用这个构造方法
    public ClassPathXmlApplicationContext(ApplicationContext parent) {
   
      super(parent);
    }
    
    ...
    
    // 最终调用的构造方法
    public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent)
			throws BeansException {
   

		super(parent);
		Assert.notNull(paths, "Path array must not be null");
		Assert.notNull(clazz, "Class argument must not be null");
        // 解析配置文件列表,放置到上面说的那个 configResources 数组中
		this.configResources = new Resource[paths.length];
		for (int i = 0; i < paths.length; i++) {
   
			this.configResources[i] = new ClassPathResource(paths[i], clazz);
		}
        // 核心方法
		refresh();
	}
    ...
}

接下来,就是 refresh() 方法,这里说明一下为什么是 refresh(),而不是 init() 这种名字的方法。因为 ApplicationContext 建立起来以后,其实我们是可以通过调用 refresh() 这个方法重建的,这样会将原来的 ApplicationContext 销毁,然后再重新执行一次初始化操作。

AbstractApplicationContext#refresh()

我们再来看一下refresh()方法的主要内容:

@Override
public void refresh() throws BeansException, IllegalStateException {
   
    // 添加同步锁,startupShutdownMonitor = new Object()
    synchronized (this.startupShutdownMonitor) {
   
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
        prepareRefresh();

        // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
        // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
        // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
        prepareBeanFactory(beanFactory);

        try {
   
            // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
            // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】
            // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
            // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
            postProcessBeanFactory(beanFactory);
			
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
            // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
            // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
            initMessageSource();

            // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
            initApplicationEventMulticaster();

            // 从方法名就可以知道,典型的模板方法(钩子方法),
            // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
            onRefresh();

            // 注册事件***,***需要实现 ApplicationListener 接口。这里不是重点
            registerListeners();

            // 重点,重点,重点
         	// 初始化所有的 singleton beans
         	//(lazy-init 的除外)
            finishBeanFactoryInitialization(beanFactory);

            // 最后,广播事件,ApplicationContext 初始化完成
            finishRefresh();
        }

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

            // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
            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...
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}

突然发现这段refresh()比之前的refresh()多了一个applicationStartup参数调用,默认是一个无实现的DefaultApplicationStartup,它的startend都是空实现。它的作用是在应用程序启动期间标记步骤,并收集有关执行上下文或其处理时间的数据。这个不是重点。