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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dm_vincent/article/details/77619780

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

Spring Boot使用的ApplicationContext

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

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

从类型层次来看的话:

AnnotationConfigApplicationContext(由Spring Context定义)

DingTalk20170816164550.png

AnnotationConfigEmbeddedWebApplicationContext(由Spring Boot定义)

DingTalk20170816164623.png


从类图中可以发现,它们都有共同的父类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会被调用到:

DingTalk20170816192020.png

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:

DingTalk20170816200934.png

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进行排序。

DingTalk20170816195509.png

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

后者的排序规则如下:

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

没有更多推荐了,返回首页