『深入学习 Spring Boot』(二) 系统初始化器

前言

还记得上一节中,框架初始化过程中,涉及到一个比较复杂的方法就是,设置系统初始化器。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  // 资源加载器,null
        this.resourceLoader = resourceLoader;
  // 主类,一般是启动类
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  // 环境监测。判断启动的是 mvc 还是 webflux
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
  // 设置系统初始化器
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  // 设置监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  // 配置 main 函数所在的类
        this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码

所以,我们就从系统初始化器开始。

初始化器解析

官方描述:系统初始化器是Spring容器刷新之前执行的一个回调函数。

作用是向Spring Boot容器中注册属性,需要实现ApplicationContextInitializer接口。

设置初始化器

把系统初始化器设置到 Spring Boot 中,有三种方式。

方式一:spring.factories

定义在srping.factories中的类,会被 Spring Boot 容器加载。

  1. 创建初始化器

    @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("sb", "sb1");
            MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
            environment.getPropertySources().addLast(mapPropertySource);
            System.out.println("run firstInitializer");
        }
    }
    复制代码
  2. 配置spring.factories

    路径:src/main/resources/META-INF/spring.factories

    org.springframework.context.ApplicationContextInitializer=\
    com.example.indepthspringboot.initializer.FirstInitializer
    复制代码
  3. 启动

    image-20210507230439087

方式二:SpringApplication 直接设置

  1. 创建初始化器

    @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("sb2", "sb2");
            MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map);
            environment.getPropertySources().addLast(mapPropertySource);
            System.out.println("run secondInitializer");
        }
    }
    复制代码
  2. 配置

    public static void main(String[] args) {
        //SpringApplication.run(InDepthSpringBootApplication.class, args);
        SpringApplication springApplication = new SpringApplication(InDepthSpringBootApplication.class);
        springApplication.addInitializers(new SecondInitializer());
        springApplication.run(args);
    }
    复制代码
  3. 启动

    image-20210507230946892

方式三:通过配置文件

  1. 创建初始化器

    @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("sb3", "sb3");
            MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map);
            environment.getPropertySources().addLast(mapPropertySource);
            System.out.println("run thirdInitializer");
        }
    }
    复制代码
  2. 配置

    路径:src/main/resources/application.properties

    context.initializer.classes=com.example.indepthspringboot.initializer.ThirdInitializer
    复制代码
  3. 启动

    image-20210507231714912

初始化器加载原理

上面第三种方式,我们发现了非常奇怪的现象,我们定了@Order(3)ThirdInitializer却跑到了第一个输出。

接下来,我们以此为线,查看一下系统初始化器的加载过程。

方式一加载原理

  1. 设置系统初始化器

    设置系统初始化器,是在框架的初始化过程中。

    // 设置系统初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    复制代码
  2. 获取类的实例

    这里涉及到了,获取类实例的方法,比较复杂。但是也是 Spring Boot 中常用的方法,后面会经常见到。

    // 这个方法的作用是:获取指定类的实例private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {    return getSpringFactoriesInstances(type, new Class<?>[] {});} private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {        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;    }
    复制代码
  3. 获取类的实例核心逻辑

    我们跳转到 SpringFactoriesLoader.loadFactoryNames(type, classLoader)中。

    // SpringFactoriesLoader#loadFactoryNamespublic static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {    String factoryClassName = factoryClass.getName();    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());}
    复制代码

    这个方法的核心方法是调用 loadSpringFactories(classLoader),我们继续深入。

    对了,这里来先提一下spring.factories 的格式:

    org.springframework.context.ApplicationContextInitializer=\  com.example.indepthspringboot.initializer.FirstInitializer,\  com.example.indepthspringboot.initializer.SecondInitializer
    复制代码

    loadSpringFactories(classLoader) 比较长,但核心逻辑就是从META-INF/spring.factories中读取类的配置,加载到内存中。

    Map<String, List<String>> 中:

    key = org.springframework.context.ApplicationContextInitializervalue = ['com.example.indepthspringboot.initializer.FirstInitializer','com.example.indepthspringboot.initializer.SecondInitializer']

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);        if (result != null) {            return result;        } else {            try {                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");                LinkedMultiValueMap result = new LinkedMultiValueMap();                // …………                cache.put(classLoader, result);                return result;            } catch (IOException var13) {                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);            }        }    }
    复制代码
  4. 排序

    ok,核心逻辑梳理完了,我们拉回主线。

    // 这个方法的作用是:获取指定类的实例private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {    return getSpringFactoriesInstances(type, new Class<?>[] {});} private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {        ClassLoader classLoader = getClassLoader();        // 确保使用名称唯一      // 此时获取到了 META-INF/spring.factories 的配置,不过都是类全路径地址。        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));    // 通过类全路径地址,实例化类        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);    // 根据 @Order() 注解排序        AnnotationAwareOrderComparator.sort(instances);        return instances;    }
    复制代码

至此,我们就了解到方式一的加载方式了。

方式二加载原理

SpringApplication springApplication = new SpringApplication(InDepthSpringBootApplication.class);springApplication.addInitializers(new SecondInitializer());springApplication.run(args);
复制代码

这里我们可以看到,addInitializers()方式是在 SpringApplication初始化后调用的,添加到initializers 中的,所以肯定在FirstInitializer后面。

方式三加载原理

这一种加载方式比较特别,是通过order = 0DelegatingApplicationContextInitializer初始化器调用实现的。

也就是在 context.initializer.classes 属性指定的初始化器,由DelegatingApplicationContextInitializer调用完成。

public class DelegatingApplicationContextInitializer        implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {     // NOTE: Similar to org.springframework.web.context.ContextLoader     private static final String PROPERTY_NAME = "context.initializer.classes";     private int order = 0;   // 初始化器都会被调用initialize()方法  // 而 DelegatingApplicationContextInitializer#initialize(),又是去调用 "context.initializer.classes" 下的初始化器的initialize()方法    @Override    public void initialize(ConfigurableApplicationContext context) {        ConfigurableEnvironment environment = context.getEnvironment();        List<Class<?>> initializerClasses = getInitializerClasses(environment);        if (!initializerClasses.isEmpty()) {            applyInitializerClasses(context, initializerClasses);        }    }  // ……}
复制代码

这也就解释了,为什么ThirdInitializer会在第一位打印。这是因为,通过context.initializer.classes设置的初始化类,会在order = 0 的初始化类中被调用。

系统初始化器的调用

系统初始化器的调用是在,准备上下文方法中:SpringApplication # prepareContext() # 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);        }    }
复制代码

这里也就是遍历initializers属性,去调用initialize()方法了,无需赘述。

小结

这一节主要是,系统初始化器相关内容。

作为学习 Spring Boot 启动的第一篇细节文章,本章除了系统初始化器外,还有个重点,就是方式一加载原理中的类加载机制。

类加载机制,在后面都会频繁用到,后面就不再细述了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享