spring boot 2源码系列(一)- 系统初始化器ApplicationContextInitializer

ApplicationContextInitializer接口的文档是这么写的:

1、ApplicationContextInitializer是一个回调接口,用于在ConfigurableApplicationContext#refresh()执行刷新之前初始化ConfigurableApplicationContext。SpringApplication#prepareContext()方法会执行ApplicationContextInitializer实现类的initialize方法。

2、通常在需要对应用程序上下文进行一些编程初始化的Web应用程序中使用,例如对ConfigurableApplicationContext#getEnvironment()注册属性源或者激活配置文件。

3、鼓励系统初始化器去实现org.springframework.core.Ordered Ordered接口或者使用@Order注解,以便在调用之前对实例进行排序。

若不举例说明,很难理解ApplicationContextInitializer接口的文档说的是什么意思。那现在就开始介绍系统初始化器的3种使用方式吧。

第一种:使用spring.factories配置

1、新建FirstInitializer实现ApplicationContextInitializer接口

/**
 * 使用@Order注解(第3点)
 */
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    /**
     * 在resources目录下新建/META-INF/spring.factories目录与文件。
     * 在resources/META-INF/spring.factories配置:
     * org.springframework.context.ApplicationContextInitializer=com.example.springbootdemo.FirstInitializer
     *
     * spring.factories的配置规则是: 接口全名=实现类全名
     */
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        /**
         * 本方法代码是添加属性 key1=value1。
         *
         * 可通过以下方式获取添加的属性:
         * @Value("${key1}")
         * String key1;
         */
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("key1", "value1");

        // 新建属性源,并添加属性源。(第2点)
        MapPropertySource mps = new MapPropertySource("firstInitializer", map);
        environment.getPropertySources().addLast(mps);

        System.out.println("#############FirstInitializer.initialize 运行");
    }

}

2、在resources目录下新建/META-INF/spring.factories目录与文件

在spring.factories中添加

org.springframework.context.ApplicationContextInitializer=com.example.springbootdemo.FirstInitializer

3、获取属性值的方式如下:

@Value("${key1}")
String key1;

4、启动工程,就能获取值了。

5、不使用@Order,通过实现Ordered接口实现排序。新建FirstOrderedInitializer.java

/**
 * 通过实现Ordered接口实现排序
 */
public class FirstOrderedInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("#############FirstOrderedInitializer.initialize 运行");
    }

    // 返回排序值
    @Override
    public int getOrder() {
        return 11;
    }
}

6、在spring.factories中添加

org.springframework.context.ApplicationContextInitializer=com.example.springbootdemo.FirstInitializer,\
com.example.springbootdemo.FirstOrderedInitializer

7、运行程序,控制台输出

#############FirstInitializer.initialize 运行
#############FirstOrderedInitializer.initialize 运行

FirstInitializer使用@Order注解排序值是1,FirstOrderedInitializer#getOrder()返回11。由此可知:排序值越小越先执行。

第二种:硬编码方式 addInitializers(new SecondInitializer());

1、新建SecondInitializer.java

@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 mps = new MapPropertySource("secondInitializer", map);
        environment.getPropertySources().addLast(mps);

        System.out.println("#############SecondInitializer.initialize 运行");
    }
}

2、修改启动类,通过代码添加SecondInitializer

@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {

        //SpringApplication.run(SpringBootDemoApplication.class, args);

        // 第2中添加系统初始化器的方式
        SpringApplication springApplication = new SpringApplication(SpringBootDemoApplication.class);
        springApplication.addInitializers(new SecondInitializer());
        springApplication.run(args);
    }

}

第三种:在application.properties配置context.initializer.classes

1、新建ThirdInitializer.java

@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 mps = new MapPropertySource("thirdInitializer", map);
        environment.getPropertySources().addLast(mps);

        System.out.println("#############ThirdInitializer.initialize 运行");
    }
}

2、在application.properties中配置

context.initializer.classes=com.example.springbootdemo.ThirdInitializer

3、启动工程,控制台有以下输出

#############ThirdInitializer.initialize 运行
#############FirstInitializer.initialize 运行
#############SecondInitializer.initialize 运行
#############FirstOrderedInitializer.initialize 运行

ThirdInitializer的注解是@Order(3),但是ThirdInitializer比FirstInitializer、SecondInitializer先运行。是不是使用context.initializer.classes配置的初始化器就会先运行呢?后面会回答这问题。

源码讲解

spring boot 版本是2.2.4.RELEASE。

先把启动类SpringBootDemoApplication.java的代码还原为 

SpringApplication.run(SpringBootDemoApplication.class, args);

使用最常规的启动方式来分析spring boot 的源码。

1、进入run方法内,直到看见如下代码:

// 源码位置org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

可粗略的得出一个结论,spring boot 使用一句代码 SpringApplication.run(SpringBootDemoApplication.class, args); 启动工程,本质上是创建SpringApplication实例,再运行实例的run方法。run方法执行完,工程就启动完成了。

1.1、继续debug,进入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();
}

1.1.1、setInitializers方法源码如下:

// 源码位置 org.springframework.boot.SpringApplication#setInitializers
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
	/**
	 * 系统初始化器可以有多个,形成一个系统初始化器列表。
	 * 在创建SpringApplication对象时,将系统初始化器列表赋值给SpringApplication的initializers属性。
	 */
    this.initializers = new ArrayList<>(initializers);
}

1.1.2、getSpringFactoriesInstances(ApplicationListener.class)是获取系统初始化器的函数

进入方法体中debug

// 源码位置 org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>, java.lang.Class<?>[], java.lang.Object...)
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

1.1.2.1、ClassLoader classLoader = getClassLoader();

getClassLoader()返回结果是AppClassLoader实例。
Java自带了3个类加载器:BootstrapClassLoader、ExtClassLoader、AppClassLoader。AppClassLoader会加载classpath路径下jar包和目录的class文件。我们自己编写的代码以及代码依赖的第三方jar包通常都是由它来加载的。
可运行System.getProperty("java.class.path"),查看当前工程的classpath是什么。

1.1.2.2、Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

type是ApplicationContextInitializer接口,这句代码是获取type接口实现类的类全名集合。进入到SpringFactoriesLoader.loadFactoryNames方法内部。

// 源码位置 org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    /**
     * 重点代码是 loadSpringFactories(classLoader)。
     * 先讲结论,loadSpringFactories(classLoader)返回一个LinkedMultiValueMap对象,此对象一个key对应多个value。
     * key是接口全类名,value是接口的实现类全类名,value可以有多个。
     */
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

debug到loadSpringFactories(classLoader)内部

// 源码位置 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 {
		/**
		 * FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
		 * 通过AppClassLoader加载Classpath下的META-INF/spring.factories
		 * 当前工程和依赖的第三方jar包中的META-INF/spring.factories都会被加载进来
		 */
		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();
			// resource是包含了spring.factories文件路径的资源对象
			UrlResource resource = new UrlResource(url);
			// 使用spring.factories中配置的键值对创建Properties对象
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			// 循环properties的键值对
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryTypeName = ((String) entry.getKey()).trim();
				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					/**
					 * factoryTypeName是spring.factories中配置的key,比如:ApplicationContextInitializer接口全类名
					 * factoryImplementationName是key对应的value,比如:ApplicationContextInitializer实现类FirstInitializer的全类名
					 */
					result.add(factoryTypeName, factoryImplementationName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		
        /**
		 * result是一个LinkedMultiValueMap。key是接口全名,value是接口的实现类全类名,value可以是多个。
		 * 需要注意的是:
		 * 1、AppClassLoader会获取Classpath下所有jar包的spring.factories文件
		 * 2、并将spring.factories中所有的配置添加到result中
		 * 基于以上两点,就不难理解为什么要用缓存了
		 * cache.put(classLoader, result); 、 MultiValueMap<String, String> result = cache.get(classLoader);
		 * AppClassLoader把classpath下所有的spring.factories读取到缓存中。下次要用spring.factories配置时,从缓存中拿就好了
		 */
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

在此总结下 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 这行代码的作用。大致分为以下2个小步骤:

1、使用AppClassLoader获取Classpath下的spring.factories文件的路径

2、读取spring.factories并创建properties对象

3、将type接口(本文中type是ApplicationContextInitializer)实现类的类全名返回。第三方jar包spring.factories中配置的ApplicationContextInitializer实现类的全类名也会被读取并返回,所以结果是Set<String>类型。

1.1.2.3、List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

获得了系统初始化的全类名集合,就能创建系统初始化器实例集合了,源码如下:

// 源码位置 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 {
			// AppClassLoader使用类全名把类加载到jvm内存中
			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;
}

1.1.2.4、AnnotationAwareOrderComparator.sort(instances); 这是对系统初始化器实例集合进行排序。

debug到如下代码内

// 源码位置 org.springframework.core.OrderComparator#doCompare()
private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
	boolean p1 = (o1 instanceof PriorityOrdered);
	boolean p2 = (o2 instanceof PriorityOrdered);
	if (p1 && !p2) {
		return -1;
	}
	else if (p2 && !p1) {
		return 1;
	}

	/**
	 * getOrder方法很复杂,本文只关注getOrder方法的两个作用。
	 * 1、获取@Order注解值
	 * 2、获取Ordered接口getOrder方法返回值
	 */
	int i1 = getOrder(o1, sourceProvider);
	int i2 = getOrder(o2, sourceProvider);

	// 排序规则是升序
	return Integer.compare(i1, i2);
}

在以下代码中打上断点,运行程序debug
com.example.springbootdemo.FirstOrderedInitializer#getOrder()
    // 调用Ordered实现类的getOrder()方法,获取排序值 
    return 2;     
org.springframework.core.annotation.OrderUtils#findOrder()
    // getOrder方法获取@Order注解的值 
    MergedAnnotation<Order> orderAnnotation = annotations.get(Order.class);
    if (orderAnnotation.isPresent()) {
        return orderAnnotation.getInt(MergedAnnotation.VALUE);
    }

至此,系统初始化器的初始化代码就讲完了。重点是getSpringFactoriesInstances方法,下面对此方法做总结:

// 源码位置 org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>, java.lang.Class<?>[], java.lang.Object...)
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	// getClassLoader()返回AppClassLoader
	ClassLoader classLoader = getClassLoader();
	// 获取系统初始化器全类名集合
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	// 创建系统初始化器集合
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	// 对系统初始化器排序
	AnnotationAwareOrderComparator.sort(instances);
	// 返回排序后的系统初始化器集合
	return instances;
}

FirstInitializer添加到SpringApplication.initializers的原理已经讲清楚了。

SecondInitializer添加给SpringApplication.initializers的方式就很简单了,springApplication.addInitializers(new SecondInitializer());  的源码如下:

// 源码位置 org.springframework.boot.SpringApplication#addInitializers
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
	this.initializers.addAll(Arrays.asList(initializers));
}

目前遗留下两个问题:

1、SecondInitializer:addAll是将SecondInitializer添加到this.initializers的末尾,排序位置不对。

2、ThirdInitializer通过application.properties配置,还没被添加到this.initializers中。

带着这两个疑问,我们来探究  new SpringApplication(primarySources).run(args);  的run方法,即SpringApplication的运行阶段。

使用 SpringApplication.run(SpringBootDemoApplication.class, args); 启动程序。在源码中是执行 new SpringApplication(primarySources).run(args); 这句代码跟前面介绍第二种配置系统初始化器的代码很相似,为了配置SecondInitializer,需要把启动类的代码改成

@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {

        //SpringApplication.run(SpringBootDemoApplication.class, args);

        // 第2中添加系统初始化器的方式
        SpringApplication springApplication = new SpringApplication(SpringBootDemoApplication.class);
        springApplication.addInitializers(new SecondInitializer());
        springApplication.run(args);  // 这句代码打上断点调试
    }

}

在 springApplication.run(args); 这句代码打上断点,启动工程、调试。

debug到run方法内

// 源码位置 org.springframework.boot.SpringApplication.run(java.lang.String...)
public ConfigurableApplicationContext run(String... args) {
    /**
     * run方法很复杂,本文作为spring boot源码系列的第一篇博客,仅关注系统初始化器相关代码。其他代码忽略
     * prepareContext方法内会运行系统初始化器
     */
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    // ApplicationContextInitializer接口文档第三点
    refreshContext(context);
}

debug到prepareContext方法中

// 源码位置 org.springframework.boot.SpringApplication.prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 应用初始化器
    applyInitializers(context);

}

applyInitializers方法源码:

// 源码位置 org.springframework.boot.SpringApplication#applyInitializers
protected void applyInitializers(ConfigurableApplicationContext context) {
	/**
	 * getInitializers()获取系统初始化器,并且对系统系统初始化器再次排序,解决了SecondInitializer的排序问题
	 * 循环系统初始化器,并执行系统初始化器的initialize方法
	 */
	for (ApplicationContextInitializer initializer : getInitializers()) {
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
				ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		initializer.initialize(context);
	}
}

1、getInitializers()获取系统初始化器,并且对系统系统初始化器再次排序

// 源码位置 org.springframework.boot.SpringApplication#asUnmodifiableOrderedSet 
private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
	List<E> list = new ArrayList<>(elements);
	// 再次对系统初始化器集合排序
	list.sort(AnnotationAwareOrderComparator.INSTANCE);
	return new LinkedHashSet<>(list);
}

2、getInitializers()获得系统初始化器集合,排在第一位的系统初始化器是DelegatingApplicationContextInitializer(委派系统初始化器,Order值是0),它会将初始化操作委派给环境属性context.initializer.classes指定的初始化器。

DelegatingApplicationContextInitializer.initialize方法源码如下

// 源码位置org.springframework.boot.context.config.DelegatingApplicationContextInitializer#initialize
public void initialize(ConfigurableApplicationContext context) {
	// 获取环境变量
	ConfigurableEnvironment environment = context.getEnvironment();
	// 获取context.initializer.classes配置的初始化器集合
	List<Class<?>> initializerClasses = getInitializerClasses(environment);
	if (!initializerClasses.isEmpty()) {
		// 执行初始化器的initialize方法
		applyInitializerClasses(context, initializerClasses);
	}
}

2.1  getInitializerClasses(environment); 源码分析

// 源码位置 org.springframework.boot.context.config.DelegatingApplicationContextInitializer#getInitializerClasses
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
	// PROPERTY_NAME = "context.initializer.classes"
	String classNames = env.getProperty(PROPERTY_NAME);
	List<Class<?>> classes = new ArrayList<>();
	if (StringUtils.hasLength(classNames)) {
		// 通过","分隔context.initializer.classes的值
		for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
			// getInitializerClass(className)使用AppClassLoader加载类
			classes.add(getInitializerClass(className));
		}
	}
	return classes;
}

2.2、applyInitializerClasses(context, initializerClasses);源码分析

// 源码位置 org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializerClasses
private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
	Class<?> contextClass = context.getClass();
	List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
	// initializerClasses是context.initializer.classes指定的类集合
	for (Class<?> initializerClass : initializerClasses) {
		// 实例化系统初始化器
		initializers.add(instantiateInitializer(contextClass, initializerClass));
	}
	// 应用系统初始化器
	applyInitializers(context, initializers);
}

2.2.1 applyInitializers(context, initializers); 源码分析

// 源码位置 org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializers
private void applyInitializers(ConfigurableApplicationContext context,
		List<ApplicationContextInitializer<?>> initializers) {
	// 先排序,这里是对context.initializer.classes配置的系统初始化器进行排序
	initializers.sort(new AnnotationAwareOrderComparator());
	for (ApplicationContextInitializer initializer : initializers) {
		// 执行初始化器的initialize方法,会进入 ThirdInitializer#initialize 方法中
		initializer.initialize(context);
	}
}

ThirdInitializer就被调用了。现在对DelegatingApplicationContextInitializer(委派系统初始化器)做个总结:

1、DelegatingApplicationContextInitializer的排序值是0,比FirstInitializer、SecondInitializer的排序值小,最先被调用。

2、DelegatingApplicationContextInitializer实例化context.initializer.classes配置的系统初始化器,并运行这些系统初始化器的initialize方法。所以context.initializer.classes配置的系统初始化器会先运行。

3、DelegatingApplicationContextInitializer的排序值是0,若把FirstInitializer的排序值设置为小于0,则FirstInitializer会先运行。

讲完了ThirdInitializer的实例化、运行过程。在回到 org.springframework.boot.SpringApplication#applyInitializers方法中,继续讲解FirstInitializer、FirstOrderedInitializer、SecondInitializer的运行过程

// 源码位置 org.springframework.boot.SpringApplication#applyInitializers
protected void applyInitializers(ConfigurableApplicationContext context) {
    /**
     * 循环处理getInitializers()返回的系统初始化器,
     * 排在第一位的是DelegatingApplicationContextInitializer,前面已经讲了此初始化器的运行过程
     * 后面几次循环获得的initializer包含FirstInitializer、FirstOrderedInitializer、SecondInitializer
     * initializer.initialize(context);就直接运行我们自定义的系统初始化器了
     */
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

好了,spring boot 2系统初始化器的使用与源码都讲完了,后面我会继续更新 spring boot 源码系列 ,欢迎大家来一起交流。

发布了51 篇原创文章 · 获赞 14 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u010606397/article/details/105103629