In-depth understanding of the mechanism SpringBoot start: initialization process (run method)

I. Introduction

  In the last one, we introduce the annotations automated configuration principle @SpringbootApplication, then first of all let's look at this annotation is mainly what has been done for our sprngboot works: The key steps we can be automatically configured and the corresponding comments Summarized as follows:

1. @ Configuration & @Bean ------ >>> and bean java code based on the configuration

2. @ Conditional -------- >>>>>> automatic configuration setting conditions depend

3. @ EnableConfigurationProperties with @ ConfigurationProperties-> reads the configuration file into a bean.

4. @ EnableAutoConfiguration, @ AutoConfigurationPackage with @ Import-> find and load the bean implementation.

  Today, we come to analyze the source code through its boot process.

  Benpian analysis based 2.0.4.RELEASE version, read this article need to have some basic Java and the Spring Framework, Spring Boot If you do not know what is recommended to walk Xiaguan network Spring Boot tutorial.

Two, Spring Boot inlet Class


1


The above is the most simple and general Spring Boot inlet class. Required class is the top entry packet following a first class containing the main method used to enable annotations @SpringBootApplication Spring Boot characteristics, using methods to start Spring Boot SpringApplication.run project.

First look at the source code to call the class method inside run:


1The first parameter primarySource: Load the main resource;


The second parameter args: application parameters passed to the application.

First with a major resource to instantiate a SpringApplication object, then call the run method of this object, so we start to analyze the source code in two steps.

三、SpringApplication 的实例化过程


1


跟着上面的run进入到下面的方法:


1


进入SpringApplication可以看到以下源码:


1


从上面的源码可以知道,整个实例化过程有7个步骤:

1.资源初始化资源加载器为 null

this.resourceLoader = resourceLoader; 复制代码

2.断言主要加载资源类不能为 null,否则报错

Assert.notNull(primarySources, "PrimarySources must not be null"); 复制代码

3.初始化主要加载资源类集合并去重

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 复制代码

4.推断当前 WEB 应用类型

this.webApplicationType = WebApplicationType.deduceFromClasspath(); 复制代码

这里进去WebApplicationType方法看一下源码以及相关构造方法:

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; 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"; 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; } static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) { if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) { return WebApplicationType.SERVLET; } if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) { return WebApplicationType.REACTIVE; } return WebApplicationType.NONE; } private static boolean isAssignable(String target, Class<?> type) { try { return ClassUtils.resolveClassName(target, null).isAssignableFrom(type); } catch (Throwable ex) { return false; } } } 复制代码

这个就是根据类路径下是否有对应项目类型的类推断出不同的应用类型,这里也说明 Spring Boot 2是支持响应式编程。

5.设置应用上下文初始化器

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); 复制代码

进入ApplicationContextInitializer,我们可以知道其作用:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { /**  * Initialize the given application context.  * @param applicationContext the application to configure  */ void initialize(C applicationContext); } 复制代码

用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等。

再来看下 setInitializers 方法源码,其实就是初始化一个 ApplicationContextInitializer 应用上下文初始化器实例的集合。

 public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) { this.initializers = new ArrayList<>(); this.initializers.addAll(initializers); } 复制代码

最后我们来看一下核心方法getSpringFactoriesInstances 其源码如下:

 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 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; } 复制代码

设置应用上下文初始化器可分为以下 5 个步骤。 这里是实例化的核心:

5.1) 获取当前线程上下文类加载器

ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 复制代码

5.2) 获取 ApplicationContextInitializer 的实例名称集合并去重

Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 复制代码

loadFactoryNames 的源码如下:

 public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 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 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); } } 复制代码

根据类路径下的 META-INF/spring.factories 文件解析并获取 ApplicationContextInitializer 接口的所有配置的类路径名称。

spring-boot-autoconfigure-2.0.4.RELEASE.jar!/META-INF/spring.factories 的初始化器相关配置内容如下:

# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnClassCondition # Auto Configure ...... 复制代码

5.3) 根据以上类路径创建初始化器实例列表

List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); 复制代码

5.4) 初始化器实例列表排序

AnnotationAwareOrderComparator.sort(instances); 复制代码

5.5) 返回实例对象

return instances; 复制代码

6.设置监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 复制代码

ApplicationListener 的作用是什么?源码如下。(有空再写一篇springboot2 监听器的应用)

@FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /**  * Handle an application event.  * @param event the event to respond to  */ void onApplicationEvent(E event); } 复制代码

看源码,这个接口继承了 JDK 的 java.util.EventListener 接口,实现了观察者模式,它一般用来定义感兴趣的事件类型,事件类型限定于 ApplicationEvent 的子类,这同样继承了 JDK 的 java.util.EventObject 接口。

Set listeners and set the initialization method call is the same, just not the same type passed in, setting the listener interface types:! GetSpringFactoriesInstances, corresponding spring-boot-autoconfigure-2.0.4.RELEASE.jar / META -INF / spring.factories configuration file content in the above configuration file inside: Application Listeners

The current can be seen only through a BackgroundPreinitializer listener file.

7. The deduced primary inlet Application Class

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; } 复制代码

By constructing a runtime exception, then the exception stack traversal method name, the method for obtaining the stack frame called main, never get the name of the class and then return this inlet.

IV Summary

Today, the main analysis of the source code to initialize instances SpringBoot analysis, analysis to temporarily chapter:


1


Guess you like

Origin blog.51cto.com/14455981/2474941