SpringBoot之ApplicationContextInitializer(系统初始化器)的理解和使用(二)

目录

 

一、 ApplicationContextInitializer 介绍

二、三种实现方式

2.1、mian函数中添加

2.2、配置文件中配置

2.3、SpringBoot的SPI扩展---META-INF/spring.factories中配置

三、排序问题

 四、通过源码分析ApplicationContextInitializer何时被调用


一、 ApplicationContextInitializer 介绍

ApplicationContextInitializer用于在spring容器刷新之前初始化Spring ConfigurableApplicationContext的回调接口。(剪短说就是在容器刷新之前调用该类的 initialize 方法。并将 ConfigurableApplicationContext 类的实例传递给该方法)

  • 通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等。
  • 可排序的(实现Ordered接口,或者添加@Order注解)

  看完这段解释,为了讲解方便,我们先看自定义 ApplicationContextInitializer 的三种方式。再通过SpringBoot的源码,分析生效的时间以及实现的功能等。

二、三种实现方式

  首先新建一个类 MyApplicationContextInitializer 并实现 ApplicationContextInitializer 接口。

@Slf4j
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        log.info("-----执行-----------MyApplicationContextInitializer initialize-----");
    }
}

2.1、mian函数中添加

优雅的写一个SpringBoot的main方法

@SpringBootApplication
public class SpringBootSourceApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(SpringBootSourceApplication.class);
        application.addInitializers(new MyApplicationContextInitializer());
        application.run(args);
    }

}

2.2、配置文件中配置

在application.properties配置

context.initializer.classes=com.alen.springbootsource.initializer.MyApplicationContextInitializer

对于这种方式是通过DelegatingApplicationContextInitializer这个初始化类中的initialize方法获取到application.properties中context.initializer.classes对应的类并执行对应的initialize方法。只需要将实现了ApplicationContextInitializer的类添加到application.properties即可。

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


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;
	}

是从配置文件中获取到对应的初始化类信息,然后执行初始化方法。

 

2.3、SpringBoot的SPI扩展---META-INF/spring.factories中配置

org.springframework.context.ApplicationContextInitializer=com.alen.springbootsource.initializer.MyApplicationContextInitializer

这个加载过程是在SpringApplication中的getSpringFactoriesInstances()方法中直接加载并实例后执行对应的initialize方法。代码如下:

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;
	}

三、排序问题

  如图所示改造一下mian方法。打一个断点,debug查看排序情况。

给 MyApplicationContextInitializer 加上Order注解:我们指定其拥有最高的排序级别。(越高越早执行)

@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        log.info("-----执行-----------MyApplicationContextInitializer initialize-----");
    }
}

下面我们通过debug分别验证二章节中提到的三种方法排序是否都是可以的。

首先验证2.1章节中采用的main函数中添加:debug,断点处查看 application.getInitializers() 这行代码的结果可见,排序生效了。

  

  然后再分别验证2.2和2.3章节中的方法。排序都是可以实现的。

  然而当采用2.3中的SPI扩展的方式,排序指定 @Order(Ordered.LOWEST_PRECEDENCE) 排序并没有生效。当然采用实现Ordered接口的方式,排序验证结果都是一样的。

 四、通过源码分析ApplicationContextInitializer何时被调用

  debug差看上文中自定的 MyApplicationContextInitializer 的调用栈。

  

  可见 ApplicationContextInitializer 在容器刷新前的准备阶段被调用。 refreshContext(context); 在SpringBoot的启动函数中, ApplicationContextInitializer 

public ConfigurableApplicationContext run(String... args) {
        //记录程序运行时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // ConfigurableApplicationContext Spring 的上下文
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        //从META-INF/spring.factories中获取监听器
        //1、获取并启动监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //2、构造容器环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            //处理需要忽略的Bean
            configureIgnoreBeanInfo(environment);
            //打印banner
            Banner printedBanner = printBanner(environment);
            ///3、初始化容器
            context = createApplicationContext();
            //实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[]{ConfigurableApplicationContext.class}, context);
            //4、刷新容器前的准备阶段
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //5、刷新容器
            refreshContext(context);
            //刷新容器后的扩展接口
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        } catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

然后看在 refreshContext(context); 具体是怎么被调用的。

private void prepareContext(ConfigurableApplicationContext context,
                            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    ...
}

   然后在 applyInitializers 中遍历调用每一个被加载的 ApplicationContextInitializer 的  initialize(context);  方法,并将 ConfigurableApplicationContext 的实例传递给 initialize 方法。

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);
    }
}

 OK,到这里通过源码说明了 ApplicationContextInitializer 是何时及如何被调用的。

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值