【SpringBoot源码】初始化器ApplicationContextInitializer介绍、回调流程

【SpringBoot源码】初始化器ApplicationContextInitializer介绍、回调流程

Scroll Down

文章目录

前言

本节就来了解一下ApplicationContextInitializer的使用、回调流程,会跟进源码看。

Spring Boot Version:2.1.7/2.1.9

ApplicationContextInitializer简介

SpringBoot在刷新上下文之前,会遍历所有配置文件中配置的ApplicationContextInitializer实现类并调用其initialize方法,用户可通过实现ApplicationContextInitializer接口+覆写initialize方法从而在刷新上下文之前做一些事情,比如:初始化一些自定义上下文属性。


实验代码

java代码

为了加深印象、断点追踪源码,我需要写一些实验代码,以下列出一些核心代码or配置,相信有springBoot使用经验的人能秒懂。


FirstInitializer.java

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;

@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("key1", "value1");
        MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run firstInitializer");
    }
}

SecondInitializer.java

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;

@Order(2)
public class SecondInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("key2", "value2");
        MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run secondInitializer");
    }
}

ThirdInitializer.java

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;

@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("key3", "value3");
        MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run thirdInitializer");
    }
}


  • 记住ThirdInitializer的@Order(3)

DemoService.java

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class TestService implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 返回上下文的环境变量/属性
     */
    public String test(String property) {
        return applicationContext.getEnvironment().getProperty(property);
    }

}

DemoController.java

import com.wenjie.sb2.service.DemoService;
import com.wenjie.sb2.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Optional;

@Controller
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private TestService testService;

    @RequestMapping("test")
    @ResponseBody
    public String test() {
        String string = testService.test("key1") + "\n"
                + testService.test("key2") + "\n"
                + testService.test("key3");
        return string;
    }
}

TestDemoApplication.java

import com.wenjie.sb2.initializer.SecondInitializer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Sb2Application {

	public static void main(String[] args) {
		SpringApplication springApplication = new SpringApplication(TestDemoApplication.class);
		springApplication.addInitializers(new SecondInitializer());
		springApplication.run(args);
	}

}

配置文件

spring.factories

org.springframework.context.ApplicationContextInitializer=com.wenjie.sb2.initializer.FirstInitializer
  • 第二种设置ApplicationContextInitializer方法。
  • 第一种设置方法在上面的main函数中。

spring.factories所在目录.png

  • 为什么在这个目录?等下跟进源码就知道了。

application.properties


server.port=8080
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://192.168.129.133:3306/test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.configuration.map-underscore-to-camel-case=true
logging.pattern.console=[%thread] %-5level %logger{50} - %msg%n
context.initializer.classes=com.wenjie.sb2.initializer.ThirdInitializer
  • 第三种设置ApplicationContextInitializer方法。

输出结果

启动应用,控制台、访问Controller输出结果如下:

控制台输出
控制台输出.png

  • 注意,thirdInitializer的注解是@Order(3),但却是最先完成调用的,后面会解释为什么。

浏览器访问接口,成功返回自定义属性:
浏览器输出三个自定义属性.png


下面就围绕实验代码、输出结果一步步跟进源码。


跟进源码

读取spring.factories配置

读取spring.factories配置最先拿到FirstInitializer的包地址。当然,除了用户自定义的spring.factories外,springboot也自带了一些spring.factories,如下图:

spring.factories.png

  • 说明除了SpringBoot也有自带的Initializer,后面我们需要查看这里面的其中一个原生配置。

下面就从源码体验一下这个加载配置文件的过程。


启动应用时,首先是要构造SpringApplication,那我们就直接找到SpringApplication的构造函数看:
org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

跟进getSpringFactoriesInstances方法:
org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class)

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

跟进getSpringFactoriesInstances方法,此处【坐标1】
org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class, java.lang.Class<?>[], java.lang.Object...)

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		// 获取/构造ClassLoader
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		// 读取完配置文件后,将要加载的类的包路径保存到set集合中
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		// 利用classLoader去加载符合条件的类
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		// 根据@Order注解的值排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

下面先跟进SpringFactoriesLoader.loadFactoryNames方法:
org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}

跟进loadSpringFactories方法,就能看到比较完整的加载配置逻辑了:
org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		// 尝试从缓存中加载
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// 加载配置
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				// 遍历加载到的配置
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					// 以,分割值,逐个保存
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			// 缓存集合
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
  • 相信经历过只用serverlet写java项目(要自己写代码加载配置)的人来说,上面的代码逻辑是没啥难度的,只是spring二次封装过的工具类而已。

看看最终返回的集合,里面就包含了FirstInitializer的包地址:
返回集合.png

来到这里不妨再看看FACTORIES_RESOURCE_LOCATION属性到底是啥,看完后相信你就明白为什么spring.factories要放在META-INF下了:

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

最后将返回的对象保存到一个ArrayList中,完成构造:
org.springframework.boot.SpringApplication#setInitializers

	public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}
  • 请记住这个initializers,后面会用到
  • 另外补充一点:SecondInitializer的对象也是被添加到这个initializers中的,不信你可以跟进一下实验代码addInitializers方法,我就不这里赘述了。

回顾整个读取spring.factories的流程,画一张流程图大概就是下面这个样子:

加载芮城.png

好了,现在我们已经知道读取spring.factories的整个流程了,而且还顺利获取到FirstInitializer的包路径,接下来就是要拿这个包路径来初始化了。



构造Initializers对象

视角重新转回到上面【坐标1】的getSpringFactoriesInstances方法,继续跟进createSpringFactoriesInstances方法:
org.springframework.boot.SpringApplication#createSpringFactoriesInstances

	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				// 使用类加载器构造对象
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				// 将构造后的对象存储到集合中
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}
  • 可见就是遍历前面读取到的包路径,利用反射机制去构造对象。

构造完之后,就存储到List集合中并返回,最后调用AnnotationAwareOrderComparator.sort(instances);进行排序。


不要忘了Initializer的作用是刷新上下文之前做某些初始化操作,现在我们还仅仅是完成了Initializer的对象构造而已,还没有调用实验代码覆写的initialize方法。

关于SecondInitializer、ThirdInitializer的构造,会在下文遇到。



回调initialize方法

回调initialize方法,是在SpringApplication完成构造之后执行的,所以我们需要跟进SpringApplication的run方法:

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			// 准备上下文
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			// 刷新上下文

很明显,初始化器完成初始化理应在准备上下文阶段的,跟进prepareContext方法:
org.springframework.boot.SpringApplication#prepareContext

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		// 遍历context中的Initializers,调用它们的initialize方法
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}
  • 我们之前构造出的一批初始化器就在context中。

跟进applyInitializers方法,此处【坐标2】
org.springframework.boot.SpringApplication#applyInitializers

	protected void applyInitializers(ConfigurableApplicationContext context) {
		// getInitializers()就是获取前面的initializers集合
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			// 这里就会调用实验代码覆写的initialize方法
			initializer.initialize(context);
		}
	}

要是不信还可以打断点看看调试结果:
断点1.png
断点2.png


SecondInitializer的调用跟FirstInitializer大致相同,上面也说了SecondInitializer是如何被添加到initializers集合的。

好了,现在FirstInitializer、SecondInitializer的回调流程都明白了,且两者回调顺序也与设置的@Order一致,那么ThirdInitializer呢?为什么明明设置了@Order(3),但它却是第一个调用的呢?想要知道原因,需要更进一步的跟进源码。



ThirdInitializer解谜

从提供实验代码中不难猜测,ThirdInitializer调用的顺序不一样,大概率是由于配置加载的顺序不一样,而事实也确实如此,下面就来看看ThirdInitializer到底实在哪里被实例化的,为什么顺序不对,又是哪里调用了它的initialize方法。

视角重新回到上面遇到过的applyInitializers方法:
org.springframework.boot.SpringApplication#applyInitializers

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

经过多次调试,ThirdInitializer的调用是在applyInitializers执行时,且遍历到FirstInitializer之前出现的,所以重点应该观察FirstInitializer之前的Initializer到底做了什么。

下面先来看看getInitializers()返回的集合:
getInitializers返回集合.png

  • 此时不难推测,ThirdInitializer的相关操作肯定就是在前两个Initializer中的某一个完成的。

经过调试,果不其然,就是在DelegatingApplicationContextInitializer的initialize完成ThirdInitializer的相关操作的,下面跟进DelegatingApplicationContextInitializer#initialize:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer#initialize

	@Override
	public void initialize(ConfigurableApplicationContext context) {
		ConfigurableEnvironment environment = context.getEnvironment();
		List<Class<?>> initializerClasses = getInitializerClasses(environment);
		if (!initializerClasses.isEmpty()) {
			applyInitializerClasses(context, initializerClasses);
		}
	}

先跟进getInitializerClasses方法:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer#getInitializerClasses

	private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {


		String classNames = env.getProperty(PROPERTY_NAME);
		List<Class<?>> classes = new ArrayList<>();
		if (StringUtils.hasLength(classNames)) {
			for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
				classes.add(getInitializerClass(className));
			}
		}
		return classes;
	}
  • 这一步完成了ThirdInitializer对象的构造并返回。

PROPERTY_NAME属性:

	private static final String PROPERTY_NAME = "context.initializer.classes";
  • 就是application.properties设置的值。

完成了ThirdInitializer对象的构造后,将对象返回到上面的initialize方法,我们继续往下跟,跟进applyInitializerClasses方法:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializerClasses

	private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
		Class<?> contextClass = context.getClass();
		List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
		for (Class<?> initializerClass : initializerClasses) {
			initializers.add(instantiateInitializer(contextClass, initializerClass));
		}
		applyInitializers(context, initializers);
	}
  • 注意这里的initializers是新定义的,也会排序,但这里的initializers不是上文说到的SpringApplication的initializers。
  • 这也说明:ThirdInitializer的@Order跟FirstInitializer、SecondInitializer不在同一层级上
  • 也就是说ThirdInitializer与(FirstInitializer、SecondInitializer)的执行顺序并没有直接关系,只是因为DelegatingApplicationContextInitializer的优先级比FirstInitializer、SecondInitializer高,所以ThirdInitializer的调用就在前面了。

继续跟进applyInitializers方法:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializers

	private void applyInitializers(ConfigurableApplicationContext context,
			List<ApplicationContextInitializer<?>> initializers) {
		initializers.sort(new AnnotationAwareOrderComparator());
		for (ApplicationContextInitializer initializer : initializers) {
			initializer.initialize(context);
		}
	}
  • 同样是遍历+回调,这里的initialize就是回调到initialize方法。
  • 当initializer是ThirdInitializer实例时,就会回调到它的initialize方法,即实验我们自定义的实验代码。

现在,从读取配置,构造Initializer、回调Initializer的initialize方法,我们都过一遍了,到这里你可能还有一个问题。

  • Q:包括DelegatingApplicationContextInitializer在内,其余Initializer是在哪里声明了要初始化的呢?
  • A:在介绍spring.factories的时候就说过了,SpringBoot原本就附带了一些配置文件,其中一个spring.factories就包含了DelegatingApplicationContextInitializer,不信看看下面的截图:
    其中一个自带的配置文件.png

本节内容的核心大概就是之前springboot启动流程图的这一部分:
image.png


小结

  • ApplicationContextInitializer的使用方式:实现ApplicationContextInitializer接口+覆写initialize方法。
  • 在initialize方法中我们可以自定义一些业务逻辑,比如:初始化一些上下文属性。
  • 添加Initializer有三种方式:
    • application.properties中配置(优先级高于后面两种)。
    • spring.factories中配置。
    • SpringApplication#addInitializers
  • 要注意ThirdInitializer这种配置方式会使@Order"失效"的问题。