【框架源码】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


相关文章
|
19天前
|
XML 安全 Java
|
23天前
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
29 0
|
8天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
55 13
|
5天前
|
IDE Java 测试技术
互联网应用主流框架整合之Spring Boot开发
通过本文的介绍,我们详细探讨了Spring Boot开发的核心概念和实践方法,包括项目结构、数据访问层、服务层、控制层、配置管理、单元测试以及部署与运行。Spring Boot通过简化配置和强大的生态系统,使得互联网应用的开发更加高效和可靠。希望本文能够帮助开发者快速掌握Spring Boot,并在实际项目中灵活应用。
23 5
|
15天前
|
缓存 Java 数据库连接
Spring框架中的事件机制:深入理解与实践
Spring框架是一个广泛使用的Java企业级应用框架,提供了依赖注入、面向切面编程(AOP)、事务管理、Web应用程序开发等一系列功能。在Spring框架中,事件机制是一种重要的通信方式,它允许不同组件之间进行松耦合的通信,提高了应用程序的可维护性和可扩展性。本文将深入探讨Spring框架中的事件机制,包括不同类型的事件、底层原理、应用实践以及优缺点。
46 8
|
16天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
25天前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
46 6
|
1月前
|
Java 数据库连接 数据库
不可不知道的Spring 框架七大模块
Spring框架是一个全面的Java企业级应用开发框架,其核心容器模块为其他模块提供基础支持,包括Beans、Core、Context和SpEL四大子模块;数据访问及集成模块支持数据库操作,涵盖JDBC、ORM、OXM、JMS和Transactions;Web模块则专注于Web应用,提供Servlet、WebSocket等功能;此外,还包括AOP、Aspects、Instrumentation、Messaging和Test等辅助模块,共同构建强大的企业级应用解决方案。
52 2
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
80 2
|
1月前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
109 1
下一篇
DataWorks