引言

早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧。今天让我们从spring boot启动开始,深入了解一下spring boot的工作原理。

为什么用spring boot

在使用一个东西或者一个工具之前,我们总是会问自己,我为什么要用?用他能给我带来什么好处?

* 最大的好处就是spring boot遵从了java**约定大于配置**不用面对一大堆的配置文件,spring boot是根据你用的包来决定提供什么配置。

* 服务器以jar包的形式内嵌于项目中,对于微服务满天飞的情况,spring boot天生适合微服务架构,方便部署。

* 提供devtools从此改代码就需重启成为历史。

有优点就一定有缺点,缺点来源于优点优点来源于缺点(感觉在说哲学问题了哈哈哈)

* 正因为配置对开发者不透明,不看源码会不清楚spring boot如何进行诸如JDBC加载、事务管理等,出现错误也很难调错。

* 自动配置之后要自定义配置需编码javaConfig,需要了解这些配置类api。

* 版本迭代太快,新版本对老版本改动太多导致不兼容,比如1.3.5之前的springBootTest和1.4.0之后的springBootTest。

只有合适的架构才是最好的架构如果能接受spring boot这些缺点,spring boot确实是一个可以提高开发效率的不错的选择。

启动流程

扯了这么多,该上正题了,让我们来看看spring boot是怎样启动和启动做了哪些事情。

以下代码是spring boot项目标准的启动方式,使用注解@SpringBootApplication并且在main方法中调用SpringApplication的run方法,就可以完成。我们就从这个run方法开始看看spring boot的启动过程。

1
2
3
4
5
6
@SpringBootApplication
public class Application {
  public static void main(String[] args){
    SpringApplication.run(Application.class,args);
  }
}

我们进入run方法,可以看到最终是调用了 new SpringApplication(sources).run(args);new SpringApplication(sources).run(args); 这个方法,可以看到,springBoot的启动可以分为两个部分,第一部分:SpringApplication的实例化;第二部分:调用该实例运行run方法。我们先来看看这个SpringApplication的实例化过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
      this.sources.addAll(Arrays.asList(sources));
    }
    //判定是否为webEnvironment
    this.webEnvironment = deduceWebEnvironment();
    //实例化并加载所有可以加载的ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    //实例化并加载所有可以加载的ApplicationListener
    setListeners((Collection)
    getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
  }

关键点在两个set方法上**

setInitializers((Collection) getSpringFactoriesInstances(

ApplicationContextInitializer.class))** 和 **setListeners((Collection)

getSpringFactoriesInstances(ApplicationListener.class))** 这两个方法一毛一样,挑实例化ApplicationContextInitializer讲一讲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
      Class<?>[] parameterTypes, Object... args) {
      //拿到类加载器
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    //使用loadFactoryNames方法载入所有的ApplicationContextInitializer的类全限定名
    Set<String> names = new LinkedHashSet<String>(
        SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //使用反射将所有的ApplicationContextInitializer实例化  
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
        classLoader, args, names);
    //排序   
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
  }

自动配置的关键就是这个 getSpringFactoriesInstances方法,确切的说是这个方法里的loadFactoryNames方法,浪我们看看这个loadFactoryNames方法干了啥,咋就能实现自动配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
      Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
      ArrayList result = new ArrayList();
      while(urls.hasMoreElements()) {
        URL url = (URL)urls.nextElement();
        Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
        String factoryClassNames = properties.getProperty(factoryClassName);
        result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }
      return result;
    } catch (IOException var8) {
      throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
    }
  }

可以看到这个方法就做了一件事,就是从META-INF/spring.factories这个路径取出所有”url”来,我们可以去到这个路径下看看到底是些啥?

1
2
3
4
5
6
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

这下大家都应该明白了,spring是通过将所有你加载的jar包中找到它需要的ApplicationContextInitializer来进行动态的配置的,只要你有用到特定的maven包,初始化的时候会找这个包下的META-INF/spring.factories的需要的类比如ApplicationContextInitializer进行实例化bean,你就可以用了,不需要任何配置。

说到这已经将所有SpringApplication实例化说完了,只是在加载完ApplicationContextInitializer和ApplicationListener这之后还有一步,就是找到启动类所在的位置并且设入属性mainApplicationClass中。

接下来让我们回到new SpringApplication(sources).run(args)方法来看看run方法是怎么run的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public ConfigurableApplicationContext run(String... args) {
    //开启启动计时器,项目启动完会打印执行时间出来
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    //获取SpringApplicationRunListener并启动监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
          args);
      //环境变量的加载   
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
          applicationArguments);
      //启动后console的打印出来的一堆配置信息   
      Banner printedBanner = printBanner(environment);
      //终极大boss->ApplicationContext实例化
      context = createApplicationContext();
      analyzers = new FailureAnalyzers(context);
      prepareContext(context, environment, listeners, applicationArguments,
          printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
        new StartupInfoLogger(this.mainApplicationClass)
            .logStarted(getApplicationLog(), stopWatch);
      }
      return context;
    }
    catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
    }
  }

从这个方法里面做的最关键的三件事情就是:

获取监听器并启动

加载环境变量,该环境变量包括system environment、classpath environment和用户自己加的application.properties

创建ApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
  }

前两点没什么好说的,重点说说第三个,创建ApplicationContext。创建applicationContext又分为几部:实例化applicationContext、prepareContext、refreshContext。实例化applicationContext会根据在之前我们说的webEnvironment这个属性判断是使用webContext类AnnotationConfigEmbeddedWebApplicationContext还是普通context类AnnotationConfigApplicationContext(在这里我们使用的是webContext为例)然后通过反射进行实例化。applicationContext实例化完了会进入prepareContext流程,这个prepareContext方法会加载之前准备好的environment进入context中,然后如果有beanNameGenerator和resourceLoader那么提前创建bean加载进applicationContext,但是一般这两个都是空的,所以直接进入applyInitializers方法,将之前实例化的所有initializers进行初始化,所有的bean就是在这里进行bean的扫描和加载的因这次讲的是启动过程,所以不再细讲。最后把创建好的applicationContext设置进入listener,prepareContext过程就结束了。最后是refreshContext,这个就和spring的bean加载过程一致了,bean的注入、beanFactory、postProcessBeanFactory等等,详情可以去看看spring bean的生命周期。

总结

spring boot 初始化内容还是很多的,但是总结起来就四点:

* 创建SpringApplication实例,判定环境,是web环境还是普通环境。加载所有需要用到的Initializers和Listeners,这里使用约定大于配置的理念揭开了自动配置的面纱。

* 加载环境变量,环境变量包括system environment、classpath environment、application environment(也就是我们自定义的application.properties配置文件)

* 创建SpringApplicationRunListeners

* 创建ApplicationContext,设置装配context,在这里将所有的bean进行扫描最后在refreshContext的时候进行加载、注入。最终将装配好的context作为属性设置进SpringApplicationRunListeners,这就完成了一个spring boot项目的启动。

以上所述是小编给大家介绍的Spring Boot启动流程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

原文地址:https://www.jb51.net/article/124286.htm

Spring Boot启动流程分析的更多相关文章

  1. Spring Boot -- 启动流程分析之ApplicationContext 中

    上一节我们已经分析到AbsractApplicationContext类refresh方法中的postProcessBeanFactory方法,在分析registerBeanPostProcessor ...

  2. Spring Boot 启动原理分析

    https://yq.aliyun.com/articles/6056 转 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启 ...

  3. Spring MVC启动流程分析

    本文是Spring MVC系列博客的第一篇,后续会汇总成贴子. Spring MVC是Spring系列框架中使用频率最高的部分.不管是Spring Boot还是传统的Spring项目,只要是Web项目 ...

  4. Spring Boot启动流程详解(一)

    环境 本文基于Spring Boot版本1.3.3, 使用了spring-boot-starter-web. 配置完成后,编写了代码如下: @SpringBootApplication public ...

  5. Spring Boot启动流程详解

    注:本文转自http://zhaox.github.io/java/2016/03/22/spring-boot-start-flow 环境 本文基于Spring Boot版本1.3.3, 使用了sp ...

  6. Spring Boot(三):Spring Boot中的事件的使用 与Spring Boot启动流程(Event 事件 和 Listeners监听器)

    前言:在讲述内容之前 希望大家对设计模式有所了解 即使你学会了本片的内容 也不知道什么时候去使用 或者为什么要这样去用 观察者模式: 观察者模式是一种对象行为模式.它定义对象间的一种一对多的依赖关系, ...

  7. Spring Boot启动流程

    基础准备 1,BeanPostProcessor:这个接口的作用在于对于新构造的实例可以做一些自定义的修改.比如如何构造.属性值的修改.构造器的选择等等 2,BeanFactoryPostProces ...

  8. Spring Boot 启动(一) SpringApplication 分析

    Spring Boot 启动(一) SpringApplication 分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html ...

  9. Spring Boot源码分析-启动过程

    Spring Boot作为目前最流行的Java开发框架,秉承"约定优于配置"原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目. 本文基于Spring ...

随机推荐

  1. 008-多线程-锁-JUC锁-CyclicBarrier【让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行】

    一.概述 “循环栅栏”.大概的意思就是一个可循环利用的屏障. CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).因 ...

  2. SSH命令工具研究报告

    0 什么是SSH Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境.SSH通过在网络中创建安全隧道来实现SSH客户端与服务器 ...

  3. 神经网络与数字货币量化交易系列(1)——LSTM预测比特币价格

    首发地址:https://www.fmz.com/digest-topic/4035 1.简单介绍 深度神经网络这些年越来越热门,在很多领域解决了过去无法解决的难题,体现了强大的能力.在时间序列的预测 ...

  4. MyBatis使用技巧、总结、注意事项

    目录 1.mybatis的官方文档地址 2.其他技巧: 2.1 如何在代码中拼接 like %% 2.2 数据库比较时日期的错误操作 2.2.1 异常情况: 2.2.2 为什么会在后面指定jdbcTy ...

  5. vue加载优化方案

    我们的项目随着组件的加入,首次加载的js文件越来越大,用户等待时间越来越长:之前想着使用webpack的splitCoding来解决,看了webpack的官方文档可以配置optimization的 m ...

  6. 破周三,前不着村后不着店的,只好学pandas了,你该这么学,No.9

    如果图片无法观看,请移步 https://blog.csdn.net/hihell 周三了,一个星期最难的一天 大中间的,今天还这么热 5月份,36度的高温 天空飘过几个字 屋里学pandas最得劲 ...

  7. 2019华东交通大学ACM基地简介

    一.基地成就简介: ACM国际大学生程序设计竞赛(英文全称:ACM International Collegiate Programming Contest(简称ACM-ICPC或ICPC))是由国际 ...

  8. Own MusicPlayer隐私策略

    本地音乐播放器 此为Own MusicPlayer本地音乐播放器的隐私策略,本隐私策略内容会不定期更新,以最新内容为主. 若您已经阅读并了解以下内容后,并继续使用该软件,即表示您已同意该协议. 访问权 ...

  9. 一个基于Unix套接字的注册登录系统

    2016/5/5 今天,我参考<Unix网络编程-卷1>第5章的TCP回射客户/服务器程序写了一个简单的注册登录系统,其功能如下:(1)注册.客户端向服务器发送个人信息请求注册,服务器查询 ...

  10. Vue代码分割懒加载的实现方法

    什么是懒加载 懒加载也叫延迟加载,即在需要的时候进行加载,随用随载. 为什么需要懒加载 在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多 ...