spring boot启动分析(二)

上次简单的学习了一下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简单,但是实际上还是有太多的东西需要去了解,过程远比想象中艰难。只能后面一步步的继续去研究了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,733评论 6 342
  • SpringBoot的启动很简单,代码如下: @SpringBootApplicationpublicclassM...
    sherlock_6981阅读 1,467评论 1 1
  • 陪这孩子读书,我是无条件陪练。既当学生又当“老师”。老师带引号,更多的角色是旧社会里那个拿“鞭子”的,督促才是...
    政坤奶奶阅读 534评论 0 6
  • 我对于大城市的感情和复杂,既向往它的繁华和便利,但又不满它的拥挤和喧嚣!索性我生活在一个离大城市南京很近的一个小...
    萌言曼语阅读 166评论 0 0