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


相关文章
|
21天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
21天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
21天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
176 37
|
9天前
|
JavaScript Java 关系型数据库
毕设项目&课程设计&毕设项目:基于springboot+vue实现的在线考试系统(含教程&源码&数据库数据)
本文介绍了一个基于Spring Boot和Vue.js实现的在线考试系统。随着在线教育的发展,在线考试系统的重要性日益凸显。该系统不仅能提高教学效率,减轻教师负担,还为学生提供了灵活便捷的考试方式。技术栈包括Spring Boot、Vue.js、Element-UI等,支持多种角色登录,具备考试管理、题库管理、成绩查询等功能。系统采用前后端分离架构,具备高性能和扩展性,未来可进一步优化并引入AI技术提升智能化水平。
毕设项目&课程设计&毕设项目:基于springboot+vue实现的在线考试系统(含教程&源码&数据库数据)
|
11天前
|
Java 关系型数据库 MySQL
毕设项目&课程设计&毕设项目:springboot+jsp实现的房屋租租赁系统(含教程&源码&数据库数据)
本文介绍了一款基于Spring Boot和JSP技术的房屋租赁系统,旨在通过自动化和信息化手段提升房屋管理效率,优化租户体验。系统采用JDK 1.8、Maven 3.6、MySQL 8.0、JSP、Layui和Spring Boot 2.0等技术栈,实现了高效的房源管理和便捷的租户服务。通过该系统,房东可以轻松管理房源,租户可以快速找到合适的住所,双方都能享受数字化带来的便利。未来,系统将持续优化升级,提供更多完善的服务。
毕设项目&课程设计&毕设项目:springboot+jsp实现的房屋租租赁系统(含教程&源码&数据库数据)
|
11天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
554 6
|
9天前
|
XML 前端开发 Java
控制spring框架注解介绍
控制spring框架注解介绍
|
9天前
|
存储 NoSQL Java
Spring Session框架
Spring Session 是一个用于在分布式环境中管理会话的框架,旨在解决传统基于 Servlet 容器的会话管理在集群和云环境中的局限性。它通过将用户会话数据存储在外部介质(如数据库或 Redis)中,实现了会话数据的跨服务器共享,提高了应用的可扩展性和性能。Spring Session 提供了无缝集成 Spring 框架的 API,支持会话过期策略、并发控制等功能,使开发者能够轻松实现高可用的会话管理。
Spring Session框架
|
16天前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
27 2
|
16天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
下一篇
无影云桌面