Featured image of post SpringBoot原理(三):启动流程分析

SpringBoot原理(三):启动流程分析

关于SpringBoot启动流程地详细源码分析

Spring Boot启动流程

开始之前


SpringBoot启动类

  • 运行SpringBoot 众所周知,当我们想运行一个SpringBoot项目时,只需要引入相关依赖,再编写一个启动类,并给这个启动类标上@SpringBootApplication注解,就可以启动项目了 ,代码如下所示:
1
2
3
4
5
6
7
8
@SpringBootApplication
public class TestApplication {

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

}

main方法中仅仅调用了SpringApplication.run(TestApplication.class, args);就完成了SpringBoot启动流程,非常简单。


  • @SpringBootApplication注解源码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};//等同于EnableAutoConfiguration注解的exclude属性

	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};//等同于EnableAutoConfiguration注解的excludeName属性

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};//等同于ComponentScan注解的basePackages属性

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};//等同于ComponentScan注解的basePackageClasses属性
	
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;//等同于SpringBootConfiguration注解的元注解@Configuration的proxyBeanMethods属性
}

@SpringBootApplication注解有三个元注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan@SpringBootApplication注解的属性有别名注解修饰, 通过之前的文章,我们已经知道了别名注解@AliasFor的作用,并且已经分析了@EnableAutoConfiguration注解


  • SpringApplication.run();的源码如下:
1
2
3
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }

执行了run方法,primarySource入参是我们传入的启动类的class,run方法源码如下:

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

创建了一个类SpringApplication的实例,入参primarySources使我们传入的启动类的class数组,并且调用了该实例的run方法,入参为args,args是我们自定义传入的参数。
primarySources使用如下方式存储到实例的对应字段,像这样this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));


  • SpringApplication的实例的run方法就是SpringBoot启动的核心实现,源码及注释如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
	public ConfigurableApplicationContext run(String... args) {
		//StopWatch用于统计run启动过程花了多少时间
		StopWatch stopWatch = new StopWatch();
		//开始计时
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		//exceptionReporters存储异常报告器
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		//配置java.awt.headless,使服务器检测不到显示器仍正常启动
		configureHeadlessProperty();
		//【1】从META-INF/spring.factories中获取SpringApplicationRunListener的集合
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//【2】遍历listener开启监听,实际类型为EventPublishingRunListener
		listeners.starting();
		try {
			//创建ApplicationArguments对象,封装了自定义args参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//准备environment变量
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			//如果JVM系统属性spring.beaninfo.ignore为空,则从environment中获取(可能是在application.yml中),
			// 默认为true,并赋值给JVM系统属性spring.beaninfo.ignore,这个与内省机制和javaBean有关,
			// javaBean的规范有无参构造、getter、setter等
			configureIgnoreBeanInfo(environment);
			//控制台打印SpringBoot的bannner标志,可以配置不打印,获取的是banner.txt和banner.gif,就是SpringBoot启动时打印的那个logo
			Banner printedBanner = printBanner(environment);
			//【2】根据webApplicationType类型加载不同的contenxt,如果是servlet则加载AnnotationConfigServletWebServerApplicationContext
			context = createApplicationContext();
			//【3】从我们熟悉的缓存cache获取SpringBootExceptionReporter的类路径集合,循环通过反射创建实例
			//cache是个静态变量,没有就从META-INF/spring.factories加载
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//为刚创建的AnnotationConfigServletWebServerApplicationContext进行一些初始化工作
			//1.赋值context的environment、reader、scanner,都与传入的environment有关
			//2.做一些后置处理,比如BeanFactory注册一个单例org.springframework.context.annotation.internalConfigurationBeanNameGenerator
			//设置context的resourceLoader属性
			//3.【4】在context刷新前调用各个ApplicationContextInitializer的初始化方法,从我们熟悉的缓存cache中获取,没有则从META-INF/spring.factories加载
			//4.注册springApplicationArguments(自定义入参applicationArguments)和springBootBanner(logo标志),所谓注册就是存入singletonObjects这个map,
			//map的key为springApplicationArguments、springBootBanner字符串,值就是我们传入的applicationArguments、printedBanner
			//5.【5】加载从最外层传入的标有@SpringBootApplication注解的启动类
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//刷新context
			//1.注册jvm的hook钩子,Runtime.getRuntime().addShutdownHook(this.shutdownHook);,当所有钩子执行完才关闭jvm
			//主要逻辑是context.doClose(),包括clear各种缓存,清除beanFactory,关闭webServer等
			//2.【6】****
			refreshContext(context);
			//刷新context的后置处理,这里源码是空方法,无实现逻辑
			afterRefresh(context, applicationArguments);
			//停止计时,可以通过stopWatch.getTotalTimeMillis()获取耗时
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			//推送ApplicationStartedEvent事件,标志context已刷新完成,所有bean实例都已加载完成
			listeners.started(context);
			//获取ApplicationRunner和CommandLineRunner执行对应的run方法
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			//推送ApplicationFailedEvent事件,标志SpirngBoot启动失败
			//exceptionReporters用于报告异常
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			//推送ApplicationReadyEvent事件,标志SpringApplication启动成功,可以接受请求了
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

run方法是SpringBoot启动的核心函数,每行代码都有许多注释,请根据自己的源码调试环境进行对照阅读

run方法源码分析

该章节会对上面run方法带有【】的注释进行重点分析

  • 【1】SpringApplicationRunListeners listeners = getRunListeners(args);,注释为从META-INF/spring.factories中获取SpringApplicationRunListener的集合

具体逻辑是从一个缓存cache中查询,没有则从META-INF/spring.factories中加载,是不是对这个逻辑比较熟悉呢?我们已多次遇到

getRunListeners的源码如下:

1
2
3
4
5
	private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
	}

new SpringApplicationRunListeners只是将listeners参数赋值并返回,核心在于getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),传入了 一个SpringApplicationRunListener.class,一个types具体为SpringApplication.classString[].class的数组, 还传入了this,this就是SpringApplication实例,最后传入自定义args参数,记住这个几个参数,后面会通过这几个参数创建SpringApplicationRunListener实例,细节源码实现如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
   private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   	//获取classLoader
   	ClassLoader classLoader = getClassLoader();
   	// Use names and ensure unique to protect against duplicates
   	//set进行去重,值为完成的类路径
   	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   	//根据反射创建对应实例
   	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   	//从小到大排序,如果1个类实现了PriorityOrdered接口,另一个类没有实现,那么实现的类排前面
   	//如果都实现了PriorityOrdered接口,或者都没有实现,那么就要根据order值从小到大排序。order值的获取方式有3种,
   	// 并且有优先级顺序,Ordered接口(@PriorityOrdered是继承Ordered接口的)> @Order注解 > @Priority,程序只会获取优先级最高的order值。
   	// 比如既实现了Ordered接口又实现了@Order注解,程序只会取Ordered接口的order值。如果都没有实现,就返回Integer.MAX_VALUE
   	AnnotationAwareOrderComparator.sort(instances);
   	return instances;
   }

该方法逻辑都比较简单,通过SpringFactoriesLoader.loadFactoryNames(type, classLoader)获取我们需要的类路径集合,其源码如下:

1
2
3
4
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

此时factoryTypeName的值是org.springframework.boot.SpringApplicationRunListener,我们接着往下看loadSpringFactories的源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

该部分代码稍多,但逻辑依赖较简单。通过MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);,从缓存cache获取一个MultiValueMap<String, String>,key为对应的classloader, MultiValueMap<String, String>的value实际类型是一个List,这里的String应该表示链表的第一个元素

如果没有获取到则从META-INF/spring.factories加载,classLoader.getResources("META-INF/spring.factories")该方法会获得所有依赖jar包中,具有META-INF/spring.factories配置文件的jar文件URI, 并进行遍历,如何获取到所有jar包的META-INF/spring.factorie呢?该逻辑较为复杂,感兴趣请自行调试

查询完成后存入缓存cache,并返回Map<String, List<String>>这个结构的数据,该结构的key为类路径,此时key为org.springframework.boot.SpringApplicationRunListener ,我们通过查看SpringBoot源码META-INF/spring.factories中具体的Listeners的具体配置如下: 对应路径在spring-boot-build/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories

1
2
3
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

原来最终获取的集合中只有一个EventPublishingRunListener实例,断点调试也是如此。到此我们了解了run方法获取的Listeners到底是什么,
同时我们也能知道类SpringFactoriesLoader的静态变量cache缓存的数据与META-INF/spring.factories的关系,cache的定义为:

1
2
//嵌套map的value的实际类型为List
Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

  • 【2】listeners.starting();向各个监听者推送事件
    EventPublishingRunListener是Springboot为我们提供的一套事件发布机制,向各个Listener推送SpringBoot启动事件,这里用到了观察者模式。
    run方法不同阶段我们知道会推送ApplicationStartingEventApplicationEnvironmentPreparedEvent等等事件,它们代表了SpringBoot启动的不同阶段

事件到底推送给谁了呢,我们通过listeners.starting();具体分析一下,该方法最终调用了EventPublishingRunListener实例的starting方法,源码如下:

1
2
3
	public void starting() {
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}

创建并传入了对应阶段的事件,调用了EventPublishingRunListener实例的initialMulticaster属性的multicastEvent方法,源码如下:

1
2
3
    public void multicastEvent(ApplicationEvent event) {
        this.multicastEvent(event, this.resolveDefaultEventType(event));
    }

这里通过event创建了ResolvableType实例,它的type属性里面存有event.getClass(),继续查看内层源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Executor executor = this.getTaskExecutor();
        Iterator var5 = this.getApplicationListeners(event, type).iterator();

        while(var5.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var5.next();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

    }

该段代码逻辑比较简单,this在这里指代initialMulticaster属性,调用getApplicationListeners获取Listeners并遍历向它们推送事件, getApplicationListeners方法这里不贴代码,只说一下具体逻辑,根据eventTypeapplication生成一个key(不要忘记,evenType存有事件的class),从initialMulticaster属性的名为retrieverCache`的map 缓存中获取Listeners

读到这里,有同学就要有新的疑问了,这个map缓存又是哪儿来的,还记得前面我们通过反射EventPublishingRunListener实例,有参构造如下:

1
2
3
4
5
6
7
8
	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

原来它是从SpringApplication实例中获取的,这里的入参一开始往下传递的(还记得最开始的四个入参吗?一个SpringApplicationRunListener.class,一个types具体为SpringApplication.classString[].class的数组, 还传入了this,this就是SpringApplication实例,最后传入自定义args参数),SpringApplication实例的Listeners是在创建时从缓存cache中获取,源码可以自己跳转过去查看, 这里看一下META-INF/spring.factories具体配置了哪些Listeners,配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
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.LiquibaseServiceLocatorApplicationListener

一共10个Listener,调试创建SpringApplication实例的构造器中就是获取的这些Listener,从配置中可以看出这些Listener是ApplicationListener接口实现类, 实现了onApplicationEvent方法

回到multicastEvent方法,现在逻辑就比较清晰了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Executor executor = this.getTaskExecutor();
        Iterator var5 = this.getApplicationListeners(event, type).iterator();

        while(var5.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var5.next();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

    }

getApplicationListeners获取对该事件感兴趣(通过cacheKey过滤)的Listener集合,遍历并调用this.invokeListener(listener, event);, 最终调用的是listener.onApplicationEvent(event);,因为所有Listener都实现了ApplicationListener接口的onApplicationEvent的方法

小结:自此,我们能够清晰的知道EventPublishingRunListener的事件推送机制是如何将SpringBoot启动过程中每个阶段的标志性事件发送 给了哪些Lisener,只是我们并没有对具体的Listener接收到对应事件所做的操作进行分析,即分析onApplicationEvent方法,感兴趣的朋友请自行调试阅读

上一篇文章中我们学习到了模板方法模式,这里我们又学习到了观察者模式


  • 回到run方法中,【2】context = createApplicationContext();根据webApplicationType的值加载具体的ConfigurableApplicationContext实例 createApplicationContext()的源码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

该段代码逻辑非常简单,这里this指代SpringApplication实例,代码中使用switch根据this.webApplicationType的值获取对应类路径的class, 并通过反射进行创建实例。当为枚举为SERVLET时加载AnnotationConfigServletWebServerApplicationContext,当枚举为REACTIVE时加载 AnnotationConfigReactiveWebServerApplicationContext,是其它则加载AnnotationConfigApplicationContext

SpringBoot一般是SERVLET,SpringBoot实例是如何确定webApplicationType的值呢?我们来看一下SpringApplication的构造方法源码,如下所示:

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

首先看一下倒数第二行,那里就是上一节我们分析的10个Listener来源,再看一下第四行this.webApplicationType = WebApplicationType.deduceFromClasspath();webApplicationType的值通过调用deduceFromClasspath方法获取的,deduceFromClasspath方法源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	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;
	}

该段代码逻辑是调用isPresent,最终调用Class.forName判断对应的类是否存在,若存在类DispatcherHandler且不存在类DispatcherServlet且 不存在类ServletContainer则返回REACTIVE;遍历加载类ServletConfigurableWebApplicationContext,有一个不存在则返回NONE,前面都不满足 时返回SERVLET


  • 【3】exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context); 熟悉的代码,从缓存cache中获取异常报告器SpringBootExceptionReporter集合,我们直接看一下META-INF/spring.factories具体配置,如下所示:
1
2
3
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

集合中原来只有一个FailureAnalyzers实例,我们直接来到调用的地方run方法的catchhandleRunFailure(context, ex, exceptionReporters, listeners);, 内层方法调用了reportFailure(exceptionReporters, exception);,reportFailure方法的源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {
		try {
			for (SpringBootExceptionReporter reporter : exceptionReporters) {
				if (reporter.reportException(failure)) {
					registerLoggedException(failure);
					return;
				}
			}
		}
		catch (Throwable ex) {
			// Continue with normal handling of the original failure
		}
		if (logger.isErrorEnabled()) {
			logger.error("Application run failed", failure);
			registerLoggedException(failure);
		}
	}

遍历异常报告器集合exceptionReporters,调用reporter.reportException(failure)如果返回true会把异常加入到一个集合中,我们知道异常报告器集合只有一个实例, 那就是FailureAnalyzers实例,reportException方法源码如下:

1
2
3
4
	public boolean reportException(Throwable failure) {
		FailureAnalysis analysis = analyze(failure, this.analyzers);
		return report(analysis, this.classLoader);
	}

analyze方法传入了异常failureFailureAnalyzers实例自身的分析器集合analyzers,我们先看一下分析器集合怎么来的,FailureAnalyzers实例的构造器如下:

1
2
3
4
5
6
	FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
		Assert.notNull(context, "Context must not be null");
		this.classLoader = (classLoader != null) ? classLoader : context.getClassLoader();
		this.analyzers = loadFailureAnalyzers(this.classLoader);
		prepareFailureAnalyzers(this.analyzers, context);
	}

第三行代码获取了分析器集合,也是从缓存cache中获取的,类型为FailureAnalyzer,直接看META-INF/spring.factories的配置,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.context.properties.NotConstructorBoundInjectionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

好家伙,有14个子类实例,回到reportException方法:

1
2
3
4
	public boolean reportException(Throwable failure) {
		FailureAnalysis analysis = analyze(failure, this.analyzers);
		return report(analysis, this.classLoader);
	}

我们知道了传入的分析器集合到底是什么,analyze方法的源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
	private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) {
		for (FailureAnalyzer analyzer : analyzers) {
			try {
				FailureAnalysis analysis = analyzer.analyze(failure);
				if (analysis != null) {
					return analysis;
				}
			}
			catch (Throwable ex) {
				logger.debug(LogMessage.format("FailureAnalyzer %s failed", analyzer), ex);
			}
		}
		return null;
	}

遍历调用分析器的analyze方法获取分析结果FailureAnalysis,只要不为空则直接返回,分析结果都为空的话则返回null,FailureAnalysis分析结果有哪些字段呢?

1
2
3
4
5
6
7
8
public class FailureAnalysis {

	private final String description;

	private final String action;

	private final Throwable cause;
}

很好理解,详细描述、动作、原因(即异常)。再次回到reportException方法:

1
2
3
4
	public boolean reportException(Throwable failure) {
		FailureAnalysis analysis = analyze(failure, this.analyzers);
		return report(analysis, this.classLoader);
	}

获取到分析结果后,调用了report方法,源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
	private boolean report(FailureAnalysis analysis, ClassLoader classLoader) {
		List<FailureAnalysisReporter> reporters = SpringFactoriesLoader.loadFactories(FailureAnalysisReporter.class,
				classLoader);
		if (analysis == null || reporters.isEmpty()) {
			return false;
		}
		for (FailureAnalysisReporter reporter : reporters) {
			reporter.report(analysis);
		}
		return true;
	}

第一行代码我们已非常熟悉,获取分析结果报告器的集合,我们直接看META-INF/spring.factories的配置,如下所示:

1
2
3
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

集合中只有一个实例LoggingFailureAnalysisReporter

if中判断分析结果为null或分析结果报告器为空则返回false(返回true会把异常加入到一个List),遍历报告器调用对应的report方法,最终返回true,看一下LoggingFailureAnalysisReporter报告器 的report方法源码:

1
2
3
4
5
6
7
8
	public void report(FailureAnalysis failureAnalysis) {
		if (logger.isDebugEnabled()) {
			logger.debug("Application failed to start due to an exception", failureAnalysis.getCause());
		}
		if (logger.isErrorEnabled()) {
			logger.error(buildMessage(failureAnalysis));
		}
	}

好家伙,逻辑异常简单,如果日志组件开启了Debug级别,则控制台打印一段信息,如果日志组件开启了Error级别,也打印一段信息,打印内容由buildMessage方法生成, buildMessage做了什么呢,往下看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
	private String buildMessage(FailureAnalysis failureAnalysis) {
		StringBuilder builder = new StringBuilder();
		builder.append(String.format("%n%n"));
		builder.append(String.format("***************************%n"));
		builder.append(String.format("APPLICATION FAILED TO START%n"));
		builder.append(String.format("***************************%n%n"));
		builder.append(String.format("Description:%n%n"));
		builder.append(String.format("%s%n", failureAnalysis.getDescription()));
		if (StringUtils.hasText(failureAnalysis.getAction())) {
			builder.append(String.format("%nAction:%n%n"));
			builder.append(String.format("%s%n", failureAnalysis.getAction()));
		}
		return builder.toString();
	}

只进行了字符串的拼接

一般来说这两段信息都会打印,是否对这个打印的日志有些熟悉呢,当我们SpringBoot启动报错时,不就是打印的这种格式的信息嘛,以APPLICATION FAILED TO START开头

小结: 我们在run方法中获取(META-INF/spring.factories中配置)了SpringBootExceptionReporter异常报告器实例集合,实际只有一个 FailureAnalyzers实例,它在创建时会从META-INF/spring.factories配置中加载14个分析器,最后使用时传入异常,遍历分析器获取分析结果,报告时分析结果时会获取 会获取分析结果报告器实例FailureAnalysisReporter集合,实际只有一个LoggingFailureAnalysisReporter子类,它的逻辑是会按照一定格式打印这个分析结果


  • 【4】prepareContext(context, environment, listeners, applicationArguments, printedBanner);context做一些初始化工作,context的类型为ConfigurableApplicationContext,一般SpringBoot是SERVLET类型,即context实际类型是 AnnotationConfigServletWebServerApplicationContext ,我们在通过反射创建该实例时调用的是无参构造方法,源码如下:
1
2
3
4
	public AnnotationConfigServletWebServerApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

初始化了自身的readerscanner,分别调用了AnnotatedBeanDefinitionReader(this)方法和ClassPathBeanDefinitionScanner(this)方法,都传入context自己。
按照方法名理解这两个变量,一个是注解bean的阅读器,一个是classpathBean的扫描器

注意:调用这个构造方法时,一定会先调用父类的构造方法,源码如下:

1
2
3
4
5
    public GenericApplicationContext() {
        this.customClassLoader = false;
        this.refreshed = new AtomicBoolean();
        this.beanFactory = new DefaultListableBeanFactory();
    }

我们run方法中context间接继承了一个GenericApplicationContextbeanFactory,这个属性是极其重要的字段,此时已经有了一个默认值

我们来看一下AnnotatedBeanDefinitionReader(this)方法,源码如下:

1
2
3
    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry, getOrCreateEnvironment(registry));
    }

接受的入参是一个BeanDefinitionRegistry,为什么传入的是context呢?原来context实现了AnnotationConfigRegistry接口的 registerscan方法,稍微看一下这两个方法的源码:

1
2
3
4
	public final void register(Class<?>... annotatedClasses) {
		Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
		this.annotatedClasses.addAll(Arrays.asList(annotatedClasses));
	}

将注解类的class集合全部加入到context自身的annotatedClasses的set集合中

1
2
3
4
	public final void scan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		this.basePackages = basePackages;
	}

this(registry, getOrCreateEnvironment(registry));调用了this()方法,我们先看一下getOrCreateEnvironment的源码:

1
2
3
4
    private static Environment getOrCreateEnvironment(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        return (Environment)(registry instanceof EnvironmentCapable ? ((EnvironmentCapable)registry).getEnvironment() : new StandardEnvironment());
    }

首先判断context是否是EnvironmentCapable类型,通过源码查看AnnotationConfigServletWebServerApplicationContext间接实现了EnvironmentCapable接口的 getEnvironment()方法,即这里获取到了Environment,接着看一下this()指代的AnnotatedBeanDefinitionReader的有参构造源码:

1
2
3
4
5
6
7
8
9
    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        this.beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
        this.scopeMetadataResolver = new AnnotationScopeMetadataResolver();
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        Assert.notNull(environment, "Environment must not be null");
        this.registry = registry;
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, (ResourceLoader)null);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

初始化了reader的一些值,context即这里的registryenvironment也被使用,此时的environment只是一个StandardEnvironment的普通变量

还初始化了一个conditionEvaluator,它的作用是根据条件注解的实现类判断是否需要加载对应的配置类到容器中,它提供了一个shouldSkip方法,我们在后面分析

上面是对【2】的一些扩展,大概说明【2】到底创建了一个什么样的context,beanFactory最初是怎么来的,此时context还有许多字段未初始化, 我们知道run方法的核心逻辑就是围绕构造这个context

【4】prepareContext(context, environment, listeners, applicationArguments, printedBanner);闪亮登场,它会为我们的context做进一步的初始化, prepareContext方法的源码如下:

postProcessApplicationContextcontext进行了一些处理,源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
	protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
		if (this.addConversionService) {
			context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
		}
	}

该段代码中this指代SpringApplication实例,创建这个对象时beanNameGeneratorresourceLoader其实是null,因此最终只会执行第三个if逻辑, 但我们仍可进行分析,加深我们对context的理解。

此时beanFactory还是默认值DefaultListableBeanFactory实例,该实例间接继承DefaultSingletonBeanRegistry,context.getBeanFactory().registerSingleton()按 语义表明会向contextbeanFactory中注册一个单例,实际就是操作继承的这些ConcurrentHashMap,几乎每部分操作都会加锁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
    private final Set<String> registeredSingletons = new LinkedHashSet(256);
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
    private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap(16));
    @Nullable
    private Set<Exception> suppressedExceptions;
    private boolean singletonsCurrentlyInDestruction = false;
    private final Map<String, Object> disposableBeans = new LinkedHashMap();
    private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap(16);
    private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap(64);
    private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap(64);
}

返回到上一层代码中,实际我们最终只调用了ontext.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());, getSharedInstance方法的源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
	public static ConversionService getSharedInstance() {
		ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
		if (sharedInstance == null) {
			synchronized (ApplicationConversionService.class) {
				sharedInstance = ApplicationConversionService.sharedInstance;
				if (sharedInstance == null) {
					sharedInstance = new ApplicationConversionService();
					ApplicationConversionService.sharedInstance = sharedInstance;
				}
			}
		}
		return sharedInstance;
	}

经典的单例模式,ApplicationConversionService的作用是进行两个类型的转换,相当于一个转换器,不用关心具体实现

回到prepareContext中,applyInitializers(context);方法获取了初始化器Initializers集合,并遍历调用它们的初始化方法initialize,这个集合是在 SpringApplication实例初始化时设置的,从缓存cache中获取,META-INF/spring.factories的配置如下:

1
2
3
4
5
6
7
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

一个有五个实现,具体不详细分析

在源码发现很多从地方META-INF/spring.factories读取配置类并执行,这是SpringBoot提供的SPI(Service Provider Interface)机制,也称服务提供者接口。

我们能对任意已支持的接口进行扩展,比如配置一个类,或配置一个初始化类,它会在SpingBoot启动过程对应的地方加载执行

java也有SPI机制,比如加载数据库驱动


SpringBoot启动流程图

SpringBoot启动流程主要就是构建context容器和context容器的beanFactory,这些都包含在一个run()方法中,参考流程图如下: SpringBoot启动流程

  • SpringApplication.run():启动流程的入口
  • StopWatch.start():
  • SpringApplicationRunListeners.starting():
  • prepareEnviroment():
  • printBanner():
  • createApplicationContext():
  • prepareContext():
  • refreshContext():
  • afterRefresh():
  • StopWatch.stop():
  • SpringApplicationRunListeners.started()
  • callRunners():
  • SpringApplicationRunListeners.running():
  • return context

小结

我们对SpringBoot的启动流程进行了初步分析,能够了解前面一部分细节,且能够熟练使用SPI机制包括配置对象、配置初始化类、配置Listener等

Please call the seeds under the diligent.
Built with Hugo
主题 StackJimmy 设计