SpringBoot:Spring容器的启动过程

Posted magic-sea

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot:Spring容器的启动过程相关的知识,希望对你有一定的参考价值。

一、简述

Spring的启动过程就是IoC容器的启动过程,本质上就是创建和初始化Bean的工厂(BeanFactory),BeanFactory是整个SpringIoC的核心,Spring使用BeanFactory来实例化、配置和管理Bean。

二、SpringBoot的启动过程

在SpringBoot中,SpringApplication封装了一套Spring应用的启动流程,对用户完全是透明的,这个类原本在Spring中是没有的。

一般来说,默认的SpringApplication执行流程可以满足大部分需求,若是想要干预这过程,可以通过SpringApplication在流程的某些地方开启扩展点来对流程进行扩展,典型的扩展方案就是setXXX方法.

1 @SpringBootApplication
2 public class CodeSheepApplication 
3     public static void main( String[] args ) 
4         // SpringApplication.run( DdsApplication.class, args ); 
5         SpringApplication app = new SpringApplication( DdsApplication.class );
6         app.setXXX( ... ); // 用户自定的扩展在此 !!!
7         app.run( args );
8     
9 

SpringBoot应用中,首先要了解的就是SpringApplication这个类了。

SpringApplication的实例化,在上面这段代码中,使用了自定义SpringApplication,通过一行代码启动SpringBoot应用,也可以自定义SpringApplication的一些扩展。

 1   @SuppressWarnings( "unchecked", "rawtypes" )
 2     public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) 
 3         this.resourceLoader = resourceLoader;
 4         Assert.notNull(primarySources, "PrimarySources must not be null");
 5         this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
 6         this.webApplicationType = WebApplicationType.deduceFromClasspath();
 7         setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
 8         setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
 9         this.mainApplicationClass = deduceMainApplicationClass();
10     

 

1.WebApplicationType是一个枚举Web Application的类型,其类型定义有三种:NONE(不应作为Web应用程序运行,也不应启动嵌入式Web服务器)、SERVLET(基于servlet的Web应用程序)、REACTIVE(响应式Web应用程序)。

NONE:org.springframework.context.annotation.AnnotationConfigApplicationContext

技术图片SERVLET:org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

REACTIVE:org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext

技术图片

WebApplicationType#deduceFromClasspath()的意思是根据Classpath的内容推断WebApplication类型。

 1   static WebApplicationType deduceFromClasspath() 
 2         if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
 3                 && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) 
 4             return WebApplicationType.REACTIVE;
 5         
 6         for (String className : SERVLET_INDICATOR_CLASSES) 
 7             if (!ClassUtils.isPresent(className, null)) 
 8                 return WebApplicationType.NONE;
 9             
10         
11         return WebApplicationType.SERVLET;
12     

ClassUtils是Spring框架所提供关于类级别的工具类,主要供框架内部使用。ClassUtils#isPresent是判断当前class loader中是否存在对应的类型。代码里面关于判断中的方法,为什么所提供的classLoader参数都为空,再进去方法里面看看,发现它是调用了ClassUtils#forName

 1 public static boolean isPresent(String className, @Nullable ClassLoader classLoader)    
 2         try 
 3             forName(className, classLoader);
 4             return true;
 5         
 6         catch (IllegalAccessError err) 
 7             throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
 8                     className + "]: " + err.getMessage(), err);
 9         
10         catch (Throwable ex) 
11             // Typically ClassNotFoundException or NoClassDefFoundError...
12             return false;
13         
14     

再看看ClassUtils#forName,方法的源码很长,它替换Class.forName(),还返回原始类型(例如“int”)和数组类名称(例如“String []”)的Class实例。 此外,它还能够以Java源代码样式解析内部类名(例如“java.lang.Thread.State”而不是“java.lang.Thread $ State”)。

里面调用了ClassUtils#resolvePrimitiveClassName来把给定的类作为基本类(如果适用的话);如果不是基本类型则在缓存找,如果是基本类型则返回;然后接着判断给出的类名是不是一维或多维的整型或字符串数组;接着判断方法传入的classLoader,为空则ClassUtils#getDefaultClassLoader来从当前System中获取默认的classLoader,再使用给定的类加载器返回与具有给定字符串名称的类或接口关联的Class对象→Class.forName(类名, false, 当前默认的classLoader),当找不到Class就会报异常。

 1 public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
 2             throws ClassNotFoundException, LinkageError 
 3 ?
 4         Assert.notNull(name, "Name must not be null");
 5 ?
 6         Class<?> clazz = resolvePrimitiveClassName(name);
 7         if (clazz == null) 
 8             clazz = commonClassCache.get(name);
 9         
10         if (clazz != null) 
11             return clazz;
12         
13 ?
14         // "java.lang.String[]" style arrays
15         if (name.endsWith(ARRAY_SUFFIX)) 
16             String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
17             Class<?> elementClass = forName(elementClassName, classLoader);
18             return Array.newInstance(elementClass, 0).getClass();
19         
20 ?
21         // "[Ljava.lang.String;" style arrays
22         if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) 
23             String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
24             Class<?> elementClass = forName(elementName, classLoader);
25             return Array.newInstance(elementClass, 0).getClass();
26         
27 ?
28         // "[[I" or "[[Ljava.lang.String;" style arrays
29         if (name.startsWith(INTERNAL_ARRAY_PREFIX)) 
30             String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
31             Class<?> elementClass = forName(elementName, classLoader);
32             return Array.newInstance(elementClass, 0).getClass();
33         
34 ?
35         ClassLoader clToUse = classLoader;
36         if (clToUse == null) 
37             clToUse = getDefaultClassLoader();
38         
39         try 
40             return Class.forName(name, false, clToUse);
41         
42         catch (ClassNotFoundException ex) 
43             int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
44             if (lastDotIndex != -1) 
45                 String innerClassName =
46                         name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
47                 try 
48                     return Class.forName(innerClassName, false, clToUse);
49                 
50                 catch (ClassNotFoundException ex2) 
51                     // Swallow - let original exception get through
52                 
53             
54             throw ex;
55         
56     

总结WebApplicationType#deduceFromClasspath(),它通过ClassPath来推断WebApplication的类型,从当前系统默认的ClassLoader获取WebApplication类型相对应类的映射,从而判断WebApplication的类型。

1 setInitializers((Collection)
2 getSpringFactoriesInstances(ApplicationContextInitializer.class));

2.setInitializers(...)使用 SpringFactoriesLoader 查找并加载 classpath下 META-INF/spring.factories文件中所有可用的 ApplicationContextInitializer ---- 用于在ConfigurableApplicationContext#refresh() 之前初始化Spring ConfigurableApplicationContext的回调接口。

 1 # PropertySource Loaders
 2 org.springframework.boot.env.PropertySourceLoader= 3 org.springframework.boot.env.PropertiesPropertySourceLoader, 4 org.springframework.boot.env.YamlPropertySourceLoader
 5 ?
 6 # Run Listeners
 7 org.springframework.boot.SpringApplicationRunListener= 8 org.springframework.boot.context.event.EventPublishingRunListener
 9 ?
10 # Error Reporters
11 org.springframework.boot.SpringBootExceptionReporter=12 org.springframework.boot.diagnostics.FailureAnalyzers
13 ?
14 # Application Context Initializers
15 org.springframework.context.ApplicationContextInitializer=16 org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,17 org.springframework.boot.context.ContextIdApplicationContextInitializer,18 org.springframework.boot.context.config.DelegatingApplicationContextInitializer,19 org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
20 ?
21 # Application Listeners
22 org.springframework.context.ApplicationListener=23 org.springframework.boot.ClearCachesApplicationListener,24 org.springframework.boot.builder.ParentContextCloserApplicationListener,25 org.springframework.boot.context.FileEncodingApplicationListener,26 org.springframework.boot.context.config.AnsiOutputApplicationListener,27 org.springframework.boot.context.config.ConfigFileApplicationListener,28 org.springframework.boot.context.config.DelegatingApplicationListener,29 org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,30 org.springframework.boot.context.logging.LoggingApplicationListener,31 org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
32 ?
33 # Environment Post Processors
34 org.springframework.boot.env.EnvironmentPostProcessor=35 org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,36 org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,37 org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
38 ?
39 # Failure Analyzers
40 org.springframework.boot.diagnostics.FailureAnalyzer=41 org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,42 org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,43 org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,44 org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,45 org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,46 org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,47 org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,48 org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,49 org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,50 org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,51 org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,52 org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,53 org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
54 ?
55 # FailureAnalysisReporters
56 org.springframework.boot.diagnostics.FailureAnalysisReporter=57 org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

3.setListeners(...) 使用SpringFactoriesLoader查找并加载 classpath下 META-INF/spring.factories文件中的所有可用的 ApplicationListener ---- 应用程序事件侦听器实现的接口, 基于Observer设计模式的标准java.util.EventListener 接口。

4.deduceMainApplicationClass()推断并设置main方法的定义类,通过Throwable#getStackTrace()方法返回堆栈中的元素,找出方法名为main的堆栈元素,再根据类名返回对应 的反射类

 1 private Class<?> deduceMainApplicationClass() 
 2         try 
 3             StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
 4             for (StackTraceElement stackTraceElement : stackTrace) 
 5                 if ("main".equals(stackTraceElement.getMethodName())) 
 6                     return Class.forName(stackTraceElement.getClassName());
 7                 
 8             
 9         
10         catch (ClassNotFoundException ex) 
11             // Swallow and continue
12         
13         return null;
14     

 

再来看看SpringBoot应用运行方法 SpringApplication#run

 1 public ConfigurableApplicationContext run(String... args) 
 2         StopWatch stopWatch = new StopWatch();
 3         stopWatch.start();
 4         ConfigurableApplicationContext context = null;
 5         Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 6         configureHeadlessProperty();
 7         SpringApplicationRunListeners listeners = getRunListeners(args);
 8         listeners.starting();
 9         try 
10             ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
11             ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
12             configureIgnoreBeanInfo(environment);
13             Banner printedBanner = printBanner(environment);
14             context = createApplicationContext();
15             exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
16                     new Class[]  ConfigurableApplicationContext.class , context);
17             prepareContext(context, environment, listeners, applicationArguments, printedBanner);
18             refreshContext(context);
19             afterRefresh(context, applicationArguments);
20             stopWatch.stop();
21             if (this.logStartupInfo) 
22                 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
23             
24             listeners.started(context);
25             callRunners(context, applicationArguments);
26         
27         catch (Throwable ex) 
28             handleRunFailure(context, ex, exceptionReporters, listeners);
29             throw new IllegalStateException(ex);
30         
31 ?
32         try 
33             listeners.running(context);
34         
35         catch (Throwable ex) 
36             handleRunFailure(context, ex, exceptionReporters, null);
37             throw new IllegalStateException(ex);
38         
39         return context;
40     

三、总结

获取SpringApplicationListener → 通知Listeners start → 创建参数,配置Environmen → 根据WebApplicationType创建ApplicationContext → 初始化ApplicationContext,设置Environment加载相关配置等 → 通知EnvironmentPrepared,contextLoaded → refresh ApplicationContext → 通知Listeners start context → 完成启动→ 通知runner → 结束

  1. 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件,获取并创建 SpringApplicationRunListener 对象

  2. 然后由 SpringApplicationRunListener 来发出 starting 消息

  3. 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment

  4. 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息

  5. 根据 WebApplicationType 创建 ApplicationContext

  6. 初始化 ApplicationContext,并设置 Environment,加载相关配置等

  7. SpringApplicationRunListener 来发出 contextPrepared 消息,告知SpringBoot 应用使用的 ApplicationContext 已准备OK

  8. 将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext 已装填OK

  9. refresh ApplicationContext,完成IoC容器可用的最后一步

  10. SpringApplicationRunListener 来发出 started 消息

  11. 完成最终的程序的启动

  12. SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了

以上是关于SpringBoot:Spring容器的启动过程的主要内容,如果未能解决你的问题,请参考以下文章

Spring容器启动流程

Springboot启动过程

Dubbo: 在springboot中的启动过程

springboot 容器启动事件

一、spring是咋启动的

springboot启动过程-refresh方法