SpringBoot:Spring容器的启动过程
在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 → 创建参数,配置Environment → 根据WebApplicationType创建ApplicationContext → 初始化ApplicationContext,设置Environment加载相关配置等 → 通知EnvironmentPrepared,contextLoaded → refresh ApplicationContext → 通知Listeners start context → 完成启动→ 通知runner → 结束
-
通过
SpringFactoriesLoader
加载META-INF/spring.factories
文件,获取并创建SpringApplicationRunListener
对象 -
然后由
SpringApplicationRunListener
来发出 starting 消息 -
创建参数,并配置当前 SpringBoot 应用将要使用的
Environment
-
完成之后,依然由
SpringApplicationRunListener
来发出environmentPrepared
消息 -
根据
WebApplicationType
创建ApplicationContext
-
初始化
ApplicationContext
,并设置Environment
,加载相关配置等 -
由
SpringApplicationRunListener
来发出contextPrepared
消息,告知SpringBoot 应用使用的ApplicationContext
已准备OK -
将各种 beans 装载入
ApplicationContext
,继续由SpringApplicationRunListener
来发出 contextLoaded 消息,告知 SpringBoot 应用使用的ApplicationContext
已装填OK -
refresh ApplicationContext,完成IoC容器可用的最后一步
-
由
SpringApplicationRunListener
来发出 started 消息 -
完成最终的程序的启动
-
由
SpringApplicationRunListener
来发出 running 消息,告知程序已运行起来了