SpringBoot源码阅读-启动流程

源码版本:SpringBoot2.7.18

@SpringBootApplication
public class BootSourceApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootSourceApplication.classargs);
    }
}

用过SpringBoot框架的朋友们对上面的代码一定十分熟悉。今天我们就一起来看一下为啥简简单单几行代码就能为我启动一个Spring应用。

进入SpringApplication#run(Class<?> primarySource, String... args)

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

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  return new SpringApplication(primarySources).run(args);
 }
    
}
  1. 进入run(Class<?> primarySource, String... args)方法后立即调用另一个run(Class<?>[] primarySources, String[] args)方法
  2. run(Class<?>[] primarySources, String[] args)方法主要做了下面两件事
    1. 创建了一个SpringApplication实例
    2. 执行run(String... args)方法

创建SpringApplication实例

public class SpringApplication {
 
    //...........

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

 @SuppressWarnings({ "unchecked""rawtypes" })
 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  this.resourceLoader = resourceLoader;
  Assert.notNull(primarySources, "PrimarySources must not be null");
  this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  this.webApplicationType = WebApplicationType.deduceFromClasspath();
  this.bootstrapRegistryInitializers = new ArrayList<>(
    getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  this.mainApplicationClass = deduceMainApplicationClass();
    }

    //..........
}

我们来看一下SpringApplication构造方法中做了哪些操作

  1. 设置resourceLoader,这里传进来的是null
  2. 设置primarySources主配置源,这里传进来的是BootSourceApplication.class
  3. 推断应用类型webApplicationType,可能是非WebServlet-WebReactive-Web
  4. 设置bootstrapRegistryInitializers,获取BootstrapRegistryInitializer的实现类
  5. 设置initializers,获取ApplicationContextInitializer的实现类
  6. 设置listeners,获取ApplicationListener的实现类
  7. 推断启动类mainApplicationClass

推断应用类型

public enum WebApplicationType {

 NONE,
 SERVLET,
 REACTIVE;

 private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
   "org.springframework.web.context.ConfigurableWebApplicationContext" };
 private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
 private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
 private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

 static WebApplicationType deduceFromClasspath() {
  if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
   return WebApplicationType.REACTIVE;
  }
  for (String className : SERVLET_INDICATOR_CLASSES) {
   if (!ClassUtils.isPresent(className, null)) {
    return WebApplicationType.NONE;
   }
  }
  return WebApplicationType.SERVLET;
 }

}

通过反射判断应用的类路径下是否存在相应的字节码,来判断是哪种应用类型

  • NONE非Web应用
  • SERVLETServlet Web应用
  • REACTIVEREACTIVE Web应用

getSpringFactoriesInstances(Class<T> type)

public class SpringApplication {
 
    //...........

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

 @SuppressWarnings({ "unchecked""rawtypes" })
 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  //..................
  this.bootstrapRegistryInitializers = new ArrayList<>(
    getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  //.....................
    }

    //..........
}

大家看一下上面的代码,在寻找BootstrapRegistryInitializerApplicationContextInitializerApplicationListener时,都是通过getSpringFactoriesInstances(Class<T> type)方法去寻找的,下面我们就研究一下这个方法的作用

public class SpringApplication {
 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
  return getSpringFactoriesInstances(type, new Class<?>[] {});
 }

 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  ClassLoader classLoader = getClassLoader();
  // Use names and ensure unique to protect against duplicates
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  AnnotationAwareOrderComparator.sort(instances);
  return instances;
 }
}

可以看到上面的代码主要做了下面几个动作

  1. SpringFactoriesLoader.loadFactoryNames(type, classLoader)最终是去重后的names
  2. createSpringFactoriesInstances创建实例
  3. AnnotationAwareOrderComparator.sort将实例排序并返回

SpringFactoriesLoader.loadFactoryNames(type, classLoader)最终是读取类路径下所有的META-INF/spring.factories文件,最终将其转换成一个Map<String,List<String>>

public final class SpringFactoriesLoader {

 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

 static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
    
 public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        //....
  List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
  //....
  return result;
 }

 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
 }

 private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
  Map<String, List<String>> result = cache.get(classLoader);
  if (result != null) {
   return result;
  }

  result = new HashMap<>();
  try {
   Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
   while (urls.hasMoreElements()) {
    for (Map.Entry<?, ?> entry : properties.entrySet()) {
     //......
    }
   }
   cache.put(classLoader, result);
  }catch(){}
  return result;
 }
}
SpringBoot源码阅读-启动流程
image.png

createSpringFactoriesInstances方法就是通过全限定名创建实例

AnnotationAwareOrderComparator.sort是对实例进行排序,具体实现看AnnotationAwareOrderComparator和其父类OrderComparator。这里就不看了

SpringBoot可以在META-INF/spring.factories定义实现类,并且可以自动加载并实例化。这被称为SPI机制

推断启动类

 private Class<?> deduceMainApplicationClass() {
  try {
   StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
   for (StackTraceElement stackTraceElement : stackTrace) {
    if ("main".equals(stackTraceElement.getMethodName())) {
     return Class.forName(stackTraceElement.getClassName());
    }
   }
  }
  catch (ClassNotFoundException ex) {
   // Swallow and continue
  }
  return null;
 }

推断启动类的方式也是让XiXi眼前一亮,通过获取运行的堆栈信息,来找到main方法的那个栈帧信息,取main方法所在的类作为启动类

SpringBoot源码阅读-启动流程
image.png

run(String... args)方法

public class SpringApplication {
 public ConfigurableApplicationContext run(String... args) {
  long startTime = System.nanoTime();
  DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  ConfigurableApplicationContext context = null;
  configureHeadlessProperty();
  SpringApplicationRunListeners listeners = getRunListeners(args);
  listeners.starting(bootstrapContext, this.mainApplicationClass);
  try {
   ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
   ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
   configureIgnoreBeanInfo(environment);
   Banner printedBanner = printBanner(environment);
   context = createApplicationContext();
   context.setApplicationStartup(this.applicationStartup);
   prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
   refreshContext(context);
   afterRefresh(context, applicationArguments);
   Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
   if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
   }
   listeners.started(context, timeTakenToStartup);
   callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
   handleRunFailure(context, ex, listeners);
   throw new IllegalStateException(ex);
  }
  try {
   Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
   listeners.ready(context, timeTakenToReady);
  }
  catch (Throwable ex) {
   handleRunFailure(context, ex, null);
   throw new IllegalStateException(ex);
  }
  return context;
 }
}
  1. 创建DefaultBootstrapContext
  2. 获取所有的SpringApplicationRunListeners
  3. 执行SpringApplicationRunListeners#starting()
  4. 创建ApplicationArgumentsDefaultApplicationArguments
  5. 准备ConfigurableEnvironmentprepareEnvironment方法
  6. 打印Banner
  7. 创建应用上下文:createApplicationContext
  8. 准备应用上下文:prepareContext
  9. 刷新应用上下文:refreshContext
  10. 应用上下文刷新后操作:afterRefresh
  11. 执行SpringApplicationRunListeners#started()
  12. 执行Runner:ApplicationRunnerCommandLineRunner
  13. 执行SpringApplicationRunListeners#ready()
  14. 返回应用上下文

若以上步骤中出现异常情况,均通过handleRunFailure进行处理

涉及SpringApplicationRunListeners的流程

这里我将SpringApplicationRunListeners在run方法中的执行流程梳理一下

SpringBoot源码阅读-启动流程
image.png
class SpringApplicationRunListeners {

 private final List<SpringApplicationRunListener> listeners;

}

SpringApplicationRunListeners包含一个SpringApplicationRunListener列表,SpringApplicationRunListeners的方法调用,实际是遍历每一个SpringApplicationRunListener进行执行。 这里XiXi感觉是用到了一点设计模式,但是又没有找到完全能对应上的设计模式。我这里暂且认为是组合模式

涉及应用上下文的流程

这里我将应用上下文在run方法中的流程抽出来整理一下

SpringBoot源码阅读-启动流程
image.png

主要涉及应用上下的创建、准备、刷新。每个动作后面又会涉及到一堆逻辑。这里就不展开了

总结

好的兄弟们,SpringBoot的整个启动代码我们就分析到这里。有些具体的点后面会依依写文章分析。谢谢大家的阅读。Taco Tuesday!!!


原文始发于微信公众号(溪溪技术笔记):SpringBoot源码阅读-启动流程

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/207084.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!