SpringBoot核心机制解读系列二、ApplicationContextInitializer

593 阅读4分钟

@[toc]

所有调试均使用SpringBoot 2.4.5版本。

这一章首先来理解下SpringBoot的spring.factories中的 org.springframework.context.ApplicationListener 配置。这个部分实现比较简单,权当热热身。

一、初始化加载器使用

ApplicationListener接口允许在Spring刷新IOC容器(大名鼎鼎的applicationContext.refresh方法)之前,进行自定义的初始化工作。我们可以创建一个SpringBoot工程来简单测试一下:(注意版本用2.4.5)
1、创建一个自定义启动器。这里只打印出SpringBoot的配置信息

package com.roy.applicationContextInitializer;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext context) {
        //获取系统属性
        System.out.println("==== systemProperties ===========");
        context.getEnvironment().getSystemProperties().forEach((key,value) -> {
            System.out.println(key +"==="+value);
        });
        System.out.println("==== systemProperties end ===========");
        //获取系统环境变量 
//        context.getEnvironment().getSystemEnvironment().forEach((key,value) -> {
//            System.out.println(key +"==="+value);
//        });
    }
}

2、在启动类中添加这个初始化器

@SpringBootApplication
public class P1Application implements CommandLineRunner {

    public static void main(String[] args) {
        final SpringApplication application = new SpringApplication(P1Application.class);
        //添加应用初始化器
        application.addInitializers(new MyApplicationContextInitializer());
        application.run(args);
    }

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void run(String... args) throws Exception {
        System.out.println(applicationContext.getId());
    }
}

3、直接执行这个main方法。然后就可以在控制台看到打印出来的系统属性。 --注意他的执行时间是非常靠前的。
在这里插入图片描述

这样就添加了一个自定义的初始化器。

可以自己尝试下在这一步可以添加一些什么东西。通常可以添加一些ApplicationListener(事件监听器),BeanFactoryPostProcessor(Bean后处理器),ProtocolResolver(协议处理器,可以用来加载自定义配置文件)。
另外,卖个小关子。示例中main方法中打印出了applicationContext的ID,结果是在application.properties配置文件中的spring.application.name属性。可是这个时候还没有进行统一配置加载,想一下是怎么来的?

4、这里了解了初始化器是什么东西后,就可以回到我们的正题,使用spring.factories扩展机制来配置ApplicationContextInitializer。 在classpath下添加META-INF/spring.factories文件,在文件中添加配置内容:

org.springframework.context.ApplicationContextInitializer=\
com.roy.applicationContextInitializer.MyApplicationContextInitializer

然后就可以把main方法中application.addInitializers这一行代码给注释掉,再启动main方法。 可以看到依然会打印出所有的系统属性。其实也就是说我们自定义的这个MyApplicationContextInitializer已经通过spring.factories文件配置的方式添加到了applicationContext当中。

二、核心机制解读

了解这个机制了之后再来看看源码中是怎么来处理spring.factories文件当中的ApplicationContextInitializer配置的。这个代码跟踪比较简单,就不再去画图什么的了。
1、加载spring.factories的配置: 在SpringApplication的构造方法中就会去添加这些初始化器。

 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = Collections.emptySet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
        this.applicationStartup = ApplicationStartup.DEFAULT;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); <=======重点在这里
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

跟踪这个getSpringFactoriesInstances方法,就可以回到我们第一节中介绍了的SpringBoot的属性加载机制了。
2、执行初始化器
这个初始化器的执行顺序是相当靠前的,在SpringApplication的run方法中就直接执行了。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext(); 
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // <==== 重点在这里
            this.refreshContext(context); //刷新Spring容器
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

在prepareContext方法中直接就执行了这些初始化器的initialize方法

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        this.applyInitializers(context); //<=====重点在这里
        listeners.contextPrepared(context);
        bootstrapContext.close(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }

        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

从源码中可以看到,Initializer的执行是在打印Banner之后,而Spring的refresh方法之前。这也跟我们之前观察到的日志打印是吻合的。
这个执行顺序也说明,在初始化阶段,Spring的IOC容器是还没有创建的,所以在这个阶段,不能引用IOC容器中的Bean。

三、SpringBoot中的扩展实现

现在再来看看SpringBoot依赖中给我们提供的默认实现:

#spring-boot
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
#spring-boot-autoconfigure
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

在这其中找几个简单的实现机制来看看,例如ContextIdApplicationContextInitializer。

public class ContextIdApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
    //加载顺序
	private int order = Ordered.LOWEST_PRECEDENCE - 10;
	public void setOrder(int order) {
		this.order = order;
	}
	@Override
	public int getOrder() {
		return this.order;
	}
	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {// <========入口方法
		ContextId contextId = getContextId(applicationContext);
		applicationContext.setId(contextId.getId());
		applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
	}
	private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
		ApplicationContext parent = applicationContext.getParent();
		if (parent != null && parent.containsBean(ContextId.class.getName())) {
			return parent.getBean(ContextId.class).createChildId();
		}
		return new ContextId(getApplicationId(applicationContext.getEnvironment()));
	}
	private String getApplicationId(ConfigurableEnvironment environment) { 
		String name = environment.getProperty("spring.application.name");// <========
		return StringUtils.hasText(name) ? name : "application";
	}

	/**
	 * The ID of a context.
	 */
	static class ContextId {
		private final AtomicLong children = new AtomicLong();
		private final String id;
		ContextId(String id) {
			this.id = id;
		}
		ContextId createChildId() {
			return new ContextId(this.id + "-" + this.children.incrementAndGet());
		}
		String getId() {
			return this.id;
		}
	}
}

可以看到,这个初始化器中做的事情并不复杂,就是解析SpringBoot的应用名。然后将携带应用名信息的ContextId对象注入到IOC容器当中。前面卖的那个小关子是不是就有了答案?

再看一个ConditionEvaluationReportLoggingListener。这个是用来处理SpringBoot的启动错误报告的。每次SpringBoot应用启动失败会打印出一大堆的堆栈错误信息,就会经过他处理的(当然,完整的处理机制不是这么简单)。

public class ConditionEvaluationReportLoggingListener
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	private final Log logger = LogFactory.getLog(getClass());

	private ConfigurableApplicationContext applicationContext;

	private ConditionEvaluationReport report;

	private final LogLevel logLevelForReport;

	public ConditionEvaluationReportLoggingListener() {
		this(LogLevel.DEBUG);
	}

	public ConditionEvaluationReportLoggingListener(LogLevel logLevelForReport) {
		Assert.isTrue(isInfoOrDebug(logLevelForReport), "LogLevel must be INFO or DEBUG");
		this.logLevelForReport = logLevelForReport;
	}

	private boolean isInfoOrDebug(LogLevel logLevelForReport) {
		return LogLevel.INFO.equals(logLevelForReport) || LogLevel.DEBUG.equals(logLevelForReport);
	}

	public LogLevel getLogLevelForReport() {
		return this.logLevelForReport;
	}

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) { //<=====加载入口
		this.applicationContext = applicationContext;
		applicationContext.addApplicationListener(new ConditionEvaluationReportListener());//注册事件监听器
		if (applicationContext instanceof GenericApplicationContext) {
			// Get the report early in case the context fails to load
			this.report = ConditionEvaluationReport.get(this.applicationContext.getBeanFactory());
		}
	}
    //监听事件的处理方法
	protected void onApplicationEvent(ApplicationEvent event) {
		ConfigurableApplicationContext initializerApplicationContext = this.applicationContext;
		if (event instanceof ContextRefreshedEvent) {
			if (((ApplicationContextEvent) event).getApplicationContext() == initializerApplicationContext) {
				logAutoConfigurationReport();
			}
		}
		else if (event instanceof ApplicationFailedEvent
				&& ((ApplicationFailedEvent) event).getApplicationContext() == initializerApplicationContext) {
			logAutoConfigurationReport(true);
		}
	}

	private void logAutoConfigurationReport() {
		logAutoConfigurationReport(!this.applicationContext.isActive());
	}

	public void logAutoConfigurationReport(boolean isCrashReport) {
		if (this.report == null) {
			if (this.applicationContext == null) {
				this.logger.info("Unable to provide the conditions report due to missing ApplicationContext");
				return;
			}
			this.report = ConditionEvaluationReport.get(this.applicationContext.getBeanFactory());
		}
		if (!this.report.getConditionAndOutcomesBySource().isEmpty()) {
			if (getLogLevelForReport().equals(LogLevel.INFO)) {
				if (this.logger.isInfoEnabled()) {
					this.logger.info(new ConditionEvaluationReportMessage(this.report));
				}
				else if (isCrashReport) {
					logMessage("info");
				}
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug(new ConditionEvaluationReportMessage(this.report));
				}
				else if (isCrashReport) {
					logMessage("debug");
				}
			}
		}
	}

	private void logMessage(String logLevel) {
		this.logger.info(String.format("%n%nError starting ApplicationContext. To display the "
				+ "conditions report re-run your application with '" + logLevel + "' enabled."));
	}

	private class ConditionEvaluationReportListener implements GenericApplicationListener {

		@Override
		public int getOrder() {
			return Ordered.LOWEST_PRECEDENCE;
		}

		@Override
		public boolean supportsEventType(ResolvableType resolvableType) {
			Class<?> type = resolvableType.getRawClass();
			if (type == null) {
				return false;
			}
			return ContextRefreshedEvent.class.isAssignableFrom(type)
					|| ApplicationFailedEvent.class.isAssignableFrom(type);
		}

		@Override
		public boolean supportsSourceType(Class<?> sourceType) {
			return true;
		}

		@Override
		public void onApplicationEvent(ApplicationEvent event) {
			ConditionEvaluationReportLoggingListener.this.onApplicationEvent(event);
		}

	}

}

从核心的initialize方法入手,就能看到他的处理逻辑主要是在初始化阶段注册了一个事件监听器,监听所有的ApplicationEvent。
关于事件监听器,其实是属于Spring的功能了。后面也会有一个章节来讲解SpringBoot针对事件监听器的处理。

其他的一些实现,就不再多贴了,有兴趣大家可以自己去看看。
通过这个章节,也希望能够理解SpringBoot的这种"无业务微核心+拔插式扩展"的设计思路。这种思路能够帮助我们更全面能深入的理解庞大的SpringBoot。另外,了解这些设计思路,也能作为应用开发的扩展点。