SpringBoot启动

SpringBoot因为内置了tomcat或jetty服务器,不需要直接部署War文件,所以SpringBoot的程序起点是一个普通的主函数。主函数如下:

1
2
3
4
5
6
@SpringBootApplication
public class SpringbootStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootStudyApplication.class, args);
}
}

整个SpringBoot的启动过程其实都是通过@SpringBootApplication注解和SpringApplication.run方法来实现的。

整个启动的过程可以概括为:

  1. 读取所有依赖的META-INF/spring.factories文件,该文件指明了哪些依赖可以被自动加载。
  2. 根据importSelector类选择加载哪些依赖,使用conditionOn系列注解排除掉不需要的配置文件
  3. 将剩余的配置文件所代表的bean加载到IOC容器中。

比如spring-boot-2.1.8RELEASE.jar中的spring.factories文件的内容是整个样子的(节选):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

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

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# 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.web.context.ServerPortInfoApplicationContextInitializer

这个文件中的内容最终会被解析为Map<K,List<V>>这种格式。键和值都是一个类的全限定名。

跟踪源码,探索原理

我们从这段代码开始跟踪SpringApplication.run(SpringbootStudyApplication.class, args);

这段代码经过重重调用最终来到了:

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

这个方法完成了SpringApplication的实例化。具体的实例化过程如下:

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

整个Application的实例化过程中,下面这两行代码比较关键。

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

方法的具体实现如下:

1
2
3
4
5
6
7
8
9
10
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//获取FactoryClass的全限定名
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//直接利用反射实例化对象
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

这里的loadFactoryNames方法,其实就是从我们之前提到的spring.factories读取数据,然后以Map的形式进行存储的,loadFactoryNames就是从这个Map`中取数据(类的全限定名)。

读取spring.factories的具体实现:

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 = cache.get(classLoader);
if (result != null) {
return result;
} try {
Enumeration<URL> urls = (classLoader != null ?
// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories" 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);
}

// output
// like:
// org.springframework.boot.autoconfigure.EnableAutoConfiguration -> {LinkedList@1446} size = 118
// |
// |- org.springframework.boot.autoconfigure.EnableAutoConfiguration

}

拿到需要加载的类的全限定名之后,就通过反射进行实例化,然后返回。在Application的构造器中,拿到这些对象后,存入到List中。

1
2
private List<ApplicationContextInitializer<?>> initializers;
private List<ApplicationListener<?>> listeners;

到这个地方,我们已经拿到了所有的依赖类,那么SpringBoot是如何进行自动配置的呢?

其实前面我们看到的源码都是SpingApplication的实例化,整个实例化过程就完成了依赖类信息,而run方法其实就是完成装配的。具体的看下面的分析:

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
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//关键代码
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

run方法中最关键的就是refreshContext(context);。它实际上是调用了refresh方法,这个方法对应读过Spring源码的同学不会陌生。而我们bean的装配过程实际上就是由它完成的。

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
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

其中invokeBeanFactoryPostProcessors会解析@import注解,并根据@import的属性进行下一步操作。

1
2
3
4
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// 省略
}
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
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<>();

if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

// 记录是否是定义类的 Processor 或者普通的 Processor

// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
// Separate between BeanDefinitionRegistryPostProcessors that implement
// PriorityOrdered, Ordered, and the rest.
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

// ...
// 应用 Bean 定义类的后置处理器
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
// ...

}

private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}

invokerBeandefintionRegistryPostProcessors函数对每一个定义类的后置处理器分别进行应用,@Configure的解析就在这个函数中。

1
2
3
4
5
6
7
// 从注册表中的配置类派生更多的bean定义
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ...
this.registriesPostProcessed.add(registryId);
// Build and validate a configuration model based on the registry of Configuration classes.
processConfigBeanDefinitions(registry);
}

进入最关键的类ConfigurationClassPostProcessor,这个类用户来注册所有的@Configure@Bean, 它的processConfigbeanDefinitions函数如下:

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
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();

// 记录所有候选的未加载的配置

// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}

// 按照 Ordered 对配置进行排序

// 加载自定义 bean 名命策略

if (this.environment == null) {
this.environment = new StandardEnvironment();
}

// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 解译候选集
parser.parse(candidates);
parser.validate();

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Read the model and create bean definitions based on its content
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);

// ...
} while (!candidates.isEmpty());

// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}

在解释候选集parser.parse(candidates)中,会调用suorceClass=doProcessConfigurationClass(configClass,sourceClass)方法依次解析注解,得到所有的候选集。该方法顺次解析@PropertySource@componentScan,@Import,@importResource,@Bean父类。

解析完成之后,会找到所有以 @PropertySource@ComponentScan@Import@ImportResource@Bean 注解的类及其对象,如果有 DeferredImportSelector,会将其加入到 deferredImportSelectorHandler 中,并调用 this.deferredImportSelectorHandler.process() 对这些 DeferredImportSelector 进行处理。

实际上,在 spring boot 中,容器初始化的时候,主要就是对 AutoConfigurationImportSelector 进行处理。

Spring 会将 AutoConfigurationImportSelector 封装成一个 AutoConfigurationGroup,用于处理。最终会调用 AutoConfigurationGroupprocess 方法。

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
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 主要通过该函数找到所有需要自动配置的类
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
return configurations;
}

如上,我们可以看到 process 最终调用了我们非常熟悉的函数 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());,该方法以 EnableAutoConfiguration 类为键(org.springframework.boot.autoconfigure.EnableAutoConfiguration),取得所有的值。

在该函数中,还会调用 configurations = filter(configurations, autoConfigurationMetadata) 方法,将不需要的候选集全部排除。(该方法内部使用 AutoConfigurationImportFilter 的实现类排除)。

我们看一个常见的 configuration,即 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,这个类中有大量的 @Bean 注解的方法,用来产生 bean,如下:

1
2
3
4
5
6
7
8
@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
adapter.setIgnoreDefaultModelOnRedirect(
this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
return adapter;
}

spring 通过读取所有所需要的 AutoConfiguration,就可以加载默认的上下文容器,实现自动注入。

SpringBoot常用注解简介

  1. @Configuration

    作用于类,用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法会被AnnotationConfigApplicationContextAnncationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化spring容器。

  2. @ComponentScan

    该注解会扫描@Controller,@Service,@Repository,@component注解到类到spring容器中。

  3. @SpringBootApplication

    该注解包含了@ComponentScan注解,所以在使用中我们可以通过@SpringBootApplication注解的scanBasepackages属性进行配置。

  4. @Conditional

    该注解作用于类,它可以根据代码中的条件装载不同的bean,在设置注解之前类需要实现Condition接口,然后对该实现接口的类设置是否装载条件。

  5. @Import

    通过导入的方式实现吧实例加入spring容器中,可以在需要时间没有被spring管理的类导入至Spring容器中。

  6. @ImportResource

    @Import类似,区别就是该注解导入的是配置文件。

  7. Component

    该注解是一个元注解,意思是可以注解其它类注解,如@Controller @Service @Repository。带此注解的类被看作组件,当使用基于注解的配置和类路径扫描的时候,这些类就会被实例化。

  8. @SpringBootApplication

    这个注解是Spring Boot最核心的注解,用在SpringBoot的主类上,标识这是一个Spring Boot应用。用来开启Spring Boot的各项能力,实际上这个注解@Configuration,@EnableAutoConfiguration,@ComponentScan三个注解的组合.由于这些注解一般都是一起使用的。

  9. @EnableAutoConfiguration

    允许Spring Boot自动配置注解,开启这个注解之后,Spring Boot就能根据当前类路径下的包或者类来配置Spring Bean。配置信息是从META-INF/spring.factories加载的。