【SpringBoot】容器启动


title: 【SpringBoot】容器启动
date: 2017-07-10 20:24:11
tags:

  • Java
  • Spring
    categories: Spring

Spring Boot 的启动前面说到了:

  • 包文件启动:从JarLaunchermain方法启动,加载各种资源后,开启一个新的线程调用程序的main方法
  • SpringApplication实例创建:判断是否是web环境,加载并实例化初始化器和监听器,查找main方法所在类
  • 启动监听器:创建SpringApplicationRunListeners实例统一管理监听器。在启动过程中调用不同容器生命周期通知,创建不同的事件类型,由ApplicationEventMulticaster广播对应事件给ApplicationListener

前面做了各种准备工作就是为了最后的容器提供服务,本文以注释的形式记录一下 Spring Boot 容器的启动过程:

    public ConfigurableApplicationContext run(String... args) {
        // ...
        try {
            // 封装应用启动参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            // 创建容器
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            // 准备容器
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            // 刷新容器
            refreshContext(context);
            // 运行runner
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            // ...
        }
    }

准备环境

    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // 创建环境
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 配置环境
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 监听器通知事件
        listeners.environmentPrepared(environment);
        // 处理非web环境
        if (isWebEnvironment(environment) && !this.webEnvironment) {
            environment = convertToStandardEnvironment(environment);
        }
        return environment;
    }

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        if (this.webEnvironment) {
            return new StandardServletEnvironment();
        }
        return new StandardEnvironment();
    }

    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
        // 配置property
        configurePropertySources(environment, args);
        // 配置profiles
        configureProfiles(environment, args);
    }

    public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";

    protected void configurePropertySources(ConfigurableEnvironment environment,
            String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        // 判断defaultProperties是否为空
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            // MutablePropertySources内部维护一个list<PropertySource<?>>集合
            // addLast先removeIfPresent()然后把defaultProperties加到最后
            sources.addLast(
                    new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        // 判断addCommandLineProperties ==true 和 args的长度
        if (this.addCommandLineProperties && args.length > 0) {
            // name=commandLineArgs
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                // 如果列表中包含,替换
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(new SimpleCommandLinePropertySource(
                        name + "-" + args.hashCode(), args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                // 如果列表中不包含
                // 将配置信息添加在最前面,优先执行
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        // spring.active.profile值
        environment.getActiveProfiles();
        // But these ones should go first (last wins in a property key clash)
        Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        // 将additionalProfiles和getActiveProfiles的值加到一起设置到环境中
        environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
    }

注释比较详细的说明了代码的作用了:

  1. 创建环境
  2. 配置环境
  3. 监听器通知环境准备完毕
  4. 对异常环境处理

上面代码注释跳过了web环境相关的配置,猜测里面会有servlet容器相关配置,这里跳过另开一文记录。

创建容器

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
         contextClass = Class.forName(this.webEnvironment
               ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
      }
      catch (ClassNotFoundException ex) {
         throw new IllegalStateException(
               "Unable create a default ApplicationContext, "
                     + "please specify an ApplicationContextClass",
               ex);
      }
   }
   return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

这部分没什么可说的,就是根据环境通过反射直接创建对应的容器的实例。

准备容器

private void prepareContext(ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   applyInitializers(context);
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }

   // Add boot specific singleton beans
   context.getBeanFactory().registerSingleton("springApplicationArguments",
         applicationArguments);
   if (printedBanner != null) {
      context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
   }

   // Load the sources
   Set<Object> sources = getSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[sources.size()]));
   listeners.contextLoaded(context);
}

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
   //  beanNameGenerator不为空,注册
   if (this.beanNameGenerator != null) {
      context.getBeanFactory().registerSingleton(
            AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
            this.beanNameGenerator);
   }
   // resourceLoader不为空,注册
   if (this.resourceLoader != null) {
      if (context instanceof GenericApplicationContext) {
         ((GenericApplicationContext) context)
               .setResourceLoader(this.resourceLoader);
      }
      if (context instanceof DefaultResourceLoader) {
         ((DefaultResourceLoader) context)
               .setClassLoader(this.resourceLoader.getClassLoader());
      }
   }
}

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);
   }
}

// 加载各种bean到容器中
protected void load(ApplicationContext context, Object[] sources) {
   if (logger.isDebugEnabled()) {
      logger.debug(
            "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
   }
   BeanDefinitionLoader loader = createBeanDefinitionLoader(
         getBeanDefinitionRegistry(context), sources);
   if (this.beanNameGenerator != null) {
      loader.setBeanNameGenerator(this.beanNameGenerator);
   }
   if (this.resourceLoader != null) {
      loader.setResourceLoader(this.resourceLoader);
   }
   if (this.environment != null) {
      loader.setEnvironment(this.environment);
   }
   loader.load();
}

容器的准备阶段的操作:

  1. 为容器设置环境信息
  2. 容器的预设置
  3. 生效初始化器
  4. 监听器通知容器准备完毕
  5. 加载各种bean到容器中
  6. 监听器通知容器加载完毕

刷新容器

private void refreshContext(ConfigurableApplicationContext context) {
   refresh(context);
   if (this.registerShutdownHook) {
      try {
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
}

protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
   ((AbstractApplicationContext) applicationContext).refresh();
}

最后这个((AbstractApplicationContext) applicationContext).refresh();就厉害了,终于把 Spring 容器整合到一起了,这里不详细分析,说起来话有点多,还是再开一篇文章介绍。

Runner

protected void afterRefresh(ConfigurableApplicationContext context,
      ApplicationArguments args) {
   callRunners(context, args);
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
   List<Object> runners = new ArrayList<Object>();
   // 找出Spring容器中ApplicationRunner接口的实现类
   runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
   // 找出Spring容器中CommandLineRunner接口的实现类
   runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
   AnnotationAwareOrderComparator.sort(runners);
   for (Object runner : new LinkedHashSet<Object>(runners)) {
      if (runner instanceof ApplicationRunner) {
         callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
         callRunner((CommandLineRunner) runner, args);
      }
   }
}

private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
   try {
      (runner).run(args);
   }
   catch (Exception ex) {
      throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
   }
}

private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
   try {
      (runner).run(args.getSourceArgs());
   }
   catch (Exception ex) {
      throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
   }
}

刷新完容器后,Spring 容器的启动就全部完成,最后这个afterRefresh方法是调用程序中的ApplicationRunnerCommandLineRunner,没有什么特别的。


总结一下,整个容器的启动步骤:

  1. 准备环境
    1. 创建环境
    2. 配置环境
    3. 对异常环境处理
  2. 创建容器
    1. 根据环境通过反射直接创建对应的容器的实例
  3. 刷新容器
    1. AbstractApplicationContext.refresh()
  4. 运行runner
    1. 调用程序中的ApplicationRunnerCommandLineRunner

监听器穿插在以上步骤中,根据容器的启动、运行状态广播对应的事件。

上述步骤当中忽略了两个关键的具体实现:

  1. 容器的创建,根据环境创建容器:

    web环境:AnnotationConfigEmbeddedWebApplicationContext

    非web环境:``AnnotationConfigApplicationContext`

  2. 容器的创建完成后的刷新

这两部分内容较多,挖坑以后填写。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,810评论 6 342
  • 1.1 spring IoC容器和beans的简介 Spring 框架的最核心基础的功能是IoC(控制反转)容器,...
    simoscode阅读 6,713评论 2 22
  • SpringBoot的启动很简单,代码如下: @SpringBootApplicationpublicclassM...
    sherlock_6981阅读 1,480评论 1 1
  • 飘在四月暖暖的雪, 该不是来告别。 洗涤塞满悲欢的心, 来不及让人白了头, 转眼寻它不见。 通往铺满花瓣的路, 该...
    文心学阅读 199评论 0 0