SpringBoot启动时都做了哪些事(一)?

简介: SpringBoot启动时都做了哪些事(一)?

本文环境是SpringBoot 2.2.4.RELEASE,我们尝试去跟踪研究启动流程都做了哪些事情。

本文分析SpringApplication的实例化。核心有四点:

  • 确定primarySources 和主启动类
  • 确定webApplicationType
  • 检测并实例化ApplicationContextInitializer
  • 检测并实例化ApplicationListener


以main方法为入口。

public static void main(String[] args) {
     SpringApplication.run(RecommendApplication.class, args);
 }

SpringApplication的run方法。

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);
}

这里primarySource就是我们的主启动类,args默认为空。其分为SpringApplication的实例化,进而触发其run方法。

public SpringApplication(Class<?>... primarySources) {
  this(null, primarySources);
}
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();
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  this.mainApplicationClass = deduceMainApplicationClass();
}

这里要特别注意的是setInitializers和setListeners。前者检索jar并实例化ApplicationContextInitializer,后者检索jar并实例化ApplicationListener然后赋予SpringApplication实例。

① getSpringFactoriesInstances

SpringApplication 的getSpringFactoriesInstances方法如下所示。

// SpringApplication 这里type为ApplicationContextInitializer.class
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
  // 检索符合目标类型的所有beanName
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  //创建bean实例
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  //对bean实例进行排序
  AnnotationAwareOrderComparator.sort(instances);
  //返回实例结果
  return instances;
}

② loadFactoryNames

将会从每个jar包下的META-INF/spring.factories路径扫描目标类型(factoryType),比如本文这里的ApplicationContextInitializer和ApplicationListener。

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

首先调用loadSpringFactories方法从每个jar的FACTORIES_RESOURCE_LOCATION==META-INF/spring.factories检索并获取配置信息放到MultiValueMap<String, String> result 中。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  MultiValueMap<String, String> result = cache.get(classLoader);
  if (result != null) {
    return result;
  }
  try {
    Enumeration<URL> urls = (classLoader != null ?
        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    result = new LinkedMultiValueMap<>();
    while (urls.hasMoreElements()) {
      URL url = urls.nextElement();
      UrlResource resource = new UrlResource(url);
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      for (Map.Entry<?, ?> entry : properties.entrySet()) {
        String factoryTypeName = ((String) entry.getKey()).trim();
        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
          result.add(factoryTypeName, factoryImplementationName.trim());
        }
      }
    }
    cache.put(classLoader, result);
    return result;
  }
  catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load factories from location [" +
        FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
}

这里拿到的result如下所示,此时是一个全集,还没有进行过滤。

这个检索结果同时会往cache中放一下:

private static final Map<ClassLoader, MultiValueMap<String, String>> cache = 
new ConcurrentReferenceHashMap<>();

③ createSpringFactoriesInstances

遍历Set<String> names,反射进行实例化。

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
    ClassLoader classLoader, Object[] args, Set<String> names) {
  List<T> instances = new ArrayList<>(names.size());
  for (String name : names) {
    try {
      Class<?> instanceClass = ClassUtils.forName(name, classLoader);
      Assert.isAssignable(type, instanceClass);
      Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
      T instance = (T) BeanUtils.instantiateClass(constructor, args);
      instances.add(instance);
    }
    catch (Throwable ex) {
      throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
    }
  }
  return instances;
}

④ 实例结果

这里获取的ApplicationContextInitializer如下所示:

0 = "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer"
1 = "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener"
2 = "org.springframework.boot.devtools.restart.RestartScopeInitializer"
3 = "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer"
4 = "org.springframework.boot.context.ContextIdApplicationContextInitializer"
5 = "org.springframework.boot.context.config.DelegatingApplicationContextInitializer"
6 = "org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer"
7 = "org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer"

排序前后的instances(有8个):

获取的ApplicationListener如下(有13个):

0 = "org.springframework.boot.autoconfigure.BackgroundPreinitializer"
1 = "org.springframework.boot.devtools.restart.RestartApplicationListener"
2 = "org.springframework.boot.devtools.logger.DevToolsLogFactory.Listener"
3 = "org.springframework.boot.ClearCachesApplicationListener"
4 = "org.springframework.boot.builder.ParentContextCloserApplicationListener"
5 = "org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor"
6 = "org.springframework.boot.context.FileEncodingApplicationListener"
7 = "org.springframework.boot.context.config.AnsiOutputApplicationListener"
8 = "org.springframework.boot.context.config.ConfigFileApplicationListener"
9 = "org.springframework.boot.context.config.DelegatingApplicationListener"
10 = "org.springframework.boot.context.logging.ClasspathLoggingApplicationListener"
11 = "org.springframework.boot.context.logging.LoggingApplicationListener"
12 = "org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener"

排序后的ApplicationListener:

0 = {RestartApplicationListener@1940} 
1 = {CloudFoundryVcapEnvironmentPostProcessor@1941} 
2 = {ConfigFileApplicationListener@1942} 
3 = {AnsiOutputApplicationListener@1943} 
4 = {LoggingApplicationListener@1944} 
5 = {BackgroundPreinitializer@1945} 
6 = {ClasspathLoggingApplicationListener@1946} 
7 = {DelegatingApplicationListener@1947} 
8 = {ParentContextCloserApplicationListener@1948} 
9 = {DevToolsLogFactory$Listener@1949} 
10 = {ClearCachesApplicationListener@1950} 
11 = {FileEncodingApplicationListener@1951} 
12 = {LiquibaseServiceLocatorApplicationListener@1952} 

接下来就会触发SpringApplication实例的run方法了。


目录
相关文章
|
6月前
|
Java
springboot启动时执行
springboot启动时执行
36 0
|
16小时前
|
Java 测试技术 数据库
SpringBoot启动时设置不加载数据库
SpringBoot启动时设置不加载数据库
15 0
|
16小时前
|
存储 Java API
你了解SpringBoot启动时API相关信息是用什么数据结构存储的吗?(上篇)
你了解SpringBoot启动时API相关信息是用什么数据结构存储的吗?(上篇)
33 0
|
16小时前
|
Java Linux Windows
windows解决SpringBoot启动时:APPLICATION FAILED TO START
windows解决SpringBoot启动时:APPLICATION FAILED TO START
42 0
|
5月前
|
Java 容器
SpringBoot启动时都做了哪些事(三)?
SpringBoot启动时都做了哪些事(三)?
35 0
|
5月前
|
监控 安全 Java
SpringBoot启动时都做了哪些事(二)?
SpringBoot启动时都做了哪些事(二)?
47 0
|
5月前
|
Java 容器
SpringBoot启动时都做了哪些事(四)?
SpringBoot启动时都做了哪些事(四)?
41 0
|
6月前
|
缓存 Java 数据库
Springboot项目启动时加载数据库数据到内存
Springboot项目启动时加载数据库数据到内存
|
7月前
|
Java
如何修改springboot项目启动时的默认图标?
如何修改springboot项目启动时的默认图标?
如何修改springboot项目启动时的默认图标?
|
9月前
|
SQL Oracle Java
springboot启动时执行sql文件
springboot启动时执行sql文件