Spring Boot - IOC(一)

1. SpringFramework与SpringBoot的IOC

1.1 重新认识ApplicationContext

我们在初学 SpringFramework 的时候,你接触的第一样IOC容器一般都是 ClassPathXmlApplicationContext ,而且我们使用 ApplicationContext 来接收它。如下所示:

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

所以我们的一般认知中,ApplicationContext 是最顶级的IOC容器,那实际上是这样吗?

1.1.1 ApplicationContext并不是最顶级容器

翻看 ApplicationContext 的源码:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver

发现它继承了好多个接口!换句话说,他根本算不上最顶级的IOC容器。那最顶级的容器是什么呢?文档注释中没有很明确的表述,我们来翻看 SpringFramework 的官方文档:

docs.spring.io/spring/docs…

第一章就叫The IoC Container ,在1.1节就已经明确的给出了答案:

img

The BeanFactory interface provides an advanced configuration mechanism capable of managing any type of object.ApplicationContext is a sub-interface of BeanFactory.

BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。ApplicationContextBeanFactory 的子接口。

由此可见 BeanFactory 才是IOC容器最顶级的接口。

为什么SpringFramework建议使用 ApplicationContext 而不是 BeanFactory,以至于我们一开始都不知道他呢?官方文档的1.16.1节有给出解释:

docs.spring.io/spring/docs…

You should use an ApplicationContext unless you have a good reason for not doing so, with GenericApplicationContext and its subclass AnnotationConfigApplicationContext as the common implementations for custom bootstrapping. These are the primary entry points to Spring’s core container for all common purposes: loading of configuration files, triggering a classpath scan, programmatically registering bean definitions and annotated classes, and (as of 5.0) registering functional bean definitions.

Because an ApplicationContext includes all the functionality of a BeanFactory, it is generally recommended over a plain BeanFactory, except for scenarios where full control over bean processing is needed. Within an ApplicationContext (such as the GenericApplicationContext implementation), several kinds of beans are detected by convention (that is, by bean name or by bean type — in particular, post-processors), while a plain DefaultListableBeanFactory is agnostic about any special beans.

For many extended container features, such as annotation processing and AOP proxying, the BeanPostProcessor extension point is essential. If you use only a plain DefaultListableBeanFactory, such post-processors do not get detected and activated by default. This situation could be confusing, because nothing is actually wrong with your bean configuration. Rather, in such a scenario, the container needs to be fully bootstrapped through additional setup.

除非有充分的理由,否则你应使用 ApplicationContext,除非将 GenericApplicationContext 及其子类 AnnotationConfigApplicationContext 作为自定义引导的常见实现,否则应使用 ApplicationContext。这些是用于所有常见目的的Spring核心容器的主要入口点:加载配置文件,触发类路径扫描,以编程方式注册Bean定义和带注解的类,以及(从5.0版本开始)注册功能性Bean定义

因为 ApplicationContext 包含 BeanFactory 的所有功能,所以通常建议在纯 BeanFactory 上使用,除非需要对Bean处理的完全控制。在 ApplicationContext(例如 GenericApplicationContext 实现)中,按照约定(即,按Bean名称或Bean类型(尤其是后处理器))检测到几种Bean,而普通的 DefaultListableBeanFactory 不知道任何特殊的Bean。

对于许多扩展的容器功能(例如注解处理和AOP代理),BeanPostProcessor 扩展点是必不可少的。如果仅使用普通的 DefaultListableBeanFactory,则默认情况下不会检测到此类后处理器并将其激活。这种情况可能会造成混淆,因为您的bean配置实际上并没有错。而是在这种情况下,需要通过其他设置完全引导容器。

文档已经描述的很清楚了,ApplicationContext 的功能更强大,所以选择用它。

1.1.2 ApplicationContext的接口继承

利用IDEA查看 ApplicationContext 接口的继承关系,我们只关注它与 BeanFactory 的关系:

它通过两个中间的接口,最终继承到 BeanFactory 中。那这两个接口分别又是什么呢?

1.1.2.1 ListableBeanFactory

它的文档注释原文翻译:

Extension of the BeanFactory interface to be implemented by bean factories that can enumerate all their bean instances, rather than attempting bean lookup by name one by one as requested by clients. BeanFactory implementations that preload all their bean definitions (such as XML-based factories) may implement this interface. If this is a HierarchicalBeanFactory, the return values will not take any BeanFactory hierarchy into account, but will relate only to the beans defined in the current factory. Use the BeanFactoryUtils helper class to consider beans in ancestor factories too. The methods in this interface will just respect bean definitions of this factory. They will ignore any singleton beans that have been registered by other means like org.springframework.beans.factory.config.ConfigurableBeanFactory's registerSingleton method, with the exception of getBeanNamesOfType and getBeansOfType which will check such manually registered singletons too. Of course, BeanFactory's getBean does allow transparent access to such special beans as well. However, in typical scenarios, all beans will be defined by external bean definitions anyway, so most applications don't need to worry about this differentiation.

它是 BeanFactory 接口的扩展,它可以实现枚举其所有bean实例,而不是按客户的要求按名称一一尝试进行bean查找。预加载其所有bean定义的 BeanFactory 实现(例如,基于XML的工厂)可以实现此接口。

如果实现类同时也实现了 HierarchicalBeanFactory,返回值也不会考虑任何 BeanFactory 层次结构,而仅与当前工厂中定义的bean有关。但可以使用 BeanFactoryUtils 工具类来获取父工厂中的bean。

该接口中的方法将仅遵守该工厂的bean定义。他们将忽略通过其他方式(例如 ConfigurableBeanFactoryregisterSingleton 方法)注册的任何单例bean,但 getBeanNamesOfTypegetBeansOfType 除外,它们也将检查此类手动注册的单例。当然,BeanFactorygetBean 确实也允许透明访问此类特殊bean。但是,在典型情况下,无论如何,所有bean都将由外部bean定义来定义,因此大多数应用程序不必担心这种区别。

从文档注释中可以获取到的最重要的信息:它可以提供Bean的迭代

1.1.2.2 HierarchicalBeanFactory

它的文档注释原文翻译:

Sub-interface implemented by bean factories that can be part of a hierarchy. The corresponding setParentBeanFactory method for bean factories that allow setting the parent in a configurable fashion can be found in the ConfigurableBeanFactory interface.

由Bean工厂实现的子接口,可以是层次结构的一部分。

可以在 ConfigurableBeanFactory 接口中找到用于bean工厂的相应 setParentBeanFactory 方法,该方法允许以可配置的方式设置父对象。

文档注释中写的比较模糊,但可以大概看出来它涉及到层次。这个接口有一个方法,可以彻底帮我们解决疑惑:

/**
* Return the parent bean factory, or {@code null} if there is none.
*/
@Nullable
BeanFactory getParentBeanFactory();

获取父工厂?我们在一开始学 SpringMVC 的时候了解到,在原生的Web开发中,配置 SpringFramework 和 SpringMVC,是需要配置父子容器的!

换言之,这个接口是实现多层嵌套容器的支撑

1.1.3 ApplicationContext的其他特征

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver

回到 ApplicationContext 的接口定义,它还继承了几个接口:

1.1.3.1 EnvironmentCapable

文档注释原文翻译:

Interface indicating a component that contains and exposes an Environment reference.

实现了此接口的类有应该有一个 Environment 类型的域,并且可以通过 getEnvironment 方法取得。

这个接口只有一个方法:

public interface EnvironmentCapable {
    
    /**
    * Return the {@link Environment} associated with this component.
    */
    Environment getEnvironment();
}

发现是跟 Environment 相关的。这个 **Environment** 的概念非常重要,会在后续IOC容器的解析时起到很大作用,后面会详细解释。

1.1.3.2 MessageSource

翻看它的文档注释:

Strategy interface for resolving messages, with support for the parameterization and internationalization of such messages.

用于解析消息的策略接口,并支持此类消息的参数化和国际化。

很明显,它是实现国际化的接口。说明 ApplicationContext 还支持国际化。

1.1.3.3 ApplicationEventPublisher

字面意思都很容易理解:应用事件发布器。它的文档注释:

Interface that encapsulates event publication functionality.

封装事件发布功能的接口。

1.1.3.4 ResourcePatternResolver

字面意思也能理解:资源模式解析器。它的文档注释:

Strategy interface for resolving a location pattern (for example, an Ant-style path pattern) into Resource objects. This is an extension to the ResourceLoader interface. A passed-in ResourceLoader (for example, an org.springframework.context.ApplicationContext passed in via org.springframework.context.ResourceLoaderAware when running in a context) can be checked whether it implements this extended interface too. PathMatchingResourcePatternResolver is a standalone implementation that is usable outside an ApplicationContext, also used by ResourceArrayPropertyEditor for populating Resource array bean properties. Can be used with any sort of location pattern (e.g. "/WEB-INF/*-context.xml"): Input patterns have to match the strategy implementation. This interface just specifies the conversion method rather than a specific pattern format. This interface also suggests a new resource prefix "classpath*:" for all matching resources from the class path. Note that the resource location is expected to be a path without placeholders in this case (e.g. "/beans.xml"); JAR files or classes directories can contain multiple files of the same name.

策略接口,用于将位置模式(例如,Ant样式的路径模式)解析为Resource对象。

这是 ResourceLoader 接口的扩展。可以检查传入的 ResourceLoader(例如,在上下文中运行时通过 ResourceLoaderAware 传入的 ApplicationContext)是否也实现了此扩展接口。

PathMatchingResourcePatternResolver 是一个独立的实现,可在 ApplicationContext 外部使用,ResourceArrayPropertyEditor也使用它来填充Resource数组Bean属性。

可以与任何类型的位置模式一起使用(例如 "/WEB-INF/*-context.xml"):输入模式必须与策略实现相匹配。该接口仅指定转换方法,而不是特定的模式格式。 此接口还为类路径中的所有匹配资源建议一个新的资源前缀 "classpath*:"。请注意,在这种情况下,资源位置应该是没有占位符的路径(例如 "/beans.xml"); jar包或类目录可以包含多个相同名称的文件。

有过SSH/SSM整合的小伙伴,一定能很清晰的理解上面的意思。我们之前在web.xml中配置 ContextLoaderListener ,并且声明 contextConfigLocation 时,配置的参数值就是类似于上面的格式。

至此,ApplicationContext 的结构已经分析完毕,下面咱来看 ApplicationContext 的子接口和一些重要的实现类。

1.1.4 ConfigurableApplicationContext

很明显,它是一个可配置的 ApplicationContext。它可配置在什么地方呢?我们来对比一下 ConfigurableApplicationContextApplicationContext

//ApplicationContext
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    
    @Nullable
    String getId();
    
    String getApplicationName();
    
    //......
}

//ConfigurableApplicationContext
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
    
    void setId(String id);
    
    void setParent(@Nullable ApplicationContext parent);
    
    //......
}

发现 ApplicationContext 中全部都是get方法,但在 ConfigurableApplicationContext 中开始出现了set方法。

ConfigurablApplicationContext 的文档注释原文翻译:

SPI interface to be implemented by most if not all application contexts. Provides facilities to configure an application context in addition to the application context client methods in the ApplicationContext interface. Configuration and lifecycle methods are encapsulated here to avoid making them obvious to ApplicationContext client code. The present methods should only be used by startup and shutdown code.

它是一种SPI接口,将由大多数(如果不是全部)ApplicationContext 的子类实现。除了 ApplicationContext 接口中的应用程序上下文客户端方法外,还提供了用于配置 ApplicationContext 的功能。

配置和生命周期方法都封装在这里,以避免这些代码显式的暴露给 ApplicationContext 客户端代码。本方法仅应由启动和关闭容器的代码使用。

其实这个接口是一个非常关键的核心接口。它包含了最核心的方法:refresh,它的作用会在后续IOC容器的启动刷新时详细解析。

1.1.5 AbstractApplicationContext

它是 ConfigurableApplicationContext 的第一级实现类,同时也是抽象类。它的文档注释原文翻译:

Abstract implementation of the ApplicationContext interface. Doesn't mandate the type of storage used for configuration; simply implements common context functionality. Uses the Template Method design pattern, requiring concrete subclasses to implement abstract methods. In contrast to a plain BeanFactory, an ApplicationContext is supposed to detect special beans defined in its internal bean factory: Therefore, this class automatically registers BeanFactoryPostProcessors, BeanPostProcessors, and ApplicationListeners which are defined as beans in the context. A MessageSource may also be supplied as a bean in the context, with the name "messageSource"; otherwise, message resolution is delegated to the parent context. Furthermore, a multicaster for application events can be supplied as an "applicationEventMulticaster" bean of type ApplicationEventMulticaster in the context; otherwise, a default multicaster of type SimpleApplicationEventMulticaster will be used. Implements resource loading by extending DefaultResourceLoader. Consequently treats non-URL resource paths as class path resources (supporting full class path resource names that include the package path, e.g. "mypackage/myresource.dat"), unless the getResourceByPath method is overridden in a subclass.

ApplicationContext 接口的抽象实现。不强制用于配置的存储类型;简单地实现通用上下文功能。这个类使用模板方法模式,需要具体的子类来实现抽象方法。

与普通 BeanFactory 相比,ApplicationContext 应该检测其内部bean工厂中定义的特殊bean:因此,此类自动注册在上下文中定义为bean的 BeanFactoryPostProcessorsBeanPostProcessorsApplicationListeners

一个 MessageSource 也可以在上下文中作为bean提供,名称为“messageSource”。否则,将消息解析委托给父上下文。此外,可以在上下文中将用于应用程序事件的广播器作为类型为 ApplicationEventMulticaster"applicationEventMulticaster" bean提供。否则,将使用类型为 SimpleApplicationEventMulticaster 的默认广播器。

通过扩展 DefaultResourceLoader 实现资源加载。因此,除非在子类中覆盖了 getResourceByPath 方法,否则将非URL资源路径视为类路径资源(支持包含包路径的完整类路径资源名称,例如 "mypackage / myresource.dat")。

从文档注释中可以看出,它已经实现了 ConfigurableApplicationContext 接口,但里面提供了几个模板方法,用于子类重写(多态)。

这个类的refresh方法是将来IOC容器启动刷新时要分析的核心方法,后续会详细解析。

1.1.6 常用的ApplicationContext的实现类

前面我们了解完 ApplicationContextBeanFactory 的关系,对这个接口以及子接口、抽象实现类也有了一个最基本的认知。下面我们对 SpringFramework 中最常用的两个IOC容器实现类来简单介绍一下。

1.1.6.1 ClassPathXmlApplicationContext

我们都很熟悉,在一开始 SpringFramework 入门的时候就用过了。它的类定义和继承结构图:

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
img

在继承关系图中,可以发现 ClassPathXmlApplicationContext 的几个特征:基于XML可刷新的可配置的

在 SpringFramework 的官方文档1.2节有介绍基础IOC容器的使用,这里面大量介绍了 ClassPathXmlApplicationContext,小册不作过多解释。

docs.spring.io/spring/docs…

1.1.6.2 AnnotationConfigApplicationContext

我们在一开始学习启动原理时也用过了,它是使用注解配置来加载初始化IOC容器的。它的类定义和继承结构图:

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry
img

它的继承关系相对简单,而且还实现了 Annotation 相关的接口。

在 SpringFramework 的官方文档1.12节,有专门的基于Java配置的容器的介绍。

docs.spring.io/spring/docs…

img

它配合的注解咱也见过不少了。由于小册主要讲解原理和源码,对于这些容器和注解的基本使用不作过多介绍。

1.2 SpringBoot对IOC容器的扩展

【该部分只作为前置知识,可以先不了解】

在spring-boot的jar包中,org.springframework.boot.web 路径下有一个 context 包,里面有两个接口:WebServerApplicationContextConfigurableWebServerApplicationContext

翻看 WebServerApplicationContext 的接口定义:

public interface WebServerApplicationContext extends ApplicationContext

发现它直接继承了 ApplicationContext,说明它与上面的提到的 ApplicationContext 的子接口都没关系了,这是独成一套。它的文档注释原文翻译:

Interface to be implemented by application contexts that create and manage the lifecycle of an embedded WebServer.

由创建和管理嵌入式Web服务器的生命周期的应用程序上下文实现的接口。

它与嵌入式Web服务器有关系。而我们之前学习 SpringBoot 的时候,就已经了解 SpringBoot 的一大优势就是嵌入式Web服务器。它在后续的IOC容器启动时也会有相关介绍。

利用IDEA查看这个接口的子接口和实现类:

img

发现一共有5个实现类,一个子接口(恰好就是之前看到的 ConfigurableWebServerApplicationContext)。这里面的 ApplicationContext 会在后续分析启动过程时会遇见,此处仅做接触了解。

2. SpringBoot准备IOC容器

2.1 main方法进入

从最简单的入门程序开始:

@SpringBootApplication
public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
}

2.2 进入SpringApplication.run方法

进入run方法,可以发现执行的 SpringBoot 应用启动操作分为两步:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    // 调下面重载的方法
    return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
    }

run方法返回的是 ApplicationContext 的子接口:ConfigurableApplicationContext ,之前我们已经了解过了,不再赘述。

底下的run方法分为两步,分开来看:

2.3 new SpringApplication(primarySources):创建SpringApplication

最终调用的构造方法是下面的两参数方法。

private Set<Class<?>> primarySources;

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // resourceLoader为null
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 将传入的DemoApplication启动类放入primarySources中,这样应用就知道主启动类在哪里,叫什么了
    // SpringBoot一般称呼这种主启动类叫primarySource(主配置资源来源)
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 3.1 判断当前应用环境
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 3.2 设置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 3.3 设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 3.4 确定主配置类
    this.mainApplicationClass = deduceMainApplicationClass();
}

暂且不看这个方法的具体实现,先看一眼构造方法的文档注释:

Create a new SpringApplication instance. The application context will load beans from the specified primary sources (see class-level documentation for details. The instance can be customized before calling run(String...).

创建一个新的 SpringApplication 实例。应用程序上下文将从指定的主要源加载Bean(有关详细信息,请参见类级别的文档)。可以在调用run(String...)之前自定义实例。

文档中描述可以在run方法之前自定义实例,换句话说,可以手动配置一些 SpringApplication 的属性。

【如果小伙伴没有见过自定义配置 SpringApplication,请继续往下看;了解的小伙伴请跳过3.0节】

2.3.0 自定义SpringApplication

@SpringBootApplication
public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(DemoApplication.class);
        springApplication.setWebApplicationType(WebApplicationType.SERVLET); //强制使用WebMvc环境
        springApplication.setBannerMode(Banner.Mode.OFF); //不打印Banner
        springApplication.run(args);
    }
    
}

下面对 SpringApplication 的构造方法实现中每一步作详细解析:

2.3.1 WebApplicationType.deduceFromClasspath:判断当前应用环境

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

这个方法没有文档注释,但方法名和返回值类型已经可以描述方法用途:从classpath下判断当前SpringBoot应用应该使用哪种环境启动

上面的代码块中我把一些这个类中定义的常量也贴了进来,方便小伙伴们阅读。它们是描述了一些 Servlet 的全限定名、DispatcherServlet 的全限定名等等,它们的用途是配合下面的方法判断应用的classpath里是否有这些类

下面的方法实现中:

  • 第一个if结构先判断是否是 Reactive 环境,发现有 WebFlux 的类但没有 WebMvc 的类,则判定为 Reactive 环境(全NIO)

  • 之后的for循环要检查是否有跟 Servlet 相关的类,如果有任何一个类没有,则判定为非Web环境

  • 如果for循环走完了,证明所有类均在当前 classpath 下,则为 Servlet(WebMvc) 环境

2.3.2 setInitializers:设置初始化器

setInitializers方法会将一组类型为 ApplicationContextInitializer 的初始化器放入 SpringApplication 中。

而这组 **ApplicationContextInitializer**,是在构造方法中,通过 getSpringFactoriesInstances 得到的。

在阅读这部分源码之前,先来了解一下 ApplicationContextInitializer 是什么。

2.3.2.0 【重要】ApplicationContextInitializer

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext>

文档注释原文翻译:

Callback interface for initializing a Spring ConfigurableApplicationContext prior to being refreshed. Typically used within web applications that require some programmatic initialization of the application context. For example, registering property sources or activating profiles against the context's environment. See ContextLoader and FrameworkServlet support for declaring a "contextInitializerClasses" context-param and init-param, respectively. ApplicationContextInitializer processors are encouraged to detect whether Spring's Ordered interface has been implemented or if the @Order annotation is present and to sort instances accordingly if so prior to invocation.

用于在刷新容器之前初始化Spring ConfigurableApplicationContext 的回调接口。

通常在需要对应用程序上下文进行某些编程初始化的Web应用程序中使用。例如,根据上下文环境注册属性源或激活配置文件。请参阅 ContextLoaderFrameworkServlet 支持,分别声明 "contextInitializerClasses" 的 context-param 和 init-param。

鼓励 ApplicationContextInitializer 处理器检测是否已实现Spring的 Ordered 接口,或者是否标注了 @Order 注解,并在调用之前相应地对实例进行排序。

第一句注释已经解释的很明白了,它是在IOC容器之前的回调。它的使用方式有三种:

2.3.2.0.1 运行SpringApplication之前手动添加

先编写一个Demo:

public class ApplicationContextInitializerDemo implements ApplicationContextInitializer {
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ApplicationContextInitializerDemo#initialize run...");
    }
    
}

之后在主启动类上手动添加:

@SpringBootApplication
public class DemoApplication {
    
    public static void main(String[] args) {
        // SpringApplication.run(DemoApplication.class, args);
        SpringApplication springApplication = new SpringApplication(DemoApplication.class);
        springApplication.addInitializers(new ApplicationContextInitializerDemo());
        springApplication.run(args);
    }
    
}

运行主启动类,控制台打印(看Banner下面的第一行):

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
    '  |____| .__|_| |_|_| |_\__, | / / / /
    =========|_|==============|___/=/_/_/_/
    :: Spring Boot ::        (v2.1.9.RELEASE)

ApplicationContextInitializerDemo#initialize run...
    1970-01-01 00:00:00.000  INFO 7876 --- [  restartedMain] com.example.demo.DemoApplication         : Starting DemoApplication on DESKTOP with PID 7876 (D:\IDEA\spring-boot-demo\target\classes started by LinkedBear in D:\IDEA\spring-boot-demo)
................

2.3.2.0.2 application.properties中配置

application.properties 中配置如下内容:

context.initializer.classes=com.example.demo.ApplicationContextInitializerDemo

2.3.2.0.3 spring.factories中配置

在工程的 resources 目录下新建 “META-INF” 目录,并在下面创建一个 spring.factories 文件。在文件内声明:

org.springframework.context.ApplicationContextInitializer=com.example.demo.ApplicationContextInitializerDemo

三种方式效果都是一样的。

回到上面的方法中:

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList<>();
    this.initializers.addAll(initializers);
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates (使用名称并确保唯一,以防止重复)
    // 3.2.1 SpringFactoriesLoader.loadFactoryNames:加载指定类型的所有已配置组件的全限定类名
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 3.2.2 createSpringFactoriesInstances:创建这些组件的实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

方法中有两步是比较重要的,下面分别来看:

2.3.2.1 SpringFactoriesLoader.loadFactoryNames

这个方法我们已经在之前详细解析过,这里不重复解释,不过我们可以看一眼 spring-bootspring-boot-autoconfigure 包下的 spring.factories 里面对于 ApplicationContextInitializer 的配置:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

它一共配置了6个 ApplicationContextInitializer,对这些Initializer作简单介绍:

  • ConfigurationWarningsApplicationContextInitializer:报告IOC容器的一些常见的错误配置

  • ContextIdApplicationContextInitializer:设置Spring应用上下文的ID

  • DelegatingApplicationContextInitializer:加载 application.propertiescontext.initializer.classes 配置的类

  • ServerPortInfoApplicationContextInitializer:将内置servlet容器实际使用的监听端口写入到 Environment 环境属性中

  • SharedMetadataReaderFactoryContextInitializer:创建一个 SpringBoot 和 ConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory 对象

  • ConditionEvaluationReportLoggingListener:将 ConditionEvaluationReport 写入日志

2.3.2.2 createSpringFactoriesInstances:反射创建这些组件的实例

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
                                                   ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            // 反射创建这些对象
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

2.3.3 setListeners:设置监听器

与上面一样,先了解下 ApplicationListener

2.3.3.0 【重要】ApplicationListener

import java.util.EventListener;

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener

它的文档注释原文翻译:

Interface to be implemented by application event listeners. Based on the standard java.util.EventListener interface for the Observer design pattern. As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.

由应用程序事件监听器实现的接口。基于观察者模式的标准 java.util.EventListener 接口。

从Spring 3.0开始,ApplicationListener 可以一般性地声明监听的事件类型。向IOC容器注册后,将相应地过滤事件,并且仅针对匹配事件对象调用监听器。

文档注释也写的很明白,它就是监听器,用于监听IOC容器中发布的各种事件。至于事件是干嘛的,要到后续看IOC容器的刷新过程时才能看到。

2.3.3.1 加载Listener

// 加载所有类型为ApplicationListener的已配置的组件的全限定类名
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

套路与 setInitializers 一致,同样的我们来看看它加载了的 Listener:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
  • ClearCachesApplicationListener:应用上下文加载完成后对缓存做清除工作

  • ParentContextCloserApplicationListener:监听双亲应用上下文的关闭事件并往自己的子应用上下文中传播

  • FileEncodingApplicationListener:检测系统文件编码与应用环境编码是否一致,如果系统文件编码和应用环境的编码不同则终止应用启动

  • AnsiOutputApplicationListener:根据 spring.output.ansi.enabled 参数配置 AnsiOutput

  • ConfigFileApplicationListener:从常见的那些约定的位置读取配置文件

  • DelegatingApplicationListener:监听到事件后转发给 application.properties 中配置的 context.listener.classes 的监听器

  • ClasspathLoggingApplicationListener:对环境就绪事件 ApplicationEnvironmentPreparedEvent 和应用失败事件 ApplicationFailedEvent 做出响应

  • LoggingApplicationListener:配置 LoggingSystem。使用 logging.config 环境变量指定的配置或者缺省配置

  • LiquibaseServiceLocatorApplicationListener:使用一个可以和 SpringBoot 可执行jar包配合工作的版本替换 LiquibaseServiceLocator

  • BackgroundPreinitializer:使用一个后台线程尽早触发一些耗时的初始化任务

2.3.4 deduceMainApplicationClass:确定主配置类

private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            // 从本方法开始往上爬,哪一层调用栈上有main方法,方法对应的类就是主配置类
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

源码很简单,从 deduceMainApplicationClass 方法开始往上爬,哪一层调用栈上有main方法,方法对应的类就是主配置类,就返回这个类。

实际上通过Debug可以发现,发现这部分的 stackTrace 就是调用栈:

img

那自然最下面调用的方法是main方法,由此可确定主配置类。

2.3.5 【补充】与SpringBoot1.x的区别

private final Set<Object> sources = new LinkedHashSet<Object>();

private void initialize(Object[] sources) {
    // sources为null时没有终止应用继续启动
    // sources为SpringBoot1.x中使用的成员,SpringBoot2.x保留了它,但启动过程中不再使用
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    // deduceWebEnvironment方法在SpringApplication中,没有抽取成一个工具方法
    // 且SpringBoot1.x使用Spring4.x版本,没有WebFlux模块,故这里面只判断是否为WebMvc环境
    this.webEnvironment = deduceWebEnvironment();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

3. 准备运行时环境

/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);

new SpringApplication() 完成后,下面开始执行run方法:

在开始走 run 方法之前,咱先大体对这部分有一个宏观的认识,便于咱接下来理解。

img

3.1 run():启动SpringApplication

源码很长,这里我们拆成几篇来看,本篇先来看前置准备和运行时环境的准备。

public ConfigurableApplicationContext run(String... args) {
    // 4.1 创建StopWatch对象
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 4.2 创建空的IOC容器,和一组异常报告器
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 4.3 配置与awt相关的信息
    configureHeadlessProperty();
    // 4.4 获取SpringApplicationRunListeners,并调用starting方法(回调机制)
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 【回调】首次启动run方法时立即调用。可用于非常早期的初始化(准备运行时环境之前)。
    listeners.starting();
    try {
        // 将main方法的args参数封装到一个对象中
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 4.5 准备运行时环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        //.............
    }

3.1.1 new StopWatch():创建StopWatch对象

这个组件看上去貌似跟时间相关,看它的文档注释(最后一句):

This class is normally used to verify performance during proof-of-concepts and in development, rather than as part of production applications.

常用于在概念验证和开发过程中验证性能,而不是作为生产应用程序的一部分。

注释已经解释的很明确了:仅用于验证性能。也就是说,这个组件是用来监控启动时间的,不是很重要,我们不作深入研究。看一眼源码吧:

public void start() throws IllegalStateException {
    start("");
}

public void start(String taskName) throws IllegalStateException {
    if (this.currentTaskName != null) {
        throw new IllegalStateException("Can't start StopWatch: it's already running");
    }
    this.currentTaskName = taskName;
    // 记录启动时的当前系统时间
    this.startTimeMillis = System.currentTimeMillis();
}

3.1.2 创建空的IOC容器,和一组异常报告器

ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

这段源码本身非常简单,但 SpringBootExceptionReporter 是什么呢?

3.1.2.0 SpringBootExceptionReporter

public interface SpringBootExceptionReporter {
    boolean reportException(Throwable failure);
}

这个接口是SpringBoot2.0出现的,它是一种异常分析器。它的文档注释原文翻译:

Callback interface used to support custom reporting of SpringApplication startup errors. reporters are loaded via the SpringFactoriesLoader and must declare a public constructor with a single ConfigurableApplicationContext parameter.

用于支持 SpringApplication 启动错误报告的自定义报告的回调接口,它通过 SpringFactoriesLoader 加载,并且必须使用单个 ConfigurableApplicationContext 参数声明公共构造函数。

文档注释已经写明白了,它是启动错误报告的报告器,并且也是用 SpringFactoriesLoader 加载。通过使用IDEA的实现继承关系查看,发现它的实现类只有一个: FailureAnalyzers

3.1.2.1 与SpringBoot1.x的对比

ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;

SpringBoot1.x中声明的直接是 FailureAnalyzers,而且是一个。

3.1.3 configureHeadlessProperty:设置awt相关

private void configureHeadlessProperty() {
    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
                       System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

这段源码很诡异,它从 System 中取了一个配置,又给设置回去了。这样做的目的是什么呢?

这就要看jdk中 System 类的这两个方法了:

public static String getProperty(String key) {
    checkKey(key);
    SecurityManager sm = getSecurityManager();
    if (sm != null) {
        sm.checkPropertyAccess(key);
    }
    // 从Properties中取值
    return props.getProperty(key);
}

public static String getProperty(String key, String def) {
    checkKey(key);
    SecurityManager sm = getSecurityManager();
    if (sm != null) {
        sm.checkPropertyAccess(key);
    }
    // 从Properties中取值,如果取不到,返回默认值
    return props.getProperty(key, def);
}

public static String setProperty(String key, String value) {
    checkKey(key);
    SecurityManager sm = getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new PropertyPermission(key,
                                                  SecurityConstants.PROPERTY_WRITE_ACTION));
    }
    
    return (String) props.setProperty(key, value);
}

发现 System 类中有两个重载的 getProperty 方法,但只有一个 setProperty!仔细观察源码,发现重载的方法有一点微妙的区别。这里要提一下 Properties 的机制:

setProperty 方法中调用的是 Properties 的两参数 setProperty 方法,分别代表key和value,这自然不必多说。getProperty 方法的两个重载的方法唯一的区别是调用 Properties 的一参数和两参数方法,它的区别类似于Map中的getgetOrDefault。换句话说,getProperty 的两参数方法如果取不到指定的key,则会返回一个默认值;一个参数的方法调用时没有则返回null。

经过上述源码的设置后,这样无论如何都能取到这个key为 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 的value了。那这个 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 又是什么呢?

// 显示器缺失
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

由此可得,这段源码的真正作用是:设置应用在启动时,即使没有检测到显示器也允许其继续启动。(服务器嘛,没显示器照样得运行。。。)

3.1.4 getRunListeners:获取SpringApplicationRunListeners

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    // 又是调getSpringFactoriesInstances方法,取spring.factories中所有SpringApplicationRunListener
    return new SpringApplicationRunListeners(logger,
                                             getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

加载机制我们懂,那 SpringApplicationRunListeners 是什么呢?

3.1.4.0 【重要】SpringApplicationRunListeners

文档注释已在源码中直接标注,不再拆分到正文。

/**
* Listener for the SpringApplication code run method.
* SpringApplicationRunListeners are loaded via the SpringFactoriesLoader
* and should declare a public constructor that accepts a SpringApplication
* instance and a String[] of arguments. A new
* SpringApplicationRunListener instance will be created for each run.
*
* 监听SpringApplication运行方法。
* SpringApplication是SpringFactoriesLoader,应该声明一个接受SpringApplication实例和String[]参数的公共构造函数。
* 将为每次运行创建一个新的SpringApplicationRunListener的instance。
*/
public interface SpringApplicationRunListener {
    
    /**
    * Called immediately when the run method has first started. Can be used for very
    * early initialization.
    * 首次启动run方法时立即调用。可用于非常早期的初始化。
    */
    void starting();
    
    /**
    * Called once the environment has been prepared, but before the
    * ApplicationContext has been created.
    * 准备好环境(Environment构建完成),但在创建ApplicationContext之前调用。
    */
    void environmentPrepared(ConfigurableEnvironment environment);
    
    /**
    * Called once the ApplicationContext has been created and prepared, but
    * before sources have been loaded.
    * 在创建和构建ApplicationContext之后,但在加载之前调用。
    */
    void contextPrepared(ConfigurableApplicationContext context);
    
    /**
    * Called once the application context has been loaded but before it has been
    * refreshed.
    * ApplicationContext已加载但在刷新之前调用。
    */
    void contextLoaded(ConfigurableApplicationContext context);
    
    /**
    * The context has been refreshed and the application has started but
    * CommandLineRunners and ApplicationRunners have not been called.
    * @since 2.0.0
    * ApplicationContext已刷新,应用程序已启动,但尚未调用CommandLineRunners和ApplicationRunners。
    */
    void started(ConfigurableApplicationContext context);
    
    /**
    * Called immediately before the run method finishes, when the application context has
    * been refreshed and all CommandLineRunners and ApplicationRunners have been called.
    * @since 2.0.0
    * 在运行方法彻底完成之前立即调用,刷新ApplicationContext并调用所有CommandLineRunners和ApplicationRunner。
    */
    void running(ConfigurableApplicationContext context);
    
    /**
    * Called when a failure occurs when running the application.
    * @since 2.0.0
    * 在运行应用程序时失败时调用。
    */
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

值得注意的是,started、running、failed方法是 SpringBoot2.0 才加入的。

后续这个run方法中会常出现这些 SpringApplicationRunListeners 的身影,我会特别标注出来的,小伙伴们也多加留意。

通过Debug,发现默认情况下加载的listeners有一个,类型为 EventPublishingRunListener

img

回到 run 方法中:

//......
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(); //【回调】首次启动run方法时立即调用。可用于非常早期的初始化(准备运行时环境之前)。
try {
    // 将main方法的args参数封装到一个对象中
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        //......

prepareEnvironment 之前,run方法中调用了:listeners.starting() ,已经开始了事件回调。

接下来要执行的方法:prepareEnvironment

3.1.5 prepareEnvironment:准备运行时环境

暂且不看方法实现,先了解一下 Environment 是什么东西(之前说过 Environment 非常重要)。

【如果小伙伴不了解 Environment,请继续往下看;熟悉的小伙伴请直接跳过4.5.0节】

3.1.5.0 【重要】Environment与ConfigurableEnvironment

3.1.5.0.1 Environment

它的文档注释非常长:

Interface representing the environment in which the current application is running. Models two key aspects of the application environment: profiles and properties. Methods related to property access are exposed via the PropertyResolver superinterface. A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema or the @Profile annotation for syntax details. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default. Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them. Beans managed within an ApplicationContext may register to be EnvironmentAware or @Inject the Environment in order to query profile state or resolve properties directly. In most cases, however, application-level beans should not need to interact with the Environment directly but instead may have to have ${...} property values replaced by a property placeholder configurer such as PropertySourcesPlaceholderConfigurer, which itself is EnvironmentAware and as of Spring 3.1 is registered by default when using <context:property-placeholder/>. Configuration of the environment object must be done through the ConfigurableEnvironment interface, returned from all AbstractApplicationContext subclass getEnvironment() methods. See ConfigurableEnvironment Javadoc for usage examples demonstrating manipulation of property sources prior to application context refresh().

表示当前应用程序正在其中运行的环境的接口。它为应用环境制定了两个关键的方面:profileproperties。与属性访问有关的方法通过 PropertyResolver 这个父接口公开。

profile机制保证了仅在给定 profile 处于激活状态时,才向容器注册的Bean定义的命名逻辑组。无论是用XML定义还是通过注解定义,都可以将Bean分配给指定的 profile。有关语法的详细信息,请参见spring-beans 3.1规范文档@Profile 注解。Environment 的作用是决定当前哪些配置文件(如果有)处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。

Properties 在几乎所有应用程序中都起着重要作用,并且可能来源自多种途径:属性文件,JVM系统属性,系统环境变量,JNDI,ServletContext 参数,临时属性对象,Map等。EnvironmentProperties 的关系是为用户提供方便的服务接口,以配置属性源,并从中解析属性值。

ApplicationContext 中管理的Bean可以注册为 EnvironmentAware 或使用 @Inject 标注在 Environment 上,以便直接查询profile的状态或解析 Properties

但是,在大多数情况下,应用程序级Bean不必直接与 Environment 交互,而是通过将${...}属性值替换为属性占位符配置器进行属性注入(例如 PropertySourcesPlaceholderConfigurer),该属性本身是 EnvironmentAware,当配置了 <context:property-placeholder/> 时,默认情况下会使用Spring 3.1的规范注册。

必须通过从所有 AbstractApplicationContext 子类的 getEnvironment() 方法返回的 ConfigurableEnvironment 接口完成环境对象的配置。请参阅 ConfigurableEnvironment 的Javadoc以获取使用示例,这些示例演示在应用程序上下文 refresh() 方法被调用之前对属性源进行的操作。

简单概括一下:它是IOC容器的运行环境,它包括Profile和Properties两大部分,它可由一个到几个激活的Profile共同配置,它的配置可在应用级Bean中获取

可以这样理解:

img

3.1.5.0.2 ConfigurableEnvironment

它的文档注释更长,我们只摘选最核心的部分,举例部分不作阅读:

Configuration interface to be implemented by most if not all Environment types. Provides facilities for setting active and default profiles and manipulating underlying property sources. Allows clients to set and validate required properties, customize the conversion service and more through the ConfigurablePropertyResolver superinterface.

大多数(如果不是全部)Environment 类型的类都将实现的配置接口。提供用于设置 Profile 和默认配置文件以及操纵基础属性源的工具。允许客户端通过ConfigurablePropertyResolver 根接口设置和验证所需的属性、自定义转换服务以及其他功能。

从文档注释中发现这种机制与 ApplicationContextConfigurableApplicationContext 类似,都是一个只提供get,另一个扩展的提供set。具体源码小册不再贴出,小伙伴们可以借助IDE自行查看。

回到方法实现:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                   ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // 4.5.1 创建运行时环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 4.5.2 配置运行时环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 【回调】SpringApplicationRunListener的environmentPrepared方法(Environment构建完成,但在创建ApplicationContext之前)
    listeners.environmentPrepared(environment);
    // 4.5.3 环境与应用绑定
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                                                                                               deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

3.1.5.1 getOrCreateEnvironment:创建运行时环境

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    // 判断当前Web应用类型
    switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
    }
}

源码很简单,还是根据当前的应用运行环境类型,创建不同的 Environment 。默认 SpringBoot 环境下会创建 StandardServletEnvironment

3.1.5.2 configureEnvironment:配置运行时环境

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

前面的if结构是向 Environment 中添加一个 ConversionService。至于 ConversionService 是什么,把这个方法先大概看一遍再了解。

添加完 ConversionService 之后,要分别配置 PropertySource 和 Profiles,底层比较简单且一般情况不会执行,不再展开描述。

3.1.5.2.1 ConversionService是什么

文档注释原文翻译:

A service interface for type conversion. This is the entry point into the convert system. Call convert(Object, Class) to perform a thread-safe type conversion using this system.

用于类型转换的服务接口。这是转换系统的入口,调用 convert(Object, Class) 使用此系统执行线程安全的类型转换。

可以看出它是一个类型转换的根接口。利用IDEA查看它的实现类,发现有一个实现类叫 DefaultConversionService 。不出意外的话,它至少能把这个接口所要描述的方法能实现了。

翻看 DefaultConversionService 的源码,发现里面有好多的 addXXXConverters 的方法。而这里面不乏有一些我们看上去比较熟悉的也比较容易猜测的:

  • StringToNumberConverterFactory

  • StringToBooleanConverter

  • IntegerToEnumConverterFactory

  • ArrayToCollectionConverter

  • StringToArrayConverter

  • ......

果然它能做得类型转换还不少。实际上就是它在 SpringWebMvc 中做参数类型转换

3.1.5.3 bindToSpringApplication:环境与应用绑定

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    try {
        Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    }
    catch (Exception ex) {
        throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    }
}

这里面的核心源码就一句话,Binder 的 bind 方法:

public <T> BindResult<T> bind(String name, Bindable<T> target) {
    return bind(ConfigurationPropertyName.of(name), target, null);
}

public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
    Assert.notNull(name, "Name must not be null");
    Assert.notNull(target, "Target must not be null");
    handler = (handler != null) ? handler : BindHandler.DEFAULT;
    Context context = new Context();
    T bound = bind(name, target, handler, context, false);
    return BindResult.of(bound);
}

手头有IDE的小伙伴,当你再点开下面bind方法的时候,你的心情可能跟我一样是非常复杂的。随着调用的方法一层一层深入,可以发现这部分非常复杂。这里我们不作过多深入的探究,仅从方法的文档注释上来看它的解释:

Bind the specified target Bindable using this binder's property sources.

使用此绑定器的属性源,绑定指定的 可绑定的目标。

说白了,也就是把配置内容绑定到指定的属性配置类中(类似于 @ConfigurationProperties)。

3.1.6 configureIgnoreBeanInfo:设置系统参数

public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
    if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
        Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
        System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
    }
}

它提到了一个配置:spring.beaninfo.ignore,我们没见过他也没配置过,那就只好借助文档注释。在文档注释中有一句话比较关键:

"spring.beaninfo.ignore", with a value of "true" skipping the search for BeanInfo classes (typically for scenarios where no such classes are being defined for beans in the application in the first place).

"spring.beaninfo.ignore" 的值为“true”,则跳过对BeanInfo类的搜索(通常用于未定义此类的情况)首先是应用中的bean)。

由此可知,它是控制是否跳过 BeanInfo 类的搜索,并且由源码可知默认值是true,不作过多研究。

3.1.7 printBanner:打印Banner

在阅读这部分源码之前,先看看 Banner 到底是什么。

【如果小伙伴仅仅是知道 Banner,不妨继续往下看看。对 Banner 很熟悉的小伙伴可以跳过4.7.0节】

3.1.7.0 Banner和它的实现类

public interface Banner {
    
    void printBanner(Environment environment, Class<?> sourceClass, PrintStream out);
    
    enum Mode {
        OFF,
        CONSOLE,
        LOG
        }
}

它是一个接口,并且内置了一个枚举类型,代表 Banner 输出的模式(关闭、控制台打印、日志输出)。

借助IDEA,发现它有几个实现类:

img

第一眼应该关注的就是这个 SpringBootBanner ,我看它最熟悉。翻看它的源码:

class SpringBootBanner implements Banner {
    
    private static final String[] BANNER = { "", "  .   ____          _            __ _ _",
                                            " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
                                            " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )", "  '  |____| .__|_| |_|_| |_\\__, | / / / /",
                                            " =========|_|==============|___/=/_/_/_/" };
    
    private static final String SPRING_BOOT = " :: Spring Boot :: ";
    
    private static final int STRAP_LINE_SIZE = 42;
    
    @Override
    public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
        // 先打印Banner内容
        for (String line : BANNER) {
            printStream.println(line);
        }
        // 打印SpringBoot的版本
        String version = SpringBootVersion.getVersion();
        version = (version != null) ? " (v" + version + ")" : "";
        StringBuilder padding = new StringBuilder();
        while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {
            padding.append(" ");
        }
        
        printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),
                                                AnsiStyle.FAINT, version));
        printStream.println();
    }
    
}

看到了上面一堆奇怪但又有些熟悉的东西,常量名是 BANNER,它就是在默认情况下打印在控制台的 Banner

它重写的 printBanner 方法,就是拿输出对象,把定义好的 Banner 和 SpringBoot 的版本号打印出去,逻辑比较简单。


回到 printBanner 中,看它的源码:

private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

private Banner printBanner(ConfigurableEnvironment environment) {
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    // Banner文件资源加载
    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
        : new DefaultResourceLoader(getClassLoader());
    // 使用BannerPrinter打印Banner
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

首先判断当前是否关闭了 Banner 输出,而默认值是打在控制台上。

之后要获取 ResourceLoader,它的作用大概可以猜测到是加载资源的。

3.1.7.1 ResourceLoader

public interface ResourceLoader {
    
    /** Pseudo URL prefix for loading from the class path: "classpath:". */
    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
    
    Resource getResource(String location);
    
    @Nullable
    ClassLoader getClassLoader();
    
}

它的文档注释原文翻译:

Strategy interface for loading resources (e.. class path or file system resources).

用于加载资源(例如类路径或文件系统资源)的策略接口。

从接口定义和文档注释,已经基本证实了我们的猜测是正确的。上面方法默认创建的是 DefaultResourceLoader,看它的 getResource 方法:

public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    
    for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }
    
    // 处理前缀
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    }
    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    }
    else {
        // try块中加载资源
        try {
            // Try to parse the location as a URL...
            URL url = new URL(location);
            return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
        }
        catch (MalformedURLException ex) {
            // No URL -> resolve as resource path.
            return getResourceByPath(location);
        }
    }
}

前面的实现都可以不看,注意最后的try块中,它借助URL类来加载资源,这种方式已经跟 classLoader 的方式差不太多了,至此石锤我们的猜测是正确的。


回到 printBanner 中:

// ...
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
    return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);

获取到 ResourceLoader 后,下面要创建一个 SpringApplicationBannerPrinter ,默认情况下最终调用到最后的return中。

3.1.7.2 SpringApplicationBannerPrinter#print

public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
    Banner banner = getBanner(environment);
    banner.printBanner(environment, sourceClass, out);
    return new PrintedBanner(banner, sourceClass);
}

首先它要获取 Banner,之后打印 Banner,最后把 Banner 封装成 PrintedBanner 返回。

3.1.7.2.1 getBanner:获取Banner

private static final Banner DEFAULT_BANNER = new SpringBootBanner();

private Banner getBanner(Environment environment) {
    Banners banners = new Banners();
    // 先加载图片Banner和文字Banner
    banners.addIfNotNull(getImageBanner(environment));
    banners.addIfNotNull(getTextBanner(environment));
    // 只要有一个,就返回
    if (banners.hasAtLeastOneBanner()) {
        return banners;
    }
    if (this.fallbackBanner != null) {
        return this.fallbackBanner;
    }
    // 都没有,返回默认的
    return DEFAULT_BANNER;
}

很明显它要先试着找有没有 图片Banner文字Banner ,如果都没有,则会取默认的 Banner,而这个 Banner 恰好就是一开始看到的,也是我们最熟悉的 Banner。

以获取 文字Banner 为例,看看它是怎么拿的:

3.1.7.2.2 getTextBanner

static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";

private Banner getTextBanner(Environment environment) {
    String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
    Resource resource = this.resourceLoader.getResource(location);
    if (resource.exists()) {
        return new ResourceBanner(resource);
    }
    return null;
}

首先它要看你有没有显式的在 application.properties 中配置 spring.banner.location 这个属性,如果有,就加载它,否则加载默认的位置,叫 banner.txt

由此可见 SpringBoot 的设计原则:约定大于配置

拿到 Banner 后,打印,返回,Banner 部分结束。

3.1.8 createApplicationContext:创建IOC容器

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
        + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
        + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            // 根据Web应用类型决定实例化哪个IOC容器
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

可以发现都是创建的基于Annotation的 ApplicationContext

(如果是非Web环境,创建的 ApplicationContext 与常规用 SpringFramework 时使用的注解驱动IOC容器一致)

注意 BeanFactory 在这里已经被创建了:

public GenericApplicationContext() {
    this.beanFactory = new DefaultListableBeanFactory();
}

之前分析过,默认导入 spring-boot-start-web 时,Servlet环境生效,故上面导入的类为:AnnotationConfigServletWebServerApplicationContext 。这个类将在后续的分析中大量出现。

到这里,咱对三种类型的运行时环境、IOC容器的类型归纳一下:

  • Servlet - StandardServletEnvironment - AnnotationConfigServletWebServerApplicationContext

  • Reactive - StandardReactiveWebEnvironment - AnnotationConfigReactiveWebServerApplicationContext

  • None - StandardEnvironment - AnnotationConfigApplicationContext

3.1.9 prepareContext:初始化IOC容器

先大体浏览这部分源码的内容,其中几个复杂的地方我们单独来看。

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 将创建好的应用环境设置到IOC容器中
    context.setEnvironment(environment);
    // 4.9.1 IOC容器的后置处理
    postProcessApplicationContext(context);
    // 4.9.2 执行Initializer
    applyInitializers(context);
    // 【回调】SpringApplicationRunListeners的contextPrepared方法(在创建和准备ApplicationContext之后,但在加载之前)
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    // 创建两个组件:在控制台打印Banner的,之前把main方法中参数封装成对象的组件
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    // 4.9.3 加载主启动类
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // 4.9.4 注册主启动类
    load(context, sources.toArray(new Object[0]));
    // 【回调】SpringApplicationRunListeners的contextLoaded方法(ApplicationContext已加载但在刷新之前)
    listeners.contextLoaded(context);
}

3.1.9.1 postProcessApplicationContext:IOC容器的后置处理

// 留意一下这个名,后面Debug的时候会看到
public static final String CONFIGURATION_BEAN_NAME_GENERATOR =
			"org.springframework.context.annotation.internalConfigurationBeanNameGenerator";

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    // 注册BeanName生成器
    if (this.beanNameGenerator != null) {
        context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                this.beanNameGenerator);
    }
    // 设置资源加载器和类加载器
    if (this.resourceLoader != null) {
        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
        }
        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
    // 设置类型转换器
    if (this.addConversionService) {
        context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
    }
}

它设置了几个组件:

  • 如果 beanNameGenerator 不为空,则把它注册到IOC容器中。 BeanNameGenerator 是Bean的name生成器,指定的 CONFIGURATION_BEAN_NAME_GENERATOR 在修改首字母大写后无法从IDEA索引到,暂且放置一边。

  • ResourceLoaderClassLoader,这些都在前面准备好了

  • ConversionService,用于类型转换的工具,前面也准备好了,并且还做了容器共享

3.1.9.2 applyInitializers:执行Initializer

protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                                                                        ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

这个方法会获取到所有 Initializer,调用initialize方法。而这些 Initializer,其实就是刚创建 SpringApplication 时准备的那些 ApplicationContextInitializer

通过Debug发现默认情况下确实是那6个 Initializer :

来到 prepareContext 的最后几行:

// Load the sources
// 4.9.3 加载主启动类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 4.9.4 注册主启动类
load(context, sources.toArray(new Object[0]));

3.1.9.3 getAllSources

private Set<Class<?>> primarySources;
private Set<String> sources = new LinkedHashSet<>();

public Set<Object> getAllSources() {
    Set<Object> allSources = new LinkedHashSet<>();
    if (!CollectionUtils.isEmpty(this.primarySources)) {
        allSources.addAll(this.primarySources);
    }
    if (!CollectionUtils.isEmpty(this.sources)) {
        allSources.addAll(this.sources);
    }
    return Collections.unmodifiableSet(allSources);
}

它要加载 primarySources 和 sources 。

在之前分析的时候, primarySources 已经被设置过了,就是主启动类。sources 不清楚也没见过,通过Debug发现它确实为空。

也就是说,getAllSources 实际上是把主启动类加载进来了。

加载进来之后,就要注册进去,来到load方法:

3.1.9.4 【复杂】load

protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
        logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    // 设置BeanName生成器,通过Debug发现此时它还没有被注册
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    // 设置资源加载器
    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }
    // 设置运行环境
    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }
    loader.load();
}

在调用 createBeanDefinitionLoader 方法之前,它先获取了 BeanDefinitionRegistry

3.1.9.4.1 getBeanDefinitionRegistry

private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
    if (context instanceof BeanDefinitionRegistry) {
        return (BeanDefinitionRegistry) context;
    }
    if (context instanceof AbstractApplicationContext) {
        return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
    }
    throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}

发现它在拿IOC容器进行类型判断和强转。

前面分析了,我们最终拿到的IOC容器是 AnnotationConfigServletWebServerApplicationContext,它的类继承结构:

public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
implements AnnotationConfigRegistry
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext
public class GenericWebApplicationContext extends GenericApplicationContext
implements ConfigurableWebApplicationContext, ThemeSource
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry

它继承自 GenericApplicationContext,而 GenericApplicationContext 就继承了 AbstractApplicationContext,实现了 BeanDefinitionRegistry 接口。

所以上面的源码实际上把IOC容器返回去了

3.1.9.4.2 createBeanDefinitionLoader

拿到IOC容器后,进入 createBeanDefinitionLoader 方法:

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
    return new BeanDefinitionLoader(registry, sources);
}

源码非常简单,直接new了一个 BeanDefinitionLoader 。那 BeanDefinitionLoader 的构造方法都干了什么呢?

BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
    Assert.notNull(registry, "Registry must not be null");
    Assert.notEmpty(sources, "Sources must not be empty");
    this.sources = sources;
    // 注册BeanDefinition解析器
    this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
    this.xmlReader = new XmlBeanDefinitionReader(registry);
    if (isGroovyPresent()) {
        this.groovyReader = new GroovyBeanDefinitionReader(registry);
    }
    this.scanner = new ClassPathBeanDefinitionScanner(registry);
    this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

这里面发现了几个关键的组件:AnnotatedBeanDefinitionReader(注解驱动的Bean定义解析器)、XmlBeanDefinitionReader(Xml定义的Bean定义解析器)、ClassPathBeanDefinitionScanner(类路径下的Bean定义扫描器),还有一个我们不用的 GroovyBeanDefinitionReader(它需要经过isGroovyPresent方法,而这个方法需要判断classpath下是否有 groovy.lang.MetaClass 类)。

BeanDefinitionLoader 的文档注释原文翻译:

Loads bean definitions from underlying sources, including XML and JavaConfig. Acts as a simple facade over AnnotatedBeanDefinitionReader, XmlBeanDefinitionReader and ClassPathBeanDefinitionScanner.

从基础源(包括XML和JavaConfig)加载bean定义。充当 AnnotatedBeanDefinitionReaderXmlBeanDefinitionReaderClassPathBeanDefinitionScanner 的简单外观(整合,外观模式)。

正好呼应了上面的组件,而且它使用了外观模式,将这几个组件整合了起来。

3.1.9.4.3 load

创建好解析器后,在上面的源码中,它又往loader中设置了 beanNameGenerator、resourceLoader、environment,最后调用了它的load方法。

public int load() {
    int count = 0;
    for (Object source : this.sources) {
        count += load(source);
    }
    return count;
}

它拿到所有的 sources(其实就主启动类一个),继续调用重载的load方法:

private int load(Object source) {
    Assert.notNull(source, "Source must not be null");
    // 根据传入source的类型,决定如何解析
    if (source instanceof Class<?>) {
        return load((Class<?>) source);
    }
    if (source instanceof Resource) {
        return load((Resource) source);
    }
    if (source instanceof Package) {
        return load((Package) source);
    }
    if (source instanceof CharSequence) {
        return load((CharSequence) source);
    }
    throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

它会根据传入的 source 的类型,来决定用哪种方式加载。主启动类属于 Class 类型,于是继续调用重载的方法:

private int load(Class<?> source) {
    if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
        // Any GroovyLoaders added in beans{} DSL can contribute beans here
        GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
        load(loader);
    }
    // 如果它是一个Component,则用注解解析器来解析它
    if (isComponent(source)) {
        this.annotatedReader.register(source);
        return 1;
    }
    return 0;
}

上面的 Groovy 相关的我们不关心,下面它要检测是否为一个 Component

回想主启动类,它被 @SpringBootApplication 注解标注,而 @SpringBootApplication 组合了一个 @SpringBootConfiguration,它又组合了一个 @Configuration 注解,@Configuration 的底层就是一个 @Component

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                                 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication
    
    @Configuration
    public @interface SpringBootConfiguration
    
    @Component
public @interface Configuration

所以主启动类是一个 Component,进入 annotatedReaderregister 方法中。

3.1.9.4.4 annotatedReader.register

public void registerBean(Class<?> beanClass) {
    doRegisterBean(beanClass, null, null, null);
}

规律总结】SpringFramework 和 SpringBoot中 有很多类似于 xxx 方法和 doXXX 方法。一般情况下,xxx方法负责引导到 doXXX 方法,doXXX 方法负责真正的逻辑和工作。

进入 doRegisterBean 中:

3.1.9.4.5 doRegisterBean

(源码较长,不太复杂的部分直接在源码上标注单行注释了)

<T> void doRegisterBean(Class<T> beanClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
                        @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
    
    // 包装为BeanDefinition
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
        return;
    }
    
    abd.setInstanceSupplier(instanceSupplier);
    // 解析Scope信息,决定作用域
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
    // 生成Bean的名称
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    
    // 解析BeanDefinition的注解
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    if (qualifiers != null) {
        for (Class<? extends Annotation> qualifier : qualifiers) {
            if (Primary.class == qualifier) {
                abd.setPrimary(true);
            }
            else if (Lazy.class == qualifier) {
                abd.setLazyInit(true);
            }
            else {
                abd.addQualifier(new AutowireCandidateQualifier(qualifier));
            }
        }
    }
    // 使用定制器修改这个BeanDefinition
    for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
        customizer.customize(abd);
    }
    
    // 使用BeanDefinitionHolder,将BeanDefinition注册到IOC容器中
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

其中 AnnotationConfigUtils.processCommonDefinitionAnnotations 的实现:

public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {
    processCommonDefinitionAnnotations(abd, abd.getMetadata());
}

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
    // 解析@Lazy
    AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
    if (lazy != null) {
        abd.setLazyInit(lazy.getBoolean("value"));
    }
    else if (abd.getMetadata() != metadata) {
        lazy = attributesFor(abd.getMetadata(), Lazy.class);
        if (lazy != null) {
            abd.setLazyInit(lazy.getBoolean("value"));
        }
    }
    
    // 解析@Primary
    if (metadata.isAnnotated(Primary.class.getName())) {
        abd.setPrimary(true);
    }
    // 解析@DependsOn
    AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
    if (dependsOn != null) {
        abd.setDependsOn(dependsOn.getStringArray("value"));
    }
    
    // 解析@Role
    AnnotationAttributes role = attributesFor(metadata, Role.class);
    if (role != null) {
        abd.setRole(role.getNumber("value").intValue());
    }
    // 解析@Description
    AnnotationAttributes description = attributesFor(metadata, Description.class);
    if (description != null) {
        abd.setDescription(description.getString("value"));
    }
}

原来这部分是在解析一些咱之前学习 SpringFramework 时候接触的注解啊!

最终会将这个 BeanDefinition 注册到IOC容器中,调用 BeanDefinitionReaderUtilsregisterBeanDefinition 方法。

3.1.9.4.6 BeanDefinitionReaderUtils.registerBeanDefinition

public static void registerBeanDefinition(
    BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
    throws BeanDefinitionStoreException {
    
    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

第7行,看到了 registry.registerBeanDefinition,有没有想起来之前在之前介绍手动装配时的 ImportBeanDefinitionRegistrar?(第5篇 自动装配原理中,4.1.4小节)

在这里它就是这么把Bean的定义信息注册进IOC容器的。其中,Bean的名称和别名在这个方法也被分开处理。


看到这里,小伙伴们可能会疑惑,这个 BeanDefinition 是个什么东西,它在IOC容器中起到了什么作用呢?

3.1.9.5 【重要】BeanDefinition

它的文档注释原文翻译:

A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations. This is just a minimal interface: The main intention is to allow a BeanFactoryPostProcessor such as PropertyPlaceholderConfigurer to introspect and modify property values and other bean metadata.

BeanDefinition 描述了一个bean实例,该实例具有属性值,构造函数参数值以及具体实现所提供的更多信息。

这只是一个最小的接口:主要目的是允许 BeanFactoryPostProcessor (例如 PropertyPlaceholderConfigurer )内省和修改属性值和其他bean元数据。

从文档注释中可以看出,它是描述Bean的实例的一个定义信息,但它不是真正的Bean。这个接口还定义了很多方法,不一一列举,全部的方法定义小伙伴们可以自行从IDE中了解。

  • String getBeanClassName();

  • String getScope();

  • String[] getDependsOn();

  • String getInitMethodName();

  • boolean isSingleton();

  • .....

SpringFramework 设计的这种机制会在后续的Bean加载和创建时起到非常关键的作用,小伙伴们一定要留意。

下面提到了一个 BeanPostProcessor ,这个概念我们到下一篇再了解。

最后更新于