Spring Boot启动主流程-构造方法

版本说明

Spring Boot:【2.4.6】

Spring: 【5.3.7】

SpringApplication构造方法主流程

新建一个简单的springboot项目,只导入web模块。

从入口开始分析

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

先不用管注解@SpringBootApplication的事,点开run方法

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

继续跟run方法

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

这里new了一个SpringApplication,并且调用该实例的run方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   // 决议web应用类型
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   // 获取BootstrapRegistryInitializer并设置
   this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
   // 获取ApplicationContextInitializer并设置
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   // 获取ApplicationListener并设置
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   // 通过栈调用链决议主类
   this.mainApplicationClass = deduceMainApplicationClass();
}

上述方法里通过调用getSpringFactoriesInstances获取相应的工厂实例,这块是个比较重要的点。

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
         // 第一次load,从spring.factories下加载工厂名字,放到cache中,之后从cache读取
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 通过反射实例化
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

上面代码出现了Spring框架的一个比较重要的类SpringFactoriesLoader,负责从classpath下的META-INF/spring.factories下加载和实例化指定类型的工厂。

General purpose factory loading mechanism for internal use within the framework.
SpringFactoriesLoader loads and instantiates factories of a given type from “META-INF/spring.factories” files which may be present in multiple JAR files in the classpath. The spring.factories file must be in Properties format, where the key is the fully qualified name of the interface or abstract class, and the value is a comma-separated list of implementation class names. For example:
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
where example.MyService is the name of the interface, and MyServiceImpl1 and MyServiceImpl2 are two implementations.

SpringFactoriesLoader内部持有一个cache,只读取一次,多次load的时候直接从cache读取

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

关键方法如下,很简单,先从cache读,没有则从FACTORIES_RESOURCE_LOCATION加载资源,解析后放到cache中。

/*
Load the fully qualified class names of factory implementations of the given type from "META-INF/spring.factories", using the given class loader.As of Spring Framework 5.3, if a particular implementation class name is discovered more than once for the given factory type, duplicates will be ignored.
*/
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()) {
         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();
            String[] factoryImplementationNames =
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
            for (String factoryImplementationName : factoryImplementationNames) {
               result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                     .add(factoryImplementationName.trim());
            }
         }
      }

      // Replace all lists with unmodifiable lists containing unique elements
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}

End

构造方法内完成的事情

  1. 决议web应用类型
  2. 获取BootstrapRegistryInitializer并设置
  3. 获取ApplicationContextInitializer并设置
  4. 获取ApplicationListener并设置
  5. 通过栈调用链决议主类

其中比较关键的调用链路

下图是具体代码的执行流程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值