欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot初始化器

程序员文章站 2022-04-11 17:04:22
...
Spring 是一个扩展性很强的容器框架,为开发者提供了丰富的扩展入口,其中一个扩展点便是 ApplicationContextInitializer (应用上下文初始化器 )。
ApplicationContextInitializer 是 Spring 在执行 ConfigurableApplicationContext.refresh() 方法对应用上下文进行刷新之前调用的一个回调接口,用来完成对 Spring 应用上下文个性化的初始化工作,该接口定义在 org.springframework.context 包中,其内部仅包含一个 initialize() 方法,其定义代码如下。
package org.springframework.context;

/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

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

}

自定义初始化器

在 Springboot 中使用自定义初始化器大致可以分为以下两个步骤:
 
  • 自定义初始化器,一般是实现 ApplicationContextInitializer 接口。
  • 注册初始化器。
第一步:自定义初始化器,此处为了测试初始化器的执行顺序定义了如下3个初始化器。
 
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;

@Order(1)
public class MyFirstApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		System.out.println("Application Display Name:" + applicationContext.getDisplayName());
	}

}
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;

@Order(2)
public class MySecondApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		System.out.println("BeanDefinitionCount:" + applicationContext.getBeanFactory().getBeanDefinitionCount());
	}

}
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;

@Order(3)
public class MyThirdApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		System.out.println("BeanDefinitionNames" + applicationContext.getBeanDefinitionNames());
	}

}
第二步:注册初始化器,有以下三种方式。
方式一:在启动类中,使用 SpringApplication.addInitializers() 方法注册。
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.pengjunlee.initializer.MyFirstApplicationContextInitializer;

@SpringBootApplication
public class MyApplication {
	public static void main(String[] args) {
		SpringApplication springApplication = new SpringApplication(MySpringBootApplication.class);
		springApplication.addInitializers(new MyFirstApplicationContextInitializer());
		ConfigurableApplicationContext context = springApplication.run(args);
		context.close();
	}
}
方式二:在 Springboot 核心配置文件 application.properties 中增加 context.initializer.classes = [ 初始化器全类名 ] 进行注册。
 
context.initializer.classes=com.pengjunlee.initializer.MySecondApplicationContextInitializer
该 context.initializer.classes 配置项名称在 DelegatingApplicationContextInitializer 类的属性常量中指定。
方式三:通过在CLASSPATH/META-INF/spring.factories中添加 org.springframework.context.ApplicationContextInitializer 配置项进行注册.
 
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=com.pengjunlee.initializer.MyThirdApplicationContextInitializer
启动应用,程序打印结果如下:
 
BeanDefinitionCount:6
Application Display Name:org.springframework.bootaaa@qq.com3439f68d
BeanDefinitionNames[Ljava.lang.String;@55a561cf
注意:虽然可以使用 @Order 注解来控制多个初始化器的执行顺序(数值越小越先执行),但是,通过不同方式注册的初始化器的执行顺序也有所不同,若多个初始化器注册的方式不同会导致 @Order 注解顺序无效,从以上程序执行后的打印结果来看,三种方式注册的初始化器的执行顺序依次是:方式二 --> 方式一 --> 方式三。

Springboot定义的初始化器

Springboot定义的 ApplicationContextInitializer 接口的实现类有下面几个,如图所示。

SpringBoot初始化器

 

DelegatingApplicationContextInitializer

DelegatingApplicationContextInitializer 初始化器负责读取核心配置文件 context.initializer.classes 配置项指定的初始化器,并调用它们的 initialize() 方法来完成对应用上下文的初始化工作。
public class DelegatingApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

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

	private int order = 0;

	/**
	 * 对应用上下文进行初始化
	 */
	@Override
	public void initialize(ConfigurableApplicationContext context) {
		ConfigurableEnvironment environment = context.getEnvironment();
		// 获取核心配置文件中指定的初始化器类
		List<Class<?>> initializerClasses = getInitializerClasses(environment);
		if (!initializerClasses.isEmpty()) {
			// 利用获取到的初始化器类对应用上下文进行初始化
			applyInitializerClasses(context, initializerClasses);
		}
	}

	/**
	 * 读取核心配置文件中 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;

	}

	/**
	 * 使用指定的初始化器类对应用上下文进行初始化
	 */
	private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
		Class<?> contextClass = context.getClass();
		List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>();
		for (Class<?> initializerClass : initializerClasses) {
			initializers.add(instantiateInitializer(contextClass, initializerClass));
		}
		applyInitializers(context, initializers);
	}
	
	/**
	 * 使用指定的初始化器对应用上下文进行初始化
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void applyInitializers(ConfigurableApplicationContext context,
			List<ApplicationContextInitializer<?>> initializers) {
		// 对初始化器进行 Order 排序
		Collections.sort(initializers, new AnnotationAwareOrderComparator());
		for (ApplicationContextInitializer initializer : initializers) {
			initializer.initialize(context);
		}
	}
	// 此处省略其它内容

}

ContextIdApplicationContextInitializer

ContextIdApplicationContextInitializer 初始化器的作用是给应用上下文设置一个ID,这个ID由 name 和 index 两个部分组成。
其中 name 会依次从读取核心配置的以下三个属性,如果存在则立即使用,若不存在则继续查找下一个,都不存在则取默认值 “application”。 
spring.application.name
vcap.application.name
spring.config.name
index 会依次从核心配置的以下几个属性获取,如果存在则立即使用,若不存在则继续查找下一个,都不存在则为空。
vcap.application.instance_index
spring.application.index
server.port
PORT
所以,ID的格式应为 [application.name][:application.index] ,例如,application:8080 。
 
public class ContextIdApplicationContextInitializer implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

	private static final String NAME_PATTERN = "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}";

	private static final String INDEX_PATTERN = "${vcap.application.instance_index:${spring.application.index:${server.port:${PORT:null}}}}";

	private final String name;

	private int order = Ordered.LOWEST_PRECEDENCE - 10;

	// 为应用上下文设置ID
	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		applicationContext.setId(getApplicationId(applicationContext.getEnvironment()));
	}

	private String getApplicationId(ConfigurableEnvironment environment) {
		String name = environment.resolvePlaceholders(this.name);
		String index = environment.resolvePlaceholders(INDEX_PATTERN);
		String profiles = StringUtils
				.arrayToCommaDelimitedString(environment.getActiveProfiles());
		if (StringUtils.hasText(profiles)) {
			name = name + ":" + profiles;
		}
		if (!"null".equals(index)) {
			name = name + ":" + index;
		}
		return name;
	}
	// 此处省略其它内容

}
注意:ContextIdApplicationContextInitializer 初始化器的 Order 为 Ordered.LOWEST_PRECEDENCE - 10 ,若要在自定义初始化器中通过 ConfigurableApplicationContext.getId() 方法获取上下文ID值,自定义初始化器的 Order 需要大于Ordered.LOWEST_PRECEDENCE - 10 。

ConfigurationWarningsApplicationContextInitializer

ConfigurationWarningsApplicationContextInitializer 初始化器用来对常见的由于配置错误而引起的警告进行打印报告。
 
public class ConfigurationWarningsApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	private static final Log logger = LogFactory
			.getLog(ConfigurationWarningsApplicationContextInitializer.class);

	@Override
	public void initialize(ConfigurableApplicationContext context) {
		// 添加一个 ConfigurationWarningsPostProcessor 用来打印警告信息
		context.addBeanFactoryPostProcessor(
				new ConfigurationWarningsPostProcessor(getChecks()));
	}

	// 此处省略其它内容
}
其中的 ConfigurationWarningsPostProcessor 是一个内部类,用来打印注册 BeanDefinition 过程中产生的配置错误警告信息。 
protected final static class ConfigurationWarningsPostProcessor
			implements PriorityOrdered, BeanDefinitionRegistryPostProcessor {

	private Check[] checks;

	public ConfigurationWarningsPostProcessor(Check[] checks) {
		this.checks = checks;
	}

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

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
			throws BeansException {
		for (Check check : this.checks) {
			String message = check.getWarning(registry);
			if (StringUtils.hasLength(message)) {
				warn(message);
			}
		}

	}

	private void warn(String message) {
		if (logger.isWarnEnabled()) {
			logger.warn(String.format("%n%n** WARNING ** : %s%n%n", message));
		}
	}

}

ServerPortInfoApplicationContextInitializer

ServerPortInfoApplicationContextInitializer 初始化器通过监听 EmbeddedServletContainerInitializedEvent 事件,来对内部服务器实际要监听的端口号进行属性设置。
public class ServerPortInfoApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

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

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

				});
	}

	protected void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
		String propertyName = getPropertyName(event.getApplicationContext());
		setPortProperty(event.getApplicationContext(), propertyName,
				event.getEmbeddedServletContainer().getPort());
	}

	protected String getPropertyName(EmbeddedWebApplicationContext context) {
		String name = context.getNamespace();
		if (StringUtils.isEmpty(name)) {
			name = "server";
		}
		return "local." + name + ".port";
	}

	private void setPortProperty(ApplicationContext context, String propertyName,
			int port) {
		if (context instanceof ConfigurableApplicationContext) {
			setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(),
					propertyName, port);
		}
		if (context.getParent() != null) {
			setPortProperty(context.getParent(), propertyName, port);
		}
	}

	@SuppressWarnings("unchecked")
	private void setPortProperty(ConfigurableEnvironment environment, String propertyName,
			int port) {
		MutablePropertySources sources = environment.getPropertySources();
		PropertySource<?> source = sources.get("server.ports");
		if (source == null) {
			source = new MapPropertySource("server.ports", new HashMap<String, Object>());
			sources.addFirst(source);
		}
		((Map<String, Object>) source.getSource()).put(propertyName, port);
	}

}

SharedMetadataReaderFactoryContextInitializer

SharedMetadataReaderFactoryContextInitializer 初始化器用来创建一个可以在 ConfigurationClassPostProcessor 和Spring Boot 之间共享的CachingMetadataReaderFactory。
	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		applicationContext.addBeanFactoryPostProcessor(new CachingMetadataReaderFactoryPostProcessor());
	}

AutoConfigurationReportLoggingInitializer

AutoConfigurationReportLoggingInitializer 初始化器用来将 ConditionEvaluationReport 记录的条件评估详情输出到日志,默认使用 DEBUG 级别,当有异常问题发生时会使用 INFO 级别。
 
	@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());
		}
	}
各个初始化器的执行顺序如下:
DelegatingApplicationContextInitializer --> ContextIdApplicationContextInitializer --> ConfigurationWarningsApplicationContextInitializer -->ServerPortInfoApplicationContextInitializer-->SharedMetadataReaderFactoryContextInitializer--> AutoConfigurationReportLoggingInitializer

本文项目源码已上传至CSDN,资源地址:https://download.csdn.net/download/pengjunlee/10335994