【SpringBoot深入理解源码】之自定义系统初始化器

有人辞官归故里
有人星夜赶科考
前途似海来日方长。

文章目录

  • 实现
      • 实现方式
      • 测试
      • 为什么ThirdInitializer第一个执行?
      • 注意
  • 如何被系统容器所识别并注册进去
      • SpringFactoriesLoader
      • 源码
      • SpringFactoriesLoader加载流程及作用

【SpringBoot深入理解源码】之自定义系统初始化器_第1张图片

实现

实现方式

  • 方式一(推荐)

实现ApplicationContextInitializer接口

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

spring.factories内填写接口实现
key值为org.springframework.context.ApplicationContextInitializer
【SpringBoot深入理解源码】之自定义系统初始化器_第2张图片

org.springframework.context.ApplicationContextInitializer=cn.test.initializer.FirstInitializer
  • 方式二

实现ApplicationContextInitializer接口

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

SpringApplication类初始化后设置进去(启动类)

@SpringBootApplication
public class Springboot2Application {

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

}
  • 方式三

实现ApplicationContextInitializer接口

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

application.properties配置文件内填写接口实现
key值为context.initializer.classes

context.initializer.classes=cn.test.initializer.ThirdInitializer

测试

  • 方式一
@Autowired
private Environment environment;

@RequestMapping("test2")
public String test2(){
    return environment.getProperty("key3");
}
  • 方式二
@Service
public class DemoService implements ApplicationContextAware {

    private ApplicationContext applicationContext;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }
    //调用test方法获取
    public String test(){
        return applicationContext.getEnvironment().getProperty("key3");
    }
}

【SpringBoot深入理解源码】之自定义系统初始化器_第3张图片

为什么ThirdInitializer第一个执行?

【SpringBoot深入理解源码】之自定义系统初始化器_第4张图片
这个DelegatingApplicationListener会在系统初始化的时候调用getListeners()方法加载properties配置文件的值。
【SpringBoot深入理解源码】之自定义系统初始化器_第5张图片
【SpringBoot深入理解源码】之自定义系统初始化器_第6张图片

注意

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

如何被系统容器所识别并注册进去

SpringFactoriesLoader

【SpringBoot深入理解源码】之自定义系统初始化器_第7张图片

  1. 框架内部使用的通用工厂加载机制
  2. 从classpath下的多个jar包特定的位置读取文件并初始化
  3. 文件内容必须是KV形式,即properties类型
  4. key是全限定名(抽象类 | 接口),value是实现,多个实现用逗号分隔

源码

从run方法进去
【SpringBoot深入理解源码】之自定义系统初始化器_第8张图片
在这里插入图片描述
获取SpringFactories实例化对象,参数是ApplicationContextInitializer
这也是我们注册系统初始化器必须的步骤
实现ApplicationContextInitializer接口
【SpringBoot深入理解源码】之自定义系统初始化器_第9张图片
加载FactoryName
【SpringBoot深入理解源码】之自定义系统初始化器_第10张图片
查缓存,缓存没有,则从文件读取,并加入缓存。

【SpringBoot深入理解源码】之自定义系统初始化器_第11张图片
【SpringBoot深入理解源码】之自定义系统初始化器_第12张图片
【SpringBoot深入理解源码】之自定义系统初始化器_第13张图片
【SpringBoot深入理解源码】之自定义系统初始化器_第14张图片

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	//使用名称并确保唯一以防止重复
	//加载FactoryName,结果保存到Set集合,确保结果唯一
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	//获取实例化对象
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	//按照Order排序,为了按照优先级加载
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

SpringFactoriesLoader加载流程及作用

Springboot框架中从类路径jar包中读取特定文件实现扩展类的载入。
【SpringBoot深入理解源码】之自定义系统初始化器_第15张图片

【SpringBoot深入理解源码】之自定义系统初始化器_第16张图片

文章持续更新,可以微信搜索「 绅堂Style 」第一时间阅读,回复【资料】有我准备的面试题笔记。
GitHub https://github.com/dtt11111/Nodes 有总结面试完整考点、资料以及我的系列文章。欢迎Star。
在这里插入图片描述

你可能感兴趣的:(SpringBoot源码)