【框架源码】SpringBoot核心源码解读之启动类源码分析

简介: 【框架源码】SpringBoot核心源码解读之启动类源码分析

a16786f526d34979b05b56f122c77670.jpg


首先我们要先带着我们的疑问,spring boot是如何启动应用程序?去分析SpringBoot的启动源码。

我们在新建SpringBoot项目时,核心方法就是主类的run方法。

SpringApplication.run(ArchWebApplication.class, args)

ff89b555db844bf0b772d02455252360.jpg

我们点击run方法进入到源码中,这块传入的了一个我们当前程序主类的类对象以及主程序参数。

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

再往下走,调用本身的run方法,这里就开始初始化SpringApplication对象啦,然后在调用run方法。初始化SpringApplication对象时也是将主类的类对象传入进去,然后调用run方法,将主程序传进来的参数传进去。

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

我们先来看SpringApplication对象初始化时都做了哪些操作。

同样调用自身的双参构造方法,null为传入的资源加载器。

  public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
  }

再往下走就到了初始化SpringApplication的核心逻辑啦。

  public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    //首先是将传来的资源加载器进行赋值,当然我们知道这个资源加载器是null
    this.resourceLoader = resourceLoader;
    //然后在进行类对象的判空
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //然后将传进来的类对像的数组转成list在转成set。
    //(我估计这里是为了去重类对象,因为可以穿进来的可变参数有重复的,可变参数实质就是一个数组)。
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //deduceFromClasspath 方法的目的是用来判断应用是servlet还是reactive应用
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //这一步的逻辑是从spring.factories文件中读取 key为ApplicationContextInitializer的类信息,采用反射实例化对象
    //设置上下文信息
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    //这一步的逻辑同上,也是从spring.factories文件中读取 key为ApplicationListener的类信息,采用反射实例化对象
    //设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //判断是否为main函数,配置主函数启动类 class信息
    this.mainApplicationClass = deduceMainApplicationClass();
  }

上面是一个创建SpringApplication的整体逻辑,那么我们在具体看一下 WebApplicationType.deduceFromClasspath()里面的逻辑是怎么样的。WebApplicationType本身是一个枚举类

  //一共三种方式返回服务条件的一种 
  static WebApplicationType deduceFromClasspath() {
    //判断当前应用是不是 REACTIVE应用
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
    }
    //判断是不是 非web应用
    for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
        return WebApplicationType.NONE;
      }
    }
    //都不是的话返回 web应用方式
    return WebApplicationType.SERVLET;
  }

ClassUtils.isPresent()这个方法的主要作用是通过反射判断相应的类存不存在。

  public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
      forName(className, classLoader);
      return true;
    }
    catch (IllegalAccessError err) {
      throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
          className + "]: " + err.getMessage(), err);
    }
    catch (Throwable ex) {
      // Typically ClassNotFoundException or NoClassDefFoundError...
      return false;
    }
  }

ok,分析完WebApplicationType.deduceFromClasspath(),我们在来看一下getSpringFactoriesInstances()这个方法的核心逻辑。

  private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
      Class<?>[] parameterTypes, Object... args) {
    //获取ClassLoader
    ClassLoader classLoader = getClassLoader();
    //这一步很重要,重点在于内部是从spring.factories中获取对应的类信息
    Set<String> names = new LinkedHashSet<>(
        SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //等到需要加载的类信息之后,通过反射创建对象。
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
        classLoader, args, names);
    //排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
  }

我们来看一下,loadFactoryNames中都干了什么,它这里面的核心就在于加载配置文件,反射实例化对象

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  }
  //核心逻辑在这个方法
  private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //首先先去判断下Map中是否有值,有值的话,就直接返回,相当于一个本地缓存。
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
      return result;
    }
    try {
      //如果Map没有值的话,获取资源目录下的spring.factories文件。加载配置
      //源码中 FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
      Enumeration<URL> urls = (classLoader != null ?
          classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
          ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      //下面就是遍历放进map中
      while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        UrlResource resource = new UrlResource(url);
        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
        for (Map.Entry<?, ?> entry : properties.entrySet()) {
          String factoryClassName = ((String) entry.getKey()).trim();
          for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
            result.add(factoryClassName, factoryName.trim());
          }
        }
      }
      cache.put(classLoader, result);
      return result;
    }
    catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
          FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
  }

05a66a72ab73441cb86f8f36f8242cfa.jpgimage.jpeg

OK,这里关于SpringApplication初始化的操作就已经完成啦,那么下面我们在一下run()方法里都做些什么操作。传进去的是主程序的参数。


be5e98eca9074d419c167db8ea4f162c.jpg

public ConfigurableApplicationContext run(String... args) {
    //创建StopWatch对象,用于记录服务启动的时间
    StopWatch stopWatch = new StopWatch();
    //记录服务启动开始时间
    stopWatch.start();
    //定义应用程序上下文
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    //配置运行程序的系统环境,以确保可正确的运行。
    configureHeadlessProperty();
    //获取在SpringApplication上的所有监听器。
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //通知所有监听器,启动应用程序
    listeners.starting();
    try {
      //封装应用程序的主程序参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
          args);
      //准备应用环境,生成环境变量
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
          applicationArguments);
      configureIgnoreBeanInfo(environment);
      //打印应用程序的banner
      Banner printedBanner = printBanner(environment);
      //创建应用上下文对象
      context = createApplicationContext();
      //从spring.factories中获取SpringBootExceptionReporter类型的异常解析器。
      exceptionReporters = getSpringFactoriesInstances(
          SpringBootExceptionReporter.class,
          new Class[] { ConfigurableApplicationContext.class }, context);
      //用已有的数据准备上下文,为刷新做准备
      prepareContext(context, environment, listeners, applicationArguments,
          printedBanner);
      //启动应用程序上下文,通过refresh实现
      refreshContext(context);
      //上下文刷新后执行一些后置处理
      afterRefresh(context, applicationArguments);
      //记录结束时间
      stopWatch.stop();
      //判断是否需要记录应用程序的启动信息
      if (this.logStartupInfo) {
        new StartupInfoLogger(this.mainApplicationClass)
            .logStarted(getApplicationLog(), stopWatch);
      }
      //通知所有监听器,应用程序已经启动,传递上下文对象和启动时间
      listeners.started(context);
      //运行所有已经注册的runner
      callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
    }
    try {
      //发布应用上下文就绪事件
      listeners.running(context);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
    }
    return context;
  }

image.jpeg

ok,下面我们来断点调试下springboot启动类的源码执行。

启动SpringBoot应用程序。


d009703f61eb4246b30fdb63df0e24f9.jpg


e42632d632664021ae2c5b0f8132a700.jpg

f136a3ce678348aa91a5f22bd0854ead.jpg


f0ed309565ee48a2a62c52185cb1c8fc.jpg

后面就是排序实例 ,返回实例。

0110ecbd0b6643d19b2cde0436922f9e.jpg


33e7ad954f6d4008bda5c0407f8616fa.jpg

剩下的就是我们上面画的那些流程啦。

9646f62ba1524e51892a07521417123f.jpg

1b013c281a094374b829f495f01f5ab4.jpg

9043d8945ca0476f94f233cb17fd265e.jpg


56ed24cb6e8a48ec93e06098c3b3cb2b.jpg

ok,至此SpringBoot启动的全流程就已经完成啦,最后在总结一下大体的流程。

初始化SpringApplication,运行SpringApplication的run方法

读取 spring.factories 的多个初始化器和监听器

配置项目中环境变量、jvm配置信息、配置文件信息

预初始化环境,创建环境对象

创建Spring容器对象(ApplicationContext)

调用spring的refresh加载IOC容器、自动配置类,并创建bean等信息

调用很多监听器并传递上下文对象

运行相关runner


相关文章
|
4天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
2天前
|
安全 前端开发 Java
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择。依赖注入使对象管理交由Spring容器处理,实现低耦合高内聚;AOP则分离横切关注点如事务管理,增强代码模块化。Spring还提供MVC、Data、Security等模块满足多样需求,并通过Spring Boot简化配置与部署,加速微服务架构构建。掌握这些核心概念与工具,开发者能更从容应对挑战,打造卓越应用。
8 1
|
1天前
|
JavaScript Java Maven
毕设项目&课程设计&毕设项目:springboot+vue实现的在线求职管理平台(含教程&源码&数据库数据)
本文介绍了一款基于Spring Boot和Vue.js实现的在线求职平台。该平台采用了前后端分离的架构,使用Spring Boot作为后端服务
毕设项目&课程设计&毕设项目:springboot+vue实现的在线求职管理平台(含教程&源码&数据库数据)
|
2天前
|
NoSQL JavaScript 前端开发
SpringBoot+Vue实现校园二手系统。前后端分离技术【完整功能介绍+实现详情+源码】
文章介绍了如何使用SpringBoot和Vue实现一个校园二手系统,采用前后端分离技术。系统具备完整的功能,包括客户端和管理员端的界面设计、个人信息管理、商品浏览和交易、订单处理、公告发布等。技术栈包括Vue框架、ElementUI、SpringBoot、Mybatis-plus和Redis。文章还提供了部分源代码,展示了前后端的请求接口和Redis验证码功能实现,以及系统重构和模块化设计的一些思考。
SpringBoot+Vue实现校园二手系统。前后端分离技术【完整功能介绍+实现详情+源码】
|
4天前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
6天前
|
NoSQL 关系型数据库 MySQL
SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘
SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘
17 2
|
4天前
|
XML Java Maven
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
这篇文章是Spring5框架的入门到实战教程,介绍了Spring5的新功能——整合日志框架Log4j2,包括Spring5对日志框架的通用封装、如何在项目中引入Log4j2、编写Log4j2的XML配置文件,并通过测试类展示了如何使用Log4j2进行日志记录。
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
|
5天前
|
Java API Spring
Spring5入门到实战------1、Spring5框架概述、入门案例
这篇文章是Spring5框架的入门教程,概述了Spring框架的核心概念和特点,并通过一个创建普通Java类的案例,详细演示了从下载Spring核心Jar包、创建配置文件、编写测试代码到运行测试结果的完整流程,涵盖了Spring IOC容器的使用和依赖注入的基本用法。
Spring5入门到实战------1、Spring5框架概述、入门案例
|
7天前
|
安全 前端开发 Java
Web端系统开发解决跨域问题——以Java SpringBoot框架配置Cors为例
在Web安全上下文中,源(Origin)是指一个URL的协议、域名和端口号的组合。这三个部分共同定义了资源的来源,浏览器会根据这些信息来判断两个资源是否属于同一源。例如,https://www.example.com:443和http://www.example.com虽然域名相同,但由于协议和端口号不同,它们被视为不同的源。同源(Same-Origin)是指两个URL的协议、域名和端口号完全相同。只有当这些条件都满足时,浏览器才认为这两个资源来自同一源,从而允许它们之间的交互操作。
10 0
Web端系统开发解决跨域问题——以Java SpringBoot框架配置Cors为例
|
4天前
|
Java API Spring
Spring5入门到实战------1、Spring5框架概述、入门案例
这篇文章是Spring5框架的入门教程,概述了Spring框架的核心概念和特点,并通过一个创建普通Java类的案例,详细演示了从下载Spring核心Jar包、创建配置文件、编写测试代码到运行测试结果的完整流程,涵盖了Spring IOC容器的使用和依赖注入的基本用法。