• springboot源码解析(二)-从源码角度分析系统初始化器的实现原理


    本篇文章是承接上一篇文章写的:springboot源码解析(一)-自定义系统初始化器
    开篇之前先把祖师爷搬出来
      费玉清:问大家一个脑筋急转弯,大家都知道泰坦尼克号的男主角叫杰克,女主角叫螺丝,那问大家泰坦尼克号为什么沉了
          思考。。。
          思考。。。
          思考。。。
      揭晓谜底:因为杰克把螺丝搞松了,所以漏水了,最后船就沉了
      反正谜底我已经揭晓了,至于大家能不能看到,我就不管了,哈哈

    概述

      上一篇文章介绍了3中方式加载系统初始化器,在文章中我有提到了好多个问题,但是并没有解答,因为如果每个问题都解答的话,篇幅会很长,所以这里单独写一篇文章从源码角度分析系统初始化器。

    第一种方式:使用META-INF/spring.factories

    下面就跟一下程序,看看springboot在启动过程中,在哪里调用了这个文件以及在哪里执行了系统初始化器。

    1.springboot原始启动程序

    @SpringBootApplication
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }

    2.进入run方法

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
            return run(new Class<?>[] { primarySource }, args);
        }

    3.进入run方法

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
            return new SpringApplication(primarySources).run(args);
        }

    4.注意这一步,这一步new SpringApplication,把SpringApplication给初始化了,下面看一下这个构造方法

    public SpringApplication(Class<?>... primarySources) {
            this(null, primarySources);
        }

    5.进入this中

    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();
            //初始化initializers属性,这个就和系统初始化器相关了
            setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
            //初始化listener属性
            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
            //这里就是即将运行main的类Class
            this.mainApplicationClass = deduceMainApplicationClass();
        }

    6.setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));这一步就是和系统初始化器相关了,我们先看外层的

    private List<ApplicationContextInitializer<?>> initializers;

    public
    void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) { this.initializers = new ArrayList<>(initializers); }

    这一步中的initializers,这个list就是存放实例化好的系统初始化的列表。

    6.1.看一下内层getSpringFactoriesInstances(ApplicationContextInitializer.class)方法,这个就是加载META-INF/spring.factories的方法,并且会把加载的系统初始化器给实例化

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

    6.2.继续进入

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
            ClassLoader classLoader = getClassLoader();
            // Use names and ensure unique to protect against duplicates
            //<7.1>从META-INF/sprig-factores读取类的信息放入到集合中
            Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            //<7.2>创建对象
            List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
            //<7.3>为对象排序
            AnnotationAwareOrderComparator.sort(instances);
            return instances;
        }

    7.1.代码中的注视标为7.1的地方SpringFactoriesLoader.loadFactoryNames()这个方法就是加载META-INF/spring-factories的地方

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
            String factoryTypeName = factoryType.getName();
         //<7.1.1>
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }

    7.1.1.进入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
                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);
                     //将从spring.factories中解析出来的resource转位Properties
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String factoryTypeName = ((String) entry.getKey()).trim();
                        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            //将结果放入到map中,其中key配置文件中等号左边的,value是一个字符串,因为配置文件中可以配置多个,用逗号分割,这里的map并不是普通的hashmap,而是linkMultiValueMap
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }    

    7.1.2.在上面代码中注视7.1.1的地方有一个getOrDefault(factoryTypeName, Collections.emptyList()),这里的factoryTypeName就是org.springframework.context.ApplicationContextInitializer,这段代码的意思是从map中把key是这个的value拿出来返回,我们debug模式看一下。

    从debug模式可以看到,我们的FirstApplicationContextInitializer在其中,下面的一些是springboot启动中系统自带的一些初始化器。

    ok,到此为止,我们已经知道在哪里加载spring.factories了,其实在6.2中还有两部,我们只分析7.2,剩下的排序不做分析了。

    7.2.List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);实例化类加载器

    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
            List<T> instances = new ArrayList(names.size());
         //获取系统初始化器集合的迭代器
         Iterator var7 = names.iterator();
    
            while(var7.hasNext()) {
                String name = (String)var7.next();
    
                try {
                    Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                    Assert.isAssignable(type, instanceClass);
              //获取系统初始化器构造方法
                    Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
              //实例化系统初始化器
                    T instance = BeanUtils.instantiateClass(constructor, args);
              //将实例化的结果放入列表中,最后返回
                    instances.add(instance);
                } catch (Throwable var12) {
                    throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
                }
            }
    
            return instances;
        }

    我们debug跟一下,看看这个instances中有什么

     

    第一个就是我们自己定义的系统初始化器,已经实例化成功了。

    现在我们在回到第5步,就是setInitializers(),这个方法会给List<ApplicationContextInitializer<?>> initializers;这个list赋值,也就是说这个里面存放的就是实例化好的系统初始化器。

    ok,到目前为止,我们已经知道怎么获取系统初始化器,怎么实例化,实例化之后存放在哪里,那另一个问题来,我们实例化好的系统初始化器在哪里被调用?这个在上篇文章中有讲到,在ConfigurableApplicationContext这个上下文刷新之前被调用,好那我们就找一下他是在哪里刷新的。


    在上面的第3步中,我们分析了SpringApplication的构造方法,但是没有分析run方法,下面接着上面分析run方法

    public ConfigurableApplicationContext run(String... args) {
            // <1> 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
            StopWatch stopWatch = new StopWatch();
            //设置一个开始时间
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
            //陪你handless属性,设置java.awt.headless属性为true就是告诉计算机可以在没有键盘鼠标的模式下工作
            configureHeadlessProperty();
            // 获得 SpringApplicationRunListener 的数组,并启动监听
            /**具体:http://svip.iocoder.cn/Spring-Boot/SpringApplication/#*/
            SpringApplicationRunListeners listeners = getRunListeners(args);
            listeners.starting();
            try {
                //创建  ApplicationArguments 对象
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                //加载属性配置。执行完成后,所有的 environment 的属性都会加载进来,包括 application.properties 和外部的属性配置。
                /**具体看:http://svip.iocoder.cn/Spring-Boot/SpringApplication/#*/
                ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                configureIgnoreBeanInfo(environment);
                //打印Spring banner,这玩意其实就是个字符串,之后打印出来,没什么好神奇的
                Banner printedBanner = printBanner(environment);
                //创建spring容器,根据不同的web类型创建不同的容器
                context = createApplicationContext();
                //异常报告
                exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);
                //<A2>主要是调用所有初始化类的 initialize 方法
                prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                //初始化spring容器
                refreshContext(context);
                //执行 Spring 容器的初始化的后置逻辑。默认实现为空。
                afterRefresh(context, applicationArguments);
                //记录结束时间
                stopWatch.stop();
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                }
                //通知 SpringApplicationRunListener 的数组,Spring 容器启动完成。
                listeners.started(context);
                //调用 ApplicationRunner 或者 CommandLineRunner 的运行方法。这个就是在run运行结束之后就会运行这个
                /**
                 *  @Component
                 * public class JDDRunner implements ApplicationRunner {
                 *     @Override
                 *     public void run(ApplicationArguments args) throws Exception {
                 *         System.out.println(args);
                 *         System.out.println("这个是测试ApplicationRunner接口");
                 *     }
                 * }
                */
                callRunners(context, applicationArguments);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, listeners);
                throw new IllegalStateException(ex);
            }
    
            try {
                //通知 SpringApplicationRunListener 的数组,Spring 容器运行中。
                listeners.running(context);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, null);
                throw new IllegalStateException(ex);
            }
            return context;
        }

    上面的代码一大坨,其他的先不要管,先看我注释中标红的地方,为了和上面的编号区分,下面的编号都会在前面加一个A,以作为区分。

    A2.prepareContext(context, environment, listeners, applicationArguments, printedBanner); 刷新前的准备阶段,我们进入看看

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
            //设置context的环境属性
            context.setEnvironment(environment);
            //设置context的一些资源加载器等属性
            postProcessApplicationContext(context);
            //<A3>调用ApplicationContextInitializer的initializer方法,这个可以自定义,这个是springApplication初始化把这个
            //ApplicationContextInitializer加进去的
            applyInitializers(context);
            //通知监听器,Spring容器准备完成
            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);
            }
            if (this.lazyInitialization) {
                context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
            }
            // Load the sources
            //加载 BeanDefinition
            Set<Object> sources = getAllSources();
            Assert.notEmpty(sources, "Sources must not be empty");
            //看这个意思是把所有的bean加载到context中,但是不确定,debug一下
            load(context, sources.toArray(new Object[0]));
            //通知监听器,加载完成
            listeners.contextLoaded(context);
        }

    上面的代码仍然是一大坨,还是老规矩,别的不要管,先看标红的注释applyInitializers(context);这一行就是执行系统初始化器的地方

    A3.进入applyInitializers看看

    protected void applyInitializers(ConfigurableApplicationContext context) {
    //这个getInitializers就是获取在上面已经搞好的那个存放系统初始化器实例的列表        
    for (ApplicationContextInitializer initializer : getInitializers()) {
                Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                        ApplicationContextInitializer.class);
                Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
           //还记得上一篇中接口的定义吗,里面只有一个方法,就是initialize()方法,在这里就是执行了自定义方法
                initializer.initialize(context);
            }
        }

    ok,至此,终于分析完了spring-factories中定义的系统初始化器的从加载到执行的全部过程,mmp,憋的膀胱都把肚子撑大了,我都怀疑我肚子变大都是尿憋的,我去放松一下,下面会继续讲第二种和第三种执行方式的原理,不过有了第一种实现方式的基础,后面两种的原理就很简单了。

            

    第二种方式:通过直接添加的方式

    @SpringBootApplication
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication application = new SpringApplication(DemoApplication.class);
            application.addInitializers(new SecondApplicationContextInitializer());
            application.run(args);
        }
    }

    上面的代码中application.addInitializers(new SecondApplicationContextInitializer());我们进入这个方法

    public void addInitializers(ApplicationContextInitializer<?>... initializers) {
            this.initializers.addAll(Arrays.asList(initializers));
        }

    可以看到,这里其实就是直接实例化我们的系统初始化器,然后放入到initializers列表中,通过在第一种实现方式中的分析可以知道,通过spring-factories的方式其实也是把系统初始化器先实例化放到这个集合中,之后在调用applyInitializers()方法进行执行,在上一篇文章中这里有一个问题就是为什么通过第一种方式注入的系统初始化器先执行,通过第二种方式注入的后执行,原因现在大家应该明白了,因为通过第一种方式在SpringApplication初始化的时候已经把系统初始化器放入到了initializers这个list中,而通过第二种方式需要SpringApplication初始化完成之后,在调用addInitializers()方法才可以添加进去,自然就是first先执行了。

    第三种方式:通过application.properties中配置

    这个的加载过程就比较隐蔽了,不容易看到,我们先找一下springboot的jar中的spring.factories,如下:

    就是第二个啦,然后我们点进入看看里面有什么。

    B0.可以发现springboot系统自定义的系统初始化器中有一个DelegatingApplicationContextInitializer,我们进入这个类中看看

    public class DelegatingApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
        private static final String PROPERTY_NAME = "context.initializer.classes";
        private int order = 0;
    
        public DelegatingApplicationContextInitializer() {
        }
      //当这个系统初始化器开始执行的时候,会执行这个方法
        public void initialize(ConfigurableApplicationContext context) {
            ConfigurableEnvironment environment = context.getEnvironment();
         //<B1>这里就是获取配置文件中配置的系统初始化器 List
    <Class<?>> initializerClasses = this.getInitializerClasses(environment); if (!initializerClasses.isEmpty()) {
            //<B2>
    this.applyInitializerClasses(context, initializerClasses); } }

    B1.我们进入B1处,this.getInitializerClasses(environment);

    private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
    //这里就是从环境中获取系统初始化器,和我们上一篇文章中在application.properties中写法是一致的     
    String classNames = env.getProperty("context.initializer.classes");
            List<Class<?>> classes = new ArrayList();
            if (StringUtils.hasLength(classNames)) {
                String[] var4 = StringUtils.tokenizeToStringArray(classNames, ",");
                int var5 = var4.length;
    
                for(int var6 = 0; var6 < var5; ++var6) {
                    String className = var4[var6];
                    classes.add(this.getInitializerClass(className));
                }
            }

    B2.看一下applyInitializerClasses(context, initializerClasses);做了什么

    private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
            Class<?> contextClass = context.getClass();
            List<ApplicationContextInitializer<?>> initializers = new ArrayList();
            Iterator var5 = initializerClasses.iterator();
    
            while(var5.hasNext()) {
                Class<?> initializerClass = (Class)var5.next();
    //这里就是先把上面后去的系统初始化器的类进行实例化,然后放入到一个list中,这里注意,这个initializers列表并不是之前的那个列表,而是新建的,为了防止下面执行的时候重复执行
                initializers.add(this.instantiateInitializer(contextClass, initializerClass));
            }
    //有了实例化的系统初始化器,这里就是执行了,和之前分析的步骤是一致的,这里就不重复分析了
            this.applyInitializers(context, initializers);
        }

    现在,我们来解答我们上一篇文章中的另一个疑问,就是为什么这三种方式,第三种方式最先执行,大家可以看到在B0步中,里面有一个order = 0,0的级别是最高的,也就是说springboot系统自带的初始化器是最先执行的,那当系统的初始化器执行的时候,自然会把配置文件中的初始化器给执行了,所以就是第三种方式最先执行。那我估计有些朋友就有疑问了,什么疑问呢?DelegatingApplicationContextInitializer这个初始化器并没有使用@Order注解修饰,这个类里面定义的Order=0,如何可以生效呢?大家可以观察一下这个类实现的方法,他不只实现了ApplicationContextInitializer接口,他还实现了Odered接口,这个接口就是关键了,下面看一下这个接口:

    public interface Ordered {
        int HIGHEST_PRECEDENCE = -2147483648;
        int LOWEST_PRECEDENCE = 2147483647;
    
        int getOrder();
    }

    这个接口中有一个getOrder()方法,这个方法会获取order的值,在DelegatingApplicationContextInitializer中有实现,那获取到这个值之后如何根据这个值排序呢?这个就是上面6.2中AnnotationAwareOrderComparator.sort(instances);这一步进行排序的,这一步上面说不分析了,其实这玩意确实有一点复杂,我打算再写一篇文章分析这个,不然这文章会写的越来越长。

    总结

    上面已经从源码的层面分析了3种方式的原理,很多疑问也都解开了,总的来说这一部分的源码并不是特别难,耐心一点,应该没什么问题。下一篇文章我会分析这篇文章的一个遗留问题,就是系统初始化器是怎么排序的。

  • 相关阅读:
    正则表达式的三种模式【贪婪、勉强、侵占】的分析
    php实用的文件上传类
    php简单实用的验证码生成类
    phpstorm不安装apache就可以本地测试PHP
    Ajax技术——带进度条的文件上传
    Mybatis 多表查询及查询结果映射
    关于textarea包在div的自适应问题
    Luogu P3200 [HNOI2009]有趣的数列
    群&置换群&burnside
    卡特兰树
  • 原文地址:https://www.cnblogs.com/gunduzi/p/12988120.html
Copyright © 2020-2023  润新知