[Spring Boot] 5. Spring Boot中的ApplicationContext - 执行ApplicationContextInitializer初始化器

Posted dm_vincent

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Spring Boot] 5. Spring Boot中的ApplicationContext - 执行ApplicationContextInitializer初始化器相关的知识,希望对你有一定的参考价值。

前面已经对Spring Boot启动过程进行过源码分析,对于代表容器上下文的关键字段ApplicationContext只是一笔带过。实际上,它的生命周期才应该是重点关注的。

Spring Boot使用的ApplicationContext

分两种场景,常规应用和Web应用使用的上下文类型不一样:

  • 常规应用:org.springframework.context.annotation.AnnotationConfigApplicationContext
  • Web应用:org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext

从类型层次来看的话:

AnnotationConfigApplicationContext(由Spring Context定义)

AnnotationConfigEmbeddedWebApplicationContext(由Spring Boot定义)


从类图中可以发现,它们都有共同的父类GenericApplicationContext,在这地方出现了分支。

再谈ApplicationContextInitializer

在创建好了ApplicationContext之后,会进行prepare操作:

private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) 
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) 
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    

    // Add boot specific singleton beans
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) 
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    

    // Load the sources
    Set<Object> sources = getSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[sources.size()]));
    listeners.contextLoaded(context);

这里面比较重要的就是调用注册的ApplicationContextInitializer实现。

/**
 * Callback interface for initializing a Spring @link ConfigurableApplicationContext
 * prior to being @linkplain ConfigurableApplicationContext#refresh() refreshed.
 *
 * <p>Typically used within web applications that require some programmatic initialization
 * of the application context. For example, registering property sources or activating
 * profiles against the @linkplain ConfigurableApplicationContext#getEnvironment()
 * context's environment. See @code ContextLoader and @code FrameworkServlet support
 * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
 *
 * <p>@code ApplicationContextInitializer processors are encouraged to detect
 * whether Spring's @link org.springframework.core.Ordered Ordered interface has been
 * implemented or if the @@link org.springframework.core.annotation.Order Order
 * annotation is present and to sort instances accordingly if so prior to invocation.
 *
 * @author Chris Beams
 * @since 3.1
 * @see org.springframework.web.context.ContextLoader#customizeContext
 * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
 * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
 * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> 

    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    void initialize(C applicationContext);

Javadoc比较长,提炼一下几个关键点:

  • 本质上是一个回调接口,用于在ConfigurableApplicationContext执行refresh操作之前对它进行一些初始化操作
  • 然后举例说明使用场景,比如Web应用中需要注册属性或者激活Profiles
  • 它支持Spring中的Ordered接口以及@Order注解来对多个ApplicationContextInitializer实例进行排序,按照排序后的顺序依次执行回调(这一点后面代码中会看到)

Spring Boot中定义的6个ApplicationContextInitializer实现类

DelegatingApplicationContextInitializer

顾名思义,这个初始化器实际上将初始化的工作委托给context.initializer.classes环境变量指定的初始化器(通过类名):

private static final String PROPERTY_NAME = "context.initializer.classes";

@Override
public void initialize(ConfigurableApplicationContext context) 
    ConfigurableEnvironment environment = context.getEnvironment();
    List<Class<?>> initializerClasses = getInitializerClasses(environment);
    if (!initializerClasses.isEmpty()) 
        applyInitializerClasses(context, initializerClasses);
    


private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) 
    String classNames = env.getProperty(PROPERTY_NAME);
    List<Class<?>> classes = new ArrayList<Class<?>>();
    if (StringUtils.hasLength(classNames)) 
        for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) 
            classes.add(getInitializerClass(className));
        
    
    return classes;

这个初始化器的优先级是Spring Boot定义的6个初始化器中优先级别最高的,因此会被第一个执行。

ContextIdApplicationContextInitializer

它的作用是给ApplicationContext设置一个ID,这个ID的生成规则是尝试读取以下几个属性:

  • spring.application.name
  • vcap.application.name
  • spring.config.name

如果不存在则使用application作为值。除此之外,还会在上面得到的结果后附加一个port或者index,同样也是读取属性:

  • vcap.application.instance_index
  • spring.application.index
  • server.port
  • PORT

所以一个可能的Context ID就是application:10001。

ConfigurationWarningsApplicationContextInitializer

这个实现的作用是报告出常见的配置错误。

它的实现方式:

@Override
public void initialize(ConfigurableApplicationContext context) 
    context.addBeanFactoryPostProcessor(
            new ConfigurationWarningsPostProcessor(getChecks()));

通过向context上下文对象中添加一个BeanFactoryPostProcessor,然后在refresh ApplicationContext的时候BeanFactoryPostProcessor会被调用到:

ServerPortInfoApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext applicationContext) 
    applicationContext.addApplicationListener(
            new ApplicationListener<EmbeddedServletContainerInitializedEvent>() 

                @Override
                public void onApplicationEvent(
                        EmbeddedServletContainerInitializedEvent event) 
                    ServerPortInfoApplicationContextInitializer.this
                            .onApplicationEvent(event);
                

            );

它的作用是监听EmbeddedServletContainerInitializedEvent类型的事件。然后将内嵌的Web服务器使用的端口给设置到ApplicationContext中。

同样地,是在ApplicationContext的refresh阶段,会触发上面的Listener:

SharedMetadataReaderFactoryContextInitializer

它会创建一个用于在ConfigurationClassPostProcessor和Spring Boot间共享的CachingMetadataReaderFactory。

@Override
public void initialize(ConfigurableApplicationContext applicationContext) 
    applicationContext.addBeanFactoryPostProcessor(
            new CachingMetadataReaderFactoryPostProcessor());

同样也是通过增加BeanFactoryPostProcessor来实现。

AutoConfigurationReportLoggingInitializer

功能是将ConditionEvaluationReport写入到log,一般的日志的级别是DEBUG,出问题的话使用INFO级别。通过增加ApplicationListener的方式实现。

@Override
public void initialize(ConfigurableApplicationContext applicationContext) 
    this.applicationContext = applicationContext;
    applicationContext.addApplicationListener(new AutoConfigurationReportListener());
    if (applicationContext instanceof GenericApplicationContext) 
        // Get the report early in case the context fails to load
        this.report = ConditionEvaluationReport
                .get(this.applicationContext.getBeanFactory());
    


// 响应事件的逻辑
// 只处理:ContextRefreshedEvent以及ApplicationFailedEvent两种类型的事件
protected void onApplicationEvent(ApplicationEvent event) 
    ConfigurableApplicationContext initializerApplicationContext = AutoConfigurationReportLoggingInitializer.this.applicationContext;
    if (event instanceof ContextRefreshedEvent) 
        if (((ApplicationContextEvent) event)
                .getApplicationContext() == initializerApplicationContext) 
            logAutoConfigurationReport();
        
    
    else if (event instanceof ApplicationFailedEvent) 
        if (((ApplicationFailedEvent) event)
                .getApplicationContext() == initializerApplicationContext) 
            logAutoConfigurationReport(true);
        
    

Spring Boot定义的ApplicationContextInitializer如何排定执行顺序

在SpringApplication中有这么一个方法:

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) 
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;

里面有一行代码:AnnotationAwareOrderComparator.sort(instances);

这个sort方法会利用AnnotationAwareOrderComparator进行排序。

具体的实现可以去看源代码,就不贴在这里了。AnnotationAwareOrderComparator扩展自OrderComparator,从而能够支持@Order注解以及javax.annotation.Priority注解。OrderComparator已经可以支持Ordered接口了。

后者的排序规则如下:

  1. 基于Order值升序排序,反应的就是优先级的从高到底
  2. 对于拥有相同Order值的对象,任意顺序
  3. 对于不能排序的对象(没有实现Ordered接口,没有@Order注解或者@Priority注解),会排在最后,因为这类对象的优先级是最低的

以上是关于[Spring Boot] 5. Spring Boot中的ApplicationContext - 执行ApplicationContextInitializer初始化器的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot打包部署修改jar文件名

Spring Boot 发送邮件 javax.mail

spring-boot1.5.6整合dubbo-spring-boot-start2.0.0

Spring Boot 2Spring Boot CLI

Spring Boot 2.5.2 现已推出,同步推出Spring Boot 2.4.8

Spring Boot 2.5.2 现已推出,同步推出Spring Boot 2.4.8