Spring Boot启动流程

Spring Boot的启动非常简单,就是一行代码并且很强大,于是我就花了些时间研究了一下这行代码都做了些什么事情,并记录下来。下面是我绘制的一张启动流程图:
image.png

Spring Boot启动方法的改造

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

Spring Boot的启动的这行代码,内部就是执行了SpringApplication的一个静态方法run。进入这个run方法:

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

当调用SpringApplication.run方法后,该方法会继续调用第2个run方法

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

发现在第2个run方法中new了一个新的SpringApplication对象,并调用了run方法,传入args参数。也就意味着这一行代码可以改写成下面这个样子:

public static void main(String[] args) {
    //SpringApplication.run(DILoopApplication.class, args);
    SpringApplication sbapp = new SpringApplication(DILoopApplication.class);
    ConfigurableApplicationContext context = sbapp.run(args);
}

SpringApplication的构造函数

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();
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

构造函数代码比较少,我们来一行一行的分析 
1:WebApplicationType
构造函数中通过WebApplicationType.deduceFromClasspath()来获取这个变量。然后看下他的定义

public enum WebApplicationType {
   NONE,
   SERVLET,
   REACTIVE;
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
      "org.springframework.web.context.ConfigurableWebApplicationContext" };

private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
      + "web.servlet.DispatcherServlet";

private static final String WEBFLUX_INDICATOR_CLASS = "org."
      + "springframework.web.reactive.DispatcherHandler";

private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

WebApplicationType其实就是一个枚举,定义了三个应用类型:
• NONE:不作为web应用启动,依赖外部web server。
• SERVLET:基于servlet的web应用启动, 使用内嵌 web server。
• REACTIVE:响应式web应用启动,使用内嵌的响应式web server。
然后来看下Spring boot的推断过程

static WebApplicationType deduceFromClasspath() {
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
   }
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
   return WebApplicationType.SERVLET;
}

ClassUtils.isPresent主要工作就是通过传入的类名,在应用程序里反射,如果反射成功返回true, 反射失败返回 false
Case 1: class path 中存在类org.springframework.web.reactive.DispatcherHandler同时不存在类org.springframework.web.servlet.DispatcherServlet和org.glassfish.jersey.servlet.ServletContainer 那应用就是REACTIVE类型
Case 2: 在class path 中同时存在javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext那应用就是SERVLET类型
CASE 3: 既不属于case1, 也不属于case2那应用就是NONE类型
2:setInitializers

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

这个函数很简单就是把将getSpringFactoriesInstances的返回结果全部添加到成员变量initializers数组中。
下面主要来看getSpringFactoriesInstances函数,他的主要功能就是扫描应用程序的所有jar包,然后读取jar中meta-inf/factory.properties文件中配置的ApplicationContextInitializer类,加载到内存中,并反射创建出具体对象。

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();
   // Use names and ensure unique to protect against duplicates
   Set<String> names = new LinkedHashSet<>(
         SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
         classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

getSpringFactoriesInstances函数中又调用了SpringFactoriesLoader.loadFactoryNames,
这个函数的功能就是遍历所有的jar包并扫描jar下的factory.properties文件。然后解析文件中的所有的properties,并保存到缓冲中。函数loadSpringFactoires执行完后会返回一个key-value的map,然后spring boot又通过getOrDefault函数来过滤了一遍返回值(只获取key是ApplicationContextInitializer类的value)
<font color=red>后面会写一个小case来说明这个getOrDefault的功能。</font>
下面是loadFactory的部分源代码:

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

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()) {

                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {

                    for(int var11 = 0; var11 < var10; ++var11) {
                        

                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
            return result;

    }
}

然后再回到getSpringFactoriesInstances,看一下他里面调用的createSpringFactoriesInstances方法,这个函数功能很简单就是,将上一步获取到的所有类全名,进行创建并load到内存中。( 循环names集合,先进行forName加载获取反射出class对象,然后再调用他的构造函数初始化对象信息。)
下面是部分源代码:

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
      Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
      Set<String> names) {
   for (String name : names) {
      try {
         Class<?> instanceClass = ClassUtils.forName(name, classLoader);
         
         Constructor<?> constructor = instanceClass
               .getDeclaredConstructor(parameterTypes);
         T instance = (T) BeanUtils.instantiateClass(constructor, args);
      }
      catch (Throwable ex) {}
      }
   }
}

3:setListeners

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
   this.listeners = new ArrayList<>();
   this.listeners.addAll(listeners);
}

这个函数很简单就是把将getSpringFactoriesInstances的返回结果全部添加到成员变量listeners数组中。
setListeners中调用的getSpringFactoriesInstances流程和setInitializers时执行逻辑是一样的,只是传入的参数不一样了,setListeners是传入的参数是ApplicationListener
deduceMainApplicationClass
最后一步就是创建springboot的main类了:

private Class<?> deduceMainApplicationClass() {
   try {
      StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
      for (StackTraceElement stackTraceElement : stackTrace) {
         if ("main".equals(stackTraceElement.getMethodName())) {
            return Class.forName(stackTraceElement.getClassName());
         }
      }
   }
   catch (ClassNotFoundException ex) {
      // Swallow and continue
   }
   return null;
}

getOrDefault(Object key, V defaultValue)

当map中存在该key时,返回该key对应的value,如果map中不存在该key时,则返回defaultValue

Map<String, Integer> name_age = new HashMap<>();
name_age.put("qingfen", 16);
name_age.put("lantian", 17);
name_age.put("baiyun", 18);

int mm = name_age.getOrDefault("qingfen", 99);
int mm1 = name_age.getOrDefault("lantian2", 99);

System.out.println("在map中找到数据,getOrDefault返回的数据" + mm);
System.out.println("在map中找不到数据,getOrDefault返回的数据" + mm1);

Map中可以查询到qingfen,所以返回qingfen对应 的Value16,但在查询不到lantian2,所以返回给定的默认值99。


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

推荐阅读更多精彩内容

  • Spring Boot的启动函数如下所示 以上代码很容易看出哪些是关键,当然是@SpringBootApplica...
    hTangle阅读 1,658评论 0 1
  • Spring Boot 启动流程 [TOC] Spring Boot 的程序启动于 SpringApplicati...
    RojerAlone阅读 329评论 0 0
  • 背景 从大学接触Spring 3.0到现在已经有5、6年的时间了,但工作后由于公司有自研的系统,一直没有机会在实际...
    王勇1024阅读 2,785评论 0 7
  • 入口是一个main方法,这个main方法里面new一个SpringApplication对象,传入bean源,就是...
    来搞事情阅读 9,528评论 0 2
  • spring boot 的启动类: 启动一个spring boot 程序很简单,只需要一个main 函数就够了,那...
    wing_yeah阅读 449评论 0 0