系统初始化器

一:系统初始化器介绍

  • 一:类名:ApplicationContextInitializer
  • 二:介绍:Spring容器刷新之前执行的一个回调函数
  • 三:作用:向SpringBoot容器中注册属性
  • 四:使用:继承接口自定义实现

使用示例(自定义系统初始化器):

第一种:spring.factories中配置
Firstinitializer类
package com.example.demo.initializer;

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 org.springframework.core.env.PropertySource;

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 map = new HashMap<>();
        map.put("key1","value1");
        PropertySource<?> propertySource = new MapPropertySource("firstinitializer",map);
        environment.getPropertySources().addLast(propertySource);
        System.out.println("run firstinitializer");
    }
}
resources目录下新建 : META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=com.example.demo.initializer.Firstinitializer
第二种:启动类中配置
SecondInitializer类
package com.example.demo.initializer;

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 org.springframework.core.env.PropertySource;

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 map = new HashMap<>();
        map.put("key2","value2");
        PropertySource<?> propertySource = new MapPropertySource("secondinitializer",map);
        environment.getPropertySources().addLast(propertySource);
        System.out.println("run secondinitializer");
    }
}
启动类
package com.example.demo;

import com.example.demo.initializer.SecondInitializer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

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

}
第三种:application.properties中配置
ThirdInitializer类
package com.example.demo.initializer;

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 org.springframework.core.env.PropertySource;

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 map = new HashMap<>();
        map.put("key3","value3");
        PropertySource<?> propertySource = new MapPropertySource("thirdinitializer",map);
        environment.getPropertySources().addLast(propertySource);
        System.out.println("run thirdinitializer");
    }
}
application.properties
context.initializer.classes=com.example.demo.initializer.ThirdInitializer
测试类
TestService
package com.example.demo.service;

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 test1(){
        return applicationContext.getEnvironment().getProperty("key1");
    }

    public String test2(){
        return applicationContext.getEnvironment().getProperty("key2");
    }

    public String test3(){
        return applicationContext.getEnvironment().getProperty("key3");
    }
}

tips
  1. 都要实现ApplicationContextInitializer接口
  2. Order值越小越先执行
  3. application.properties中定义的优先于其他方式

二:SpringFactoriesLoader介绍

源码中英文介绍
中文释义:

 * 框架内内部使用的通用工厂加载机制。
 *  * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories 加载} 并实例化
 * 来自 {@value #FACTORIES_RESOURCE_LOCATION} 文件的给定类型的工厂
 * 可能出现在类路径中的多个 JAR 文件中。 {@code spring.factories}
 * 文件必须是 {@link Properties} 格式,其中键是完全限定的
 * 接口或抽象类的名称,值是逗号分隔的列表
 * 实现类名。 例如:
 *  * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
 *  * 其中 {@code example.MyService} 是接口的名称,而 {@code MyServiceImpl1}
 * 和 {@code MyServiceImpl2} 是两个实现。
---------------------------------------------------------------------------------------------------------------------------------------------------
总结下就是
 * 一:框架内部使用的通用工厂加载机制
 * 二:从classpath下多个jar包特定的位置读取文件并初始化类
 * 三:文件内容必须是KV形式,即properties类型
 * 四:key是全限定名(抽象类|接口)、value是实现(多个实现用逗号分割)

探索什么时候进行的系统初始化器的加载

	//第一步
	SpringApplication.run(MystuflinkApplication.class, args);
 	//第二步
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
	//第三步	
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}
	//第四步
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}
	//第五步
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		/*......*/
		//通过getSpringFactoriesInstances找到所有初始化器,在通过setInitializers进行注册
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		/*......*/
	}
	//5.1
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}
	//5.2
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		//获取当前类加载器
		ClassLoader classLoader = getClassLoader();
		//通过ApplicationContextInitializer.class的实现类的全限定名称
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//依次对系统初始化器创建实例
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		//对实例进行排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}
	//5.3
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		//获取的类加载器
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		//获得ApplicationContextInitializer的类名
		String factoryTypeName = factoryType.getName();
		//loadSpringFactories获得所有的META-INF/spring.factories下的内容
		//getOrDefault方法通过上一步的内容进行筛选获取属于ApplicationContextInitializer子类的全限定名
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}
	//5.4
	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		//是否缓存过META-INF/spring.factories信息
		if (result != null) {
			return result;
		}
		result = new HashMap<>();
		try {
		    //初始加载,FACTORIES_RESOURCE_LOCATION=”META-INF/spring.factories“
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			//缓存起来
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

loadFactories流程图
在loadFactories流程图

三:系统初始化器原理解析

在这里插入图片描述

中文释义

* 用于初始化 Spring {@link ConfigurableApplicationContext} 的回调接口
  * 在 {@linkplain ConfigurableApplicationContext#refresh() 刷新}之前。
  *
  * <p>通常用于需要一些编程初始化的 Web 应用程序中
  * 应用程序上下文。 例如,注册财产来源或激活
  * 针对 {@linkplain ConfigurableApplicationContext#getEnvironment() 的配置文件
  * 上下文的环境}。 请参阅 {@code ContextLoader} 和 {@code FrameworkServlet} 支持
  * 分别用于声明“contextInitializerClasses”上下文参数和初始化参数。
  *
  * <p>{@code ApplicationContextInitializer} 鼓励处理器检测
  * Spring 的 {@link org.springframework.core.Ordered Ordered} 接口是否已经
  * 已实现或如果 {@link org.springframework.core.annotation.Order @Order}
  * 存在注释并在调用之前相应地对实例进行排序。

---------------------------------------------------------------------------------------------------------------------------------------------------
	总结下就是
	1、上下文刷新即refresh方法前被调用
	2、用来编码设置一些属性变量通常用在web环境中
	3、可以通过实现order接口进行排序

探索什么时候进行的系统初始化器的使用呢

	//1
	SpringApplication.run(MystuflinkApplication.class, args);
	//2
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
	//3
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}
	//4
	public ConfigurableApplicationContext run(String... args) {
		/*......*/
		try {
			/*......*/
			//此处是系统初始化器被调用的地方
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			//应用上下文刷新
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			/*......*/
		}
		catch (Throwable ex) {
			/*......*/
		}
		/*......*/
		return context;
	}
	//5
	private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
			/*......*/
			//开始调用
			applyInitializers(context);
			/*......*/
	}
	//6
	protected void applyInitializers(ConfigurableApplicationContext context) {
		//getInitializers方法拿到所有的系统初始化器
		for (ApplicationContextInitializer initializer : getInitializers()) {
			//获取泛型类型为ApplicationContextInitializer的初始化器
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			//执行调用
			initializer.initialize(context);
		}
	}

拓展

在第一栏目中的使用示例中有讲了三种系统初始化器的实现方式,以上讲解的是第一种(即:通过META-INF/spring.factories文件中配置的方式),另外两种都是第一种方式的拓展,下面来讲讲区别在哪里:

  1. 通过在启动类中配置
		SpringApplication springApplication = new SpringApplication();
        springApplication.addInitializers(...);
        springApplication.run();
		//这个比较好理解,因为在SpringApplication构造器中已经初始化好了initializers属性,这里直接进行add即可,然后通过调用run进行执行
  1. 通过application.properties文件配置
	//1 打开DelegatingApplicationContextInitializer类
	public class DelegatingApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
		/*......*/
		//因为order为0,所以也就解释了为什么通过application.properties配置的优先级更高了
		private int order = 0;
		/*......*/
		private static final String PROPERTY_NAME = "context.initializer.classes";
		/*......*/
	}
	//2 spring-boot-*.jar
	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
	//这个也很容易看出和第一步的加载逻辑其实是一样的
	//3执行逻辑,在SpringApplication的run方法内调用了prepareContext方法,prepareContext方法调用了applyInitializers方法,在applyInitializers内部执行initializer.initialize(context);即系统初始化器的调用
	//4  
	public void initialize(ConfigurableApplicationContext context) {
		ConfigurableEnvironment environment = context.getEnvironment();
		//获得 context.initializer.classes对应的一个或多个系统初始化器
		List<Class<?>> initializerClasses = getInitializerClasses(environment);
		if (!initializerClasses.isEmpty()) {
			//执行调用
			applyInitializerClasses(context, initializerClasses);
		}
	}
	//4.1  PROPERTY_NAME="context.initializer.classes";
	private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
	    //获取application.properties中context.initializer.classes的value值
		String classNames = env.getProperty(PROPERTY_NAME);
		List<Class<?>> classes = new ArrayList<>();
		if (StringUtils.hasLength(classNames)) {
		    //获得以逗号分割的全路径类名的数组,并循环
			for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
			    //在getInitializerClass方法通过forname方法获得Class对象
				classes.add(getInitializerClass(className));
			}
		}
		return classes;
	}
	//4.2
	private Class<?> getInitializerClass(String className) throws LinkageError {
		try {
			Class<?> initializerClass = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
			Assert.isAssignable(ApplicationContextInitializer.class, initializerClass);
			return initializerClass;
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
		}
	}
	//5  获系统初始化器的实例对象 并调用初始化方法
	private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
		Class<?> contextClass = context.getClass();
		List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
		for (Class<?> initializerClass : initializerClasses) {
			//instantiateInitializer 获系统初始化器的实例对象
			initializers.add(instantiateInitializer(contextClass, initializerClass));
		}
		//初始化方法调用
		applyInitializers(context, initializers);
	}
	//6 初始化方法调用
	private void applyInitializers(ConfigurableApplicationContext context,
			List<ApplicationContextInitializer<?>> initializers) {
		initializers.sort(new AnnotationAwareOrderComparator());
		for (ApplicationContextInitializer initializer : initializers) {
		   //initialize方法调用
			initializer.initialize(context);
		}
	}

四:总结

调用流程

调用流程

实现方式:

  1. 定义在spring.factories文件中被SpringFactoriesLoader发现注册
  2. SpringApplication初始化完毕后手动添加
  3. 通过application.properties定义成环境变量被DelegatingApplicationContextInitializer发现注册

回顾&面试

实现回顾

  1. org.springframework.context.ApplicationContextInitializer=com.wwy.myspringboot.initializer.FirstInitializer
  2. springApplication.addInitializers(…);
  3. context.initializer.classes=com.wwy.myspringboot.initializer.FirstInitializer

面试题

  1. 介绍下SpringFactoriesLoader?
  2. SpringFactoriesLoader如何加载工厂类?
  3. 系统初始化器的作用?
  4. 系统初始化器的调用时机?
  5. 如何自定义实现系统初始化器?
  6. 自定义实现系统初始化器有哪些注意事项?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值