Detailed Explanation of Spring Boot System Initializer

Spring Boot 3.x series of articles

  1. Spring Boot 2.7.8 Chinese Reference Guide (1)
  2. Spring Boot 2.7.8 Chinese Reference Guide (2)-Web
  3. Spring Boot source code reading initialization environment construction
  4. Detailed explanation of the overall startup process of the Spring Boot framework
  5. Detailed Explanation of Spring Boot System Initializer

custom system initializer

Spring Boot has a variety of ways to load custom initializers:
1. Create a class that implements the ApplicationContextInitializer interface, spring.factoriesadd it in, such as MyInitializer
2. Create a class that implements the ApplicationContextInitializer interface, and add it in SpringApplication addInitializers, such as MyInitializer2
3. Create an implementation The class of the ApplicationContextInitializer interface is added in or application.yml, such as 4. Create a class that implements the EnvironmentPostProcessor interface, and add it in, such asapplication.propertiescontext.initializer.classesMyInitializer3
spring.factoriesMyEnvironmentPostProcessor

The code looks like this:

MyInitializer.java


import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Order(2)
public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
    
    
        ConfigurableEnvironment configurableEnvironment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("key", "value");
        MapPropertySource mapPropertySource = new MapPropertySource("mySource", map);
        configurableEnvironment.getPropertySources().addLast(mapPropertySource);
        log.info("My Initializer run");
    }
}

MyInitializer2.java


import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Order(1)
public class MyInitializer2 implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
    
    
        ConfigurableEnvironment configurableEnvironment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("key2", "value2");
        MapPropertySource mapPropertySource = new MapPropertySource("mySource", map);
        configurableEnvironment.getPropertySources().addLast(mapPropertySource);
        log.info("My Initializer2 run");
    }
}

MyInitializer3.java


import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Order(10)
public class MyInitializer3 implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
    
    
        ConfigurableEnvironment configurableEnvironment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("key3", "value3");
        MapPropertySource mapPropertySource = new MapPropertySource("mySource", map);
        configurableEnvironment.getPropertySources().addLast(mapPropertySource);
        log.info("My Initializer3 run");
    }
}

MyEnvironmentPostProcessor.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Order(5)
public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
    
    
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("key", "value");
        MapPropertySource mapPropertySource = new MapPropertySource("mySource", map);
        environment.getPropertySources().addLast(mapPropertySource);
        //为什么不打印日志
//        log.info("My EnvironmentPostProcessor run");
        System.out.println("My EnvironmentPostProcessor run");
    }
}

Screenshot after startup:
insert image description here

question❓

  • In the example of MyEnvironmentPostProcessor, using log.info("My EnvironmentPostProcessor run");will not print the log.
  • How can the output of MyInitializer3 be before MyInitializer2.

loading principle

Example 1 loading principle

In the previous article " Detailed Explanation of the Overall Startup Process of the Spring Boot Framework ", it was introduced that when the Spring Boot application is initialized, it will start fromMETA-INF/spring.factories加载ApplicationContextInitializer类实例

insert image description here
SpringFactoriesLoaderIs a class in the Spring framework that is used to META-INF/spring.factoriesload and instantiate a given type from multiple Jar files. spring.factoriesThe file must be in Propertiesthe format, where key is the fully qualified name of an interface or abstract class, and value is a comma-separated implementation class name list. For example:
example.MyService = example.MyServicesImpl1, example.MyServiceImpl2
where example.MyService is the name of the interface and MyServiceImpl1 and MyServiceImpl2 are the two implementations.

Obtaining the instance is divided into two parts. First, META-INF/spring.factoriesload the key and value from multiple Jar files, return a SpringFactoriesLoader instance, and then call the load method of SpringFactoriesLoader to initialize all the values ​​corresponding to the specified key (the key is the fully qualified name of the interface or abstract class). (Interface implementation class), returns a list of instances.

loading of spring.factories

insert image description here
FACTORIES_RESOURCE_LOCATIONThe path to load is specified

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {
    
    
// 判断资源路径是否为空,若为空则抛出异常
	Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
// 获取资源对应的类加载器,若传入的类加载器为空,则使用SpringFactoriesLoader类的类加载器
	ClassLoader resourceClassLoader = (classLoader != null ? classLoader :
			SpringFactoriesLoader.class.getClassLoader());
// 从缓存中获取SpringFactoriesLoader,若不存在,则创建一个并缓存 Map<String, SpringFactoriesLoader>,key为ClassLoader,资源对应的类加载器
	Map<String, SpringFactoriesLoader> loaders = cache.computeIfAbsent(
			resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
// 返回resourceLocation对应的SpringFactoriesLoader对象,若不存在,则创建一个并缓存,key为resourceLocation,资源路径
	return loaders.computeIfAbsent(resourceLocation, key ->
			new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
}

computeIfAbsentReturns the value associated with the key

The last step value creates an SpringFactoriesLoaderinstance, loadFactoriesResourceloaded from "META-INF/spring.factories" using the given resource class loader

protected static Map<String, List<String>> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
    
    
//实现列表,key=接口或抽象类全限定名 value=实现类全限定名
	Map<String, List<String>> result = new LinkedHashMap<>();
	try {
    
    
	//获取指定路径下所有的资源URL
		Enumeration<URL> urls = classLoader.getResources(resourceLocation);
		while (urls.hasMoreElements()) {
    
    
			UrlResource resource = new UrlResource(urls.nextElement());
			//从URL资源中读取配置
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			properties.forEach((name, value) -> {
    
    
			//实现类逗号分割,转换为数组
				String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) value);
				//接口的实现类列表
				List<String> implementations = result.computeIfAbsent(((String) name).trim(),
						key -> new ArrayList<>(factoryImplementationNames.length));

//去掉实现类两边空格,并插入实现类列表
				Arrays.stream(factoryImplementationNames).map(String::trim).forEach(implementations::add);
			});
		}
		
//去重
result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList);
	}
	catch (IOException ex) {
    
    
		throw new IllegalArgumentException("Unable to load factories from location [" + resourceLocation + "]", ex);
	}
	//返回不可修改的map
	return Collections.unmodifiableMap(result);
}

There are a lot of keys in the loading part, and the values ​​should be clearly distinguished.

Spring.factories interface implementation class instantiation

Instantiate by calling the load method of SpringFactoriesLoader

	public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver) {
    
    
		return load(factoryType, argumentResolver, null);
	}

factoryTypeSpecify the type to be instantiated, here is
argumentResolverthe parameter required for org.springframework.context.ApplicationContextInitializer instantiation, here is null

public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver,
		@Nullable FailureHandler failureHandler) {
    
    

	Assert.notNull(factoryType, "'factoryType' must not be null");
	//从factories 中获取指定接口类型的所有实现
	//factories就是加载步骤中返回的result
	List<String> implementationNames = loadFactoryNames(factoryType);
	logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames));
	List<T> result = new ArrayList<>(implementationNames.size());
	//定义失败处理器
	FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;
	//循环,实例化
	for (String implementationName : implementationNames) {
    
    
	//通过构造函数实例化
		T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
		if (factory != null) {
    
    
			result.add(factory);
		}
	}
	//根据order 排序
	AnnotationAwareOrderComparator.sort(result);
	return result;
}

Finally, return the sorted ApplicationContextInitializer instance and assign it to the initializers variable of SpringApplication.

implement

The execution will be called in the prepareContext (preparation context) of the SpringApplication class, as shown in the figure:
insert image description here

	//返回一个只读的有序,LinkedHashSet 类型
	public Set<ApplicationContextInitializer<?>> getInitializers() {
    
    
		return asUnmodifiableOrderedSet(this.initializers);
	}
	protected void applyInitializers(ConfigurableApplicationContext context) {
    
    
	//获取所有的ApplicationContextInitializer实例
		for (ApplicationContextInitializer initializer : getInitializers()) {
    
    
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
			// 判断ApplicationContextInitializer实例泛型是否与context对象类型一致
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			// 调用ApplicationContextInitializer实例的initialize方法进行初始化操作
			initializer.initialize(context);
		}
	}

Example 2 loading principle

addInitializersCreate a class that implements the ApplicationContextInitializer interface, use add in SpringApplication , eg MyInitializer2.

We use addInitializers to add the implementation of the ApplicationContextInitializer interface to SpringApplication.

	public void addInitializers(ApplicationContextInitializer<?>... initializers) {
    
    
		this.initializers.addAll(Arrays.asList(initializers));
	}

initializers is the initializers variable in SpringApplication. The execution point is the same as instance 1. It is executed when the context is prepared. Since a sort is performed before execution, the order of the two is correct.

Example 3 loading principle

Create a class that implements the ApplicationContextInitializer interface, use Add in application.ymlor , such asapplication.propertiescontext.initializer.classesMyInitializer3

Here, the ApplicationContextInitializer implementation class is added through the configuration file, and DelegatingApplicationContextInitializerloaded and executed through the initializer.

insert image description here
DelegatingApplicationContextInitializer is defined META-INF/spring.factoriesin , and because its order is 0, it will be executed before we customize MyInitializer and MyInitializer2. It is another independent initializer, which is specially used to configure the configuration file The ApplicationContextInitializer implementation class is loaded into the Spring container.

insert image description here
Executed in the applyInitializers method of the DelegatingApplicationContextInitializer class

private void applyInitializers(ConfigurableApplicationContext context,
		List<ApplicationContextInitializer<?>> initializers) {
    
    
		//排序
	initializers.sort(new AnnotationAwareOrderComparator());
	for (ApplicationContextInitializer initializer : initializers) {
    
    
	//调用initialize方法
		initializer.initialize(context);
	}
}

Example 4 Loading principle

Create a class that implements the EnvironmentPostProcessor interface, spring.factoriesadd in, eg MyEnvironmentPostProcessor.
Instance 4 is the first to print the log in all tests because it is prepareEnvironmentexecuted in (preparation environment), while the previous three instances are all prepareContext(准备上下文)executed in .
The prepareEnvironment method will be called in this instance EventPublishingRunListener, EventPublishingRunListenerwhich is defined in the Spring Boot Jar package META-INF/spring.factoriesand used to publish various SpringApplicationEvent events.

EventPublishingRunListenerin class

public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
		ConfigurableEnvironment environment) {
    
    
		//广播环境准备完成事件
	multicastInitialEvent(
			new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}

private void multicastInitialEvent(ApplicationEvent event) {
    
    
	//刷新SimpleApplicationEventMulticaster中的事件列表
	refreshApplicationListeners();
	//广播事件
	this.initialMulticaster.multicastEvent(event);
}

private void refreshApplicationListeners() {
    
    
	this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);
}

SimpleApplicationEventMulticasterin class

public void multicastEvent(ApplicationEvent event) {
    
    
	multicastEvent(event, null);
}

@Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
    
    
	ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
	// 获取执行事件的线程池
	Executor executor = getTaskExecutor();
	//获取指定事件类型的事件集合
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    
    
	//如果定义了执行线程池,则用线程池调用
		if (executor != null) {
    
    
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
    
    
		//同步调用监听器
			invokeListener(listener, event);
		}
	}
}

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    
    
//获取失败处理器
	ErrorHandler errorHandler = getErrorHandler();
	if (errorHandler != null) {
    
    
		try {
    
    
			doInvokeListener(listener, event);
		}
		catch (Throwable err) {
    
    
			errorHandler.handleError(err);
		}
	}
	else {
    
    
		doInvokeListener(listener, event);
	}
}

@SuppressWarnings({
    
    "rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    
    
	try {
    
    
	//此处执行事件监听器的onApplicationEvent方法
		listener.onApplicationEvent(event);
	}
	catch (ClassCastException ex) {
    
    
		String msg = ex.getMessage();
		if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
				(event instanceof PayloadApplicationEvent payloadEvent &&
						matchesClassCastMessage(msg, payloadEvent.getPayload().getClass()))) {
    
    
			// Possibly a lambda-defined listener which we could not resolve the generic event type for
			// -> let's suppress the exception.
			Log loggerToUse = this.lazyLogger;
			if (loggerToUse == null) {
    
    
				loggerToUse = LogFactory.getLog(getClass());
				this.lazyLogger = loggerToUse;
			}
			if (loggerToUse.isTraceEnabled()) {
    
    
				loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
			}
		}
		else {
    
    
			throw ex;
		}
	}
}

at listener.onApplicationEvent(event);, in this case theEnvironmentPostProcessorApplicationListener

EnvironmentPostProcessorApplicationListenerIn class:

public void onApplicationEvent(ApplicationEvent event) {
    
    
//根据各个事件类型分别去处理
	if (event instanceof ApplicationEnvironmentPreparedEvent environmentPreparedEvent) {
    
    
		onApplicationEnvironmentPreparedEvent(environmentPreparedEvent);
	}
	if (event instanceof ApplicationPreparedEvent) {
    
    
		onApplicationPreparedEvent();
	}
	if (event instanceof ApplicationFailedEvent) {
    
    
		onApplicationFailedEvent();
	}
}

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    
    
	ConfigurableEnvironment environment = event.getEnvironment();
	SpringApplication application = event.getSpringApplication();
	// 获取所有的 EnvironmentPostProcessor,然后执行其 postProcessEnvironment 方法
	for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
			event.getBootstrapContext())) {
    
    
		postProcessor.postProcessEnvironment(environment, application);
	}
}

// 获取所有的 EnvironmentPostProcessor
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(ResourceLoader resourceLoader,
		ConfigurableBootstrapContext bootstrapContext) {
    
    
	ClassLoader classLoader = (resourceLoader != null) ? resourceLoader.getClassLoader() : null;
	//postProcessorsFactory 是一个函数表达式
	EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader);
	return postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
}

EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader);The postProcessorsFactory in is EnvironmentPostProcessorApplicationListenerinitialized when it is instantiated. According to the previous article, we know that EnvironmentPostProcessorApplicationListenerit is a listener, which will be initialized when SpringBoot is initialized.

public EnvironmentPostProcessorApplicationListener() {
    
    
	this(EnvironmentPostProcessorsFactory::fromSpringFactories);
}

private EnvironmentPostProcessorApplicationListener(
		Function<ClassLoader, EnvironmentPostProcessorsFactory> postProcessorsFactory) {
    
    
	this.postProcessorsFactory = postProcessorsFactory;
	this.deferredLogs = new DeferredLogs();
}

static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {
    
    
	return new SpringFactoriesEnvironmentPostProcessorsFactory(
			SpringFactoriesLoader.forDefaultResourceLocation(classLoader));
}

EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader)It will EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader);be called when applying, if it is not loaded, META-INF/spring.factoriesit will be loaded here again.

EnvironmentPostProcessorsFactoryThe main role is instantiation EnvironmentPostProcessor, SpringFactoriesEnvironmentPostProcessorsFactoryis its subclass.

SpringFactoriesEnvironmentPostProcessorsFactoryIn class:

public List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
		ConfigurableBootstrapContext bootstrapContext) {
    
    
	ArgumentResolver argumentResolver = ArgumentResolver.of(DeferredLogFactory.class, logFactory);
	//向argumentResolver对象中添加ConfigurableBootstrapContext.class和bootstrapContext,获取更新后的argumentResolver对象
	argumentResolver = argumentResolver.and(ConfigurableBootstrapContext.class, bootstrapContext);
	// // 向argumentResolver对象中添加BootstrapRegistry.class和bootstrapContext,获取更新后的argumentResolver对象
	argumentResolver = argumentResolver.and(BootstrapContext.class, bootstrapContext);
	 通过this.loader.load方法加载EnvironmentPostProcessor类型的对象,参数为argumentResolver
	argumentResolver = argumentResolver.and(BootstrapRegistry.class, bootstrapContext);
	//加载EnvironmentPostProcessor类型的对象
	return this.loader.load(EnvironmentPostProcessor.class, argumentResolver);
}

Finally the loop call postProcessor.postProcessEnvironment(environment, application);completes execution.

Summarize

Similarly, use a picture to summarize the entire process of this article:
insert image description here


Other articles by the author:
Prometheus series of articles

  1. Introduction and installation of Prometheus
  2. Intuitive experience of PromQL and its data types
  3. PromQL selectors and operators
  4. Functions of PromQL
  5. Prometheus alarm mechanism introduction and command interpretation
  6. Prometheus alarm module configuration depth analysis
  7. Prometheus configuration authentication
  8. Prometheus dynamically pulls monitoring services
  9. Prometheus monitors cloud Mysql and self-built Mysql

Grafana series of articles, version: OOS v9.3.1

  1. Introduction and installation of Grafana
  2. Introduction to configuration parameters of Grafana monitoring large screen (1)
  3. Introduction to configuration parameters of Grafana monitoring large screen (2)
  4. Grafana monitors large-screen visualization charts
  5. Grafana query data and transform data
  6. Introduction to Grafana Alarm Module
  7. Grafana alarm access Feishu notification

Spring Boot Admin series

  1. Spring Boot Admin Reference Guide
  2. The problem that the SpringBoot Admin service is offline and does not display health information
  3. Loading of Spring Boot Admin2 @EnableAdminServer
  4. Detailed Explanation of Spring Boot Admin2 AdminServerAutoConfiguration
  5. Detailed Explanation of Spring Boot Admin2 Instance Status Monitoring
  6. Spring Boot Admin2 custom JVM monitoring notification
  7. Spring Boot Admin2 custom exception monitoring
  8. Spring Boot Admin monitoring indicators connected to Grafana visualization

Guess you like

Origin blog.csdn.net/weixin_40972073/article/details/131034991
Recommended