当前位置 : 首页 > 维修数据

Spring Boot 启动类推断机制解析

<|begin▁of▁sentence|># 1. 概述 本文,我们来分享 [《精尽 Spring Boot 源码分析 —— SpringApplication》](http://svip.iocoder.cn/Spring-Boot/SpringApplication/) 的**初始化**的源码解析。 在 SpringApplication 的构造方法中,会进行一些初始化的工作。虽然说,它没有向 Spring 容器中添加 Bean ,但是**初始化**的过程,也是非常重要的。代码如下: ```java // SpringApplication.java /** * Create a new {@link SpringApplication} instance. The application context will load * beans from the specified primary sources. See {@link #run(String...)} for details * on how the application is bootstrapped. * @param resourceLoader the resource loader to use * @param primarySources the primary bean sources * @see #run(Class, String[]) * @see #setSources(Set) */ @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)); // <1> 推断 Web 应用类型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // <2> 设置 ApplicationContextInitializer setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // <3> 设置 ApplicationListener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // <4> 推断主应用类 this.mainApplicationClass = deduceMainApplicationClass(); } ``` - `<1>` 处,调用 `WebApplicationType#deduceFromClasspath()` 方法,推断 Web 应用类型。详细解析,见 [「2. 推断 Web 应用类型」](http://svip.iocoder.cn/Spring-Boot/SpringApplication/#) 。 - `<2>` 处,设置 ApplicationContextInitializer 数组。详细解析,见 [「3. 设置 ApplicationContextInitializer」](http://svip.iocoder.cn/Spring-Boot/SpringApplication/#) 。 - `<3>` 处,设置 ApplicationListener 数组。详细解析,见 [「4. 设置 ApplicationListener」](http://svip.iocoder.cn/Spring-Boot/SpringApplication/#) 。 - `<4>` 处,调用 `#deduceMainApplicationClass()` 方法,推断主应用类。详细解析,见 [「5. 推断主应用类」](http://svip.iocoder.cn/Spring-Boot/SpringApplication/#) 。 # 2. 推断 Web 应用类型 `WebApplicationType#deduceFromClasspath()` 方法,推断 Web 应用类型。代码如下: ```java // WebApplicationType.java 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"; private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"; public static WebApplicationType deduceFromClasspath() { // 如果存在 REACTIVE 相关的类,则返回 REACTIVE if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } // 如果不存在 SERVLET 相关的类,则返回 NONE for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } // 如果存在 SERVLET 相关的类,则返回 SERVLET return WebApplicationType.SERVLET; } ``` - 根据不同的条件,返回不同的 WebApplicationType 枚举对象。 - WebApplicationType 枚举,代码如下: ```java // WebApplicationType.java public enum WebApplicationType { /** * The application should not run as a web application and should not start an * embedded web server. */ NONE, /** * The application should run as a servlet-based web application and should start an * embedded servlet web server. */ SERVLET, /** * The application should run as a reactive web application and should start an * embedded reactive web server. */ REACTIVE; } ``` - 一共有三种类型,NONE、SERVLET、REACTIVE 。 # 3. 设置 ApplicationContextInitializer 在 SpringApplication 的构造方法中,会调用 `#getSpringFactoriesInstances(Class type)` 方法,获得 ApplicationContextInitializer 类型的数组。代码如下: ```java // SpringApplication.java private Collection getSpringFactoriesInstances(Class type) { return getSpringFactoriesInstances(type, new Class[] {}); } private Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates // <1> 加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组 Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // <2> 创建对象 List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // <3> 排序 AnnotationAwareOrderComparator.sort(instances); return instances; } ``` - `<1>` 处,调用 `SpringFactoriesLoader#loadFactoryNames(Class factoryClass, ClassLoader classLoader)` 方法,加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组。 - 在 Spring Boot 中,`META-INF/spring.factories` 配置如下: ```properties # Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer ``` - 关于 SpringFactoriesLoader 的源码解析,可见 [《【死磕 Spring】—— IoC 之加载 BeanDefinition》](http://svip.iocoder.cn/Spring/IoC-load-BeanDefinitions/#3-2-2-loadFactoryNames) 的 [「3.2.2 loadFactoryNames」](http://svip.iocoder.cn/Spring-Boot/SpringApplication/#) 小节。 - `<2>` 处,调用 `#createSpringFactoriesInstances(Class type, Class[] parameterTypes, ClassLoader classLoader, Object[] args, Set names)` 方法,创建对象数组。代码如下: ```java // SpringApplication.java private List createSpringFactoriesInstances(Class type, Class[] parameterTypes, ClassLoader classLoader, Object[] args, Set names) { List 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; } ``` - 通过反射,创建对象。 - `<3>` 处,调用 `AnnotationAwareOrderComparator#sort(List list)` 方法,排序对象数组。 - 关于 AnnotationAwareOrderComparator 的排序,可见 [《【死磕 Spring】—— @Order 和 Ordered》](http://svip.iocoder.cn/Spring/Order-Ordered/) 。 - 创建完 ApplicationContextInitializer 对象数组后,会调用 `#setInitializers(Collection> initializers)` 方法,进行设置。代码如下: ```java // SpringApplication.java public void setInitializers(Collection> initializers) { this.initializers = new ArrayList<>(); this.initializers.addAll(initializers); } ``` - 设置到 `initializers` 属性中。 # 4. 设置 ApplicationListener 在 SpringApplication 的构造方法中,会调用 `#getSpringFactoriesInstances(Class type)` 方法,获得 ApplicationListener 类型的数组。 - 具体的逻辑,和 [「3. 设置 ApplicationContextInitializer」](http://svip.iocoder.cn/Spring-Boot/SpringApplication/#) 是一致的。 - 在 Spring Boot 中,`META-INF/spring.factories` 配置如下: ```properties # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceApplicationListener ``` - 创建完 ApplicationListener 对象数组后,会调用 `#setListeners(Collection> listeners)` 方法,进行设置。代码如下: ```java // SpringApplication.java public void setListeners(Collection> listeners) { this.listeners = new ArrayList<>(); this.listeners.addAll(listeners); } ``` - 设置到 `listeners` 属性中。 # 5. 推断主应用类 `#deduceMainApplicationClass()` 方法,推断主应用类。代码如下: ```java // SpringApplication.java private Class deduceMainApplicationClass() { try { // 获得当前 StackTraceElement 数组 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); // 遍历每个 StackTraceElement ,通过方法名是否为 main , for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; } ``` - 通过获得当前 StackTraceElement 数组,然后遍历每个 StackTraceElement ,通过方法名是否为 `"main"` ,来判断是哪个类。例如: ``` java.lang.RuntimeException at org.springframework.boot.SpringApplication.deduceMainApplicationClass(SpringApplication.java:284) at org.springframework.boot.SpringApplication.(SpringApplication.java:226) at org.springframework.boot.SpringApplication.(SpringApplication.java:212) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) at cn.iocoder.springboot.lab01.springbootdemo.SpringbootDemoApplication.main(SpringbootDemoApplication.java:15) ``` - 此时,`stackTraceElement.getClassName()` 方法,就会返回 `"cn.iocoder.springboot.lab01.springbootdemo.SpringbootDemoApplication"` 。

栏目列表