美文网首页
spring boot启动分析(二)

spring boot启动分析(二)

作者: 非典型_程序员 | 来源:发表于2018-12-01 21:16 被阅读0次

    上次简单的学习了一下spring boot的整个启动的流程,今天主要详细的学习一下具体每一步做了什么事情!今天主要是学习一下创建SpringApplication实例时比较重要的两步,即initializers和listeners的两个变量的创建过程和它们的作用。开始之前首先改正一个错误,在上次学习spring boot启动分析时,initializers是获取ApplicationContextInitializer其实现类的所有名字,listeners是获取ApplicationListener的所有实现类名称,然后通过反射去创建实例。今天看源码的时候发现我是错的,很尴尬,接下来仔细看一下到底是怎么样一个过程吧!!

    一、initializers的初始化

    在SpringApplication的构造方法里面是通过set方法完成initializers注入的:

    A  this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    B  private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
            return this.getSpringFactoriesInstances(type, new Class[0]);
        }
    C  private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
       a    Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
       b    List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
            AnnotationAwareOrderComparator.sort(instances);
            return instances;
        }
    a  public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
       }
    b  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;
        }  
    
    

    上面的方法是initializers完成注入的一个整体流程,A获取到所有initializers实例完成initializers的初始化,它调用的是B方法,且入参为ApplicationContextInitializer的类对象,而后B方法调用了C方法,入参为ApplicationContextInitializer的类对象和一个大小为0的Class数组。关键是C方法,C方法里面又分为a和b方法,a方法是获取所有initializer类的名称,b方法是根据类名成创建出initializer实例(b方法是一个通用方法,创建listener实例也是通过它完成的),另外在C方法里面会对所有的initializer进行排序。

    1、获取相关initializer类的名称

    先来看下a方法,a方法new了一个HashSet用户存储所有initializer类的名称,而这些类的名称则来自下面的方法:

    A  public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
           String factoryClassName = factoryClass.getName();
           return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
           // 可以简单的这样看:
           Map<String, List<String>> map = loadSpringFactories(classLoader);
           return (List)map.getOrDefault(factoryClassName, Collections.emptyList());
      }
    B  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();
    
                   while(urls.hasMoreElements()) {
                       URL url = (URL)urls.nextElement();
                       UrlResource resource = new UrlResource(url);
                       Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                       Iterator var6 = properties.entrySet().iterator();
    
                       while(var6.hasNext()) {
                           Entry<?, ?> entry = (Entry)var6.next();
                           List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                           result.addAll((String)entry.getKey(), factoryClassNames);
                       }
                   }
    
                   cache.put(classLoader, result);
                   return result;
               } catch (IOException var9) {
                   throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
               }
           }
      }
    

    上面的方法中A方法先根据ApplicationContextInitializer类对象,获取到它的类名成,即:

    org.springframework.context.ApplicationContextInitializer

    然后调用B方法,先试着从cache变量中根据类加载器作为key去获取相应的值(这是一个map变量,只是稍微复杂一点,它的类型是Map<ClassLoader, MultiValueMap<String, String>>),如果获取到相应的值,直接返回;否则才会去获取"META-INF/spring.factories",这个是一个URL的枚举,然后遍历该枚举,获取到到每一个"META-INF/spring.factories"配置文件,再遍历所有的配置文件,最后将对应的key(一些类的名称)和对应的value(一个或多个类名称,转成List<String>)添加到result中,然后会将这些值都放到cache变量中,避免再次使用时重新加载,最后返回结果。
    在方法A中在B方法返回结果的基础上调用getOrDefault方法,B方法返回结果是一个map,getOrDefault方法就是使用"org.springframework.context.ApplicationContextInitializer"作为key,获取所有对应的initializer的类的名称。所以上次说获取所有ApplicationContextInitializer实现类的名称是错误的,其实获取的是配置文件中指定的以"org.springframework.context.ApplicationContextInitializer"为key的所有类的名称。
    自己也查看了一下所有的"spring.factories"配置文件,主要是在3个jar包下面,分别是:

    spring-boot-2.0.6.RELEASE.jar
    spring-boot-autoconfigure-2.0.6.RELEASE.jar
    spring-beans-5.0.10.RELEASE.jar

    打开相应的配置文件看一下,就会看到相应的配置,因为配置还是比较多的,所以这里简单的截图看一下:


    spring-boot-jar下配置.png
    spring-boot-autoconfigure-jar下配置.png

    通过图片也可以很直观的看到以"org.springframework.context.ApplicationContextInitializer"为key的value值是多个相关的类名(当然这些类确实也都实现类ApplicationContextInitializer接口)。

    2、创建initializer类的实例

    方法a返回的是所有需要实列话的initializer类的名称集合,然后在b方法中,先根据类的名称和类加载器获取到对应的Class实例,然后通过反射获取对应类的构造方法,然后调应构造方法的newInstance方法创建出实列,最后将这些initializer返回,最终返回到A方法,并完成initializers变量的初始化,到这里initializers初始化就完成了。


    二、listeners的初始化

    listeners的初始化和initializer过程完全一样,只是接口类型不同而已,所以这里就不过多介绍了。


    三、initializer的作用

    因为程序启动的顺序的原因,initializer会在准备context阶段才执行,这里只说initializer的initialize方法执行,不包括一些内部类的相关方法。

    this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    
    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
          context.setEnvironment(environment);
          this.postProcessApplicationContext(context);
          // initializer执行
          this.applyInitializers(context);
          listeners.contextPrepared(context);
          if (this.logStartupInfo) {
              this.logStartupInfo(context.getParent() == null);
              this.logStartupProfileInfo(context);
          }
    
          context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
          if (printedBanner != null) {
              context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
          }
    
          Set<Object> sources = this.getAllSources();
          Assert.notEmpty(sources, "Sources must not be empty");
          this.load(context, sources.toArray(new Object[0]));
          listeners.contextLoaded(context);
    }
    
    

    initializer执行顺序不太一样,自己大概跟踪了一下每个initializer的执行过程,但是比较乱,所以我就单独的把每一个initializer拿出来,大概说一下它的作用,有的initializer还是非常复杂的。自己就直接根据相应类中的方法简单的介绍一下:
    1、org.springframework.boot.context.config.DelegatingApplicationContextInitializer
    用户自定义应用环境或者应用上下文时执行的一些初始化操作.
    2、org.springframework.boot.context.ContextIdApplicationContextInitializer
    主要作用获取当前应用名称,并将名称赋给applicationContext,最后将ContextIdApplicationContextInitializer.ContextId实例作为单例注入到applicationContext的beanFactory中。
    3、org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
    将ConfigurationWarningsPostProcessor实例注入到context的beanFactoryPostProcessors集合中。
    4、org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
    这是一个比较特殊的类,因为它同时实现了ApplicationContextInitializer接口和ApplicationListener接口
    它的initialize方法主要是将其本身注入到context的applicationListeners中去。
    而它的onApplicationEvent方法监听的则是WebServerInitializedEvent事件,获取web服务器的启动端口,然后添加到环境变量的propertySources中去。
    5、org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
    initialize方法将CachingMetadataReaderFactoryPostProcessor注入到context的beanFactoryPostProcessors
    集合中。
    此外它的另一个内部类SharedMetadataReaderFactoryBean的实现类的onApplicationEvent方法监听ContextRefreshedEvent事件,以清除缓存。
    6、org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    它的initialize方法主要是将context注入到其实例当中,并创建其内部类ConditionEvaluationReportListener的实例,添加到应用context的applicationListener集合中,最后是其report(类型ConditionEvaluationReport)的注入。
    其内部类ConditionEvaluationReportListener监听事件ContextRefreshedEvent。


    四、listener的作用

    listener的作用主要和其监听的事件有关系,一个监听器可能会监听多个事件,并根据事件类型做不同的操作,下面也是根据代码来推断每个监听器的大概功能:

    1、org.springframework.boot.context.config.ConfigFileApplicationListener
    监听事件ApplicationEnvironmentPreparedEvent和ApplicationPreparedEvent。
    在ApplicationEnvironmentPreparedEvent事件中,先以"org.springframework.boot.env.EnvironmentPostProcessor"作为key,获得"META-INF/spring.factories"配置文件夹下的所有类名(这里其实不需要再去加载配置文件的,因为在加载initializer时已经全部加载过了),然后和initializer一样,通过反射创建出三个EnvironmentPostProcessor实现类,然后循环调用它们以及ConfigFileApplicationListener实例的postProcessEnvironment方法。而ConfigFileApplicationListener实例,获取配置文件的加载器,最终会加载用户自定义的配置文件以及指定的配置文件。这个方法有点复杂,感兴趣可以耐心的看看。
    在ApplicationPreparedEvent事件中会创建一个内部类PropertySourceOrderingPostProcessor对象,然后添加到context的beanFactoryPostProcessors变量中。
    2、org.springframework.boot.context.config.AnsiOutputApplicationListener
    监听事件ApplicationEnvironmentPreparedEvent,并通过配置文件设置ANSI是否通过console输出。具体是什么我也不是很清楚。
    3、org.springframework.boot.context.logging.LoggingApplicationListener
    日志监听器,监听的事件有ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent、ContextClosedEvent和ApplicationFailedEvent。
    ApplicationStartingEvent阶段:日志系统的初始化之前的准备工作
    ApplicationEnvironmentPreparedEvent阶段:日志系统初始化,初始化日志级别等。
    ApplicationPreparedEvent阶段,把日志系统作为单列注入到bean工厂。这里主要是日志的一些内容,简单了解一下就好。
    4、org.springframework.boot.context.logging.ClasspathLoggingApplicationListener
    这个感觉也没什么用,判断日志级别是否为debug级别,是的话则输出监听事件信息,否则不做任何操作。监听事件包括ApplicationEnvironmentPreparedEvent和ApplicationFailedEvent。
    5、org.springframework.boot.autoconfigure.BackgroundPreinitializer
    监听事件ApplicationStartingEvent、ApplicationReadyEvent和ApplicationFailedEvent,
    主要是做一些准备工作,比如:字符集的设置、JSON和对象转换相关内容、http请求消息转换器以及tomcat中MBeanFactory的创建等等。
    6、org.springframework.boot.context.config.DelegatingApplicationListener
    监听事件ApplicationEnvironmentPreparedEvent作用和DelegatingApplicationContextInitializer类似,都是判断用户自定义的一些内容。 spring boot文档 有一些介绍,有兴趣可以看一下。
    7、org.springframework.boot.builder.ParentContextCloserApplicationListener
    主要监听的是ParentContextAvailableEvent事件,也就是父context可用时,创建一个ContextCloserListener实例,并注入到父context的监听器集合中,父context必须是"ConfigurableApplicationContext"的实例。
    8、org.springframework.boot.ClearCachesApplicationListener
    监听的ContextRefreshedEvent事件,目的就是清楚类加载器的缓存的方法和字段信息,当前类加载器的类对象通过方法名"clearCache"得到相应的Method对象,然后执行该方法,最后递归调用当前类加载器的父加载器执行以上内容。
    9、org.springframework.boot.context.FileEncodingApplicationListener
    监听事件ApplicationEnvironmentPreparedEvent,主要是查看环境中的propertySources有没有配置文件字符集,有的话和当前系统的文件字符集比较是否相等,不想等就日志打印错误信息,个人感觉没什么用。
    10、org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
    监听ApplicationStartingEvent事件,这个监听器具体做什么的我也不太清楚,跟踪源码发现是使用
    ApplicationStartingEvent事件springApplication对象的类加载器去尝试加载名字为"liquibase.servicelocator.CustomResolverServiceLocator"的类或者"liquibase.servicelocator"中子类名称为"CustomResolverServiceLocator"的类。如果没有加载不到就不进行任何操作,加载到则创建LiquibaseServiceLocatorApplicationListener的子类LiquibasePresent的实例,并执行其replaceServiceLocator方法。具体有什么用我也不清楚,我的项目是没有加载到相关的类。


    总结:

    以上就是initializer和listener的作用,当然这些都是spring boot在配置文件中指定的类的实例。然后单独的分析了每个initializer和listener的用途,实际项目启动过程中要比我们说的复杂的多。另外很多东西都不是上面介绍的那么简单,尤其像ConfigFileApplicationListener自己看了很久都还没有完全的看清整个过程。另外还有一些其他的内容,像beanFactory、postProcessors的等等有哪些作用,tomcat又是如何启动的,这些都是需要去了解的。本以为spring boot简单,但是实际上还是有太多的东西需要去了解,过程远比想象中艰难。只能后面一步步的继续去研究了。

    相关文章

      网友评论

          本文标题:spring boot启动分析(二)

          本文链接:https://www.haomeiwen.com/subject/dpnlcqtx.html