Spring Boot 源码分析(一)
sschrodinger
2019/05/28
Spring boot 简介
Spring boot 采用约定大于配置的思想对应用程序进行默认配置,减少了大量的配置时间。
Spring Boot 包含的特性如下:
- 创建可以独立运行的 Spring 应用
- 直接嵌入 Tomcat 或 Jetty 服务器,不需要部署 WAR 文件
- 提供推荐的基础 POM 文件来简化 Apache Maven 配置
- 尽可能的根据项目依赖来自动配置 Spring 框架
- 提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查
- 没有代码生成,也没有 XML 配置文件
通过 Spring Boot,创建新的 Spring 应用变得非常容易,而且创建出的 Spring 应用符合通用的最佳实践。只需要简单的几个步骤就可以创建出一个 Web 应用。对于 spring boot 的创建,参见 spring 官方启动器。
Spring boot 加载
因为 Spring boot 内置了 Tomcat 或 Jetty 服务器,不需要直接部署 War 文件,所以 spring boot 的程序起点为一个普通的主函数,主函数类如下:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
看似和普通的程序没有什么区别,其实他最主要的步骤都通过注解 @SpringBootApplication
和方法 SpringApplication.run()
完成了。
其实所有的依赖在这一步就可以完成注入,主要的步骤是 ==spring 读取所有依赖中 META-INF/spring.factories
文件,该文件指明了哪些依赖可以被自动加载,然后根据 ImportSelector
类选择加载哪些依赖,使用 ConditionOn
系列注解排除掉不需要的配置文件,最后将剩余的配置文件所代表的 bean 加载到 IoC 容器中==。
META-INF/spring.factories
文件的读取
spring 所有的配置文件都分布在 jar 包的 META-INF/spring.factories
中,如下:
每一个 META-INF/spring.factories
为一个键值对列表,截取了部分 spring-boot
jar 包下的 spring.factories
文件,如下:
# 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>>
的格式,即一个 MultiValueMap
(一个键对应多个值的 map),键和每一个值都是一个类的权限定名。
主函数中,只有一个方法,即 SpringApplication.run()
,如下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
我们来看一个读取 META-INF/spring.factories
的例子。
在程序初始化时,即运行 new SpringApplication()
时,就会读取一次配置文件,我们来分析是如何读取配置文件的。
SpringApplication.run(DemoApplication.class, args)
方法是 SpringApplication
类提供的一个静态方法,调用的是 run(new Class<?>[] { primarySource }, args)
方法。如下:
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
其中,读取配置文件的操作就发生在 SpringApplication
类的实例化过程中。实例化的代码如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 把初始化的初始化器存到数组中
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 把初始化的监听器加入到数组中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 获得主类
this.mainApplicationClass = deduceMainApplicationClass();
}
在 getSpringFactoriesInstances(ApplicationContextInitializer.class))
中 spring 读取了spring.factories
文件。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 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;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
这里一定要记住 loadFactoryNames((Class<?> factoryClass, @Nullable ClassLoader classLoader)
这个函数,该函数是 SpringFactoriesLoader
类提供的一个静态方法,用于读取 factoryClass
的类全限定名这个键在 spring-factoies
中的值列表。
loadSpringFactories
读取所有的配置文件中的属性的键值对,并把缓存到一个 cache
中,在需要读取的时候进行读取。
在运行完 loadSpringFactories(classLoader)
执行语句后,在如上的 spring.factories
实例下,cache
可能变成如下形式:
cache :
key = classloader.toString()
->
value = {
"org.springframework.boot.env.PropertySourceLoader"
-> ["org.springframework.boot.env.PropertiesPropertySourceLoader", "
org.springframework.boot.env.YamlPropertySourceLoader"],
"org.springframework.boot.SpringApplicationRunListener"
-> ["org.springframework.boot.context.event.EventPublishingRunListener"],
"org.springframework.boot.SpringBootExceptionReporter"
-> ["org.springframework.boot.diagnostics.FailureAnalyzers"],
"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"],
}
// 以属性文件的 key 作为键,value 作为值,注意这个值用一个链表表示,因为可能一个键对应多个值
具体加载和读取配置文件如下所示:
解析配置文,如下:
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
}
以指定的 factoryClass
的类全限定名为键,尝试从缓存中取出值,对于 ApplicationContextInitializer.class
这个 factoryClass
来说,会取出所有键为 org.springframework.context.ApplicationContextInitializer
的值,对于以上的 spring.factories
,就是取出了 `
"org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer"
"org.springframework.boot.context.ContextIdApplicationContextInitializer"
"org.springframework.boot.context.config.DelegatingApplicationContextInitializer"
"org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer"
总结
- ==
SpringFactoriesLoader
类的静态方法实现了依赖文件的读取,读取所有的配置类的全限定名名称==- ==整个 SpringApplication 在初始化时完成了最基本配置的读取和实例化==
- ==我们需要记住这个时候的
cache
,这个cache
在上下文加载中非常有用==
文件上下文的加载
上一节展示了如何将配置文件所在的类加载到内存中,那么 spring boot 的自动配置如何办到?
如,在 spring mvc 中,我们需要手工配置如视图解析器等基本配置,需要如下的代码配置:
@Bean
public ViewResolver viewResolver() {
return new ThymeleafViewResolver();
}
既然 spring boot 可以自动配置,那么在 spring boot 中,则一定有类似的语句加载默认的视图解析器等,我们来看看怎么加载的。
注解简介
首先介绍几个注解
@SpringBootApplication
@SpringBootApplication
是一个复合注解,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 注解一
@EnableAutoConfiguration // 注解二
// 没有配置 basepackages,默认代表当前包及其子包
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class) }) // 注解三
public @interface SpringBootApplication {
// ...
}
注解一代表配置注解,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}
有了这个注解,我们就可以把由 @SpringBootApplication
注解修饰的类作为配置类来使用,即在类 DemoApplication
中由 @Bean
注解修饰的代码都会被注入到 IoC 容器中由 Spring 统一管理。
注解三会自动扫描该类所在包,将各种组件注入到 IoC 容器中让其管理,所以我们也可以类 DemoApplication
上增加诸如 @Controller
的注解,将其作为一个 Controller。
@EnableAutoConfiguration
我们注意到 SpringBootApplication
注解同时由 @EnableAutoConfiguration
注解修饰,我们继续看 @EnableAutoConfiguration
。
@EnableAutoConfiguration
的定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 注解一
@Import(AutoConfigurationImportSelector.class) // 注解二
public @interface EnableAutoConfiguration {
// ...
}
该注解的核心是注解二,用于导入自动配置类。
note
- 在 Spring 中,由 Enable 开头的注解的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的 bean 定义。
@Import
@Import
注解可以导入三种类:
-
@Configuration
注解修饰的类 -
ImportSelector
或ImportBeanDefinitionRegistrar
的实现类 - 普通的组件类,即由
@Component
注解修饰的类。
note
- 在 spring boot 中,自动配置的加载使用的是
AutoConfigurationImportSelector
这个类。
ImportSelector
ImportSelector
接口定义如下:
public interface ImportSelector {
// 返回哪些类需要被创建
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
该接口的主要作用是根据给定的选择条件(通常是一个或多个注解属性)导入哪个 @Configuration
类。
note
- 如果该接口的子类实现了如下四个接口,会先执行如下四个接口的函数:
- EnvironmentAware
- BeanFactoryAware
- BeanClassLoaderAware
- ResourceLoaderAware
- 如果希望在所有的
@Configuration
类都导入后再导入该类,则使用其子接口DeferredImportSelector
filter
在 spring 中,有过滤器注解,如下:
@ConditionalOnBean // 在所有的 Bean 都加载好之后才能够加载
@ConditionalOnMissingBean // 在没有该 bean 的时候才能够加载
@ConditionalOnSingleCandidate // 仅当指定类的Bean已包含在BeanFactory中并且可以确定单个候选项时才匹配的条件
@ConditionalOnClass // 所有的 class 都加载之后才能够加载
@ConditionalOnMissingClass // 不存在 class 的时候才能够加载
@ConditionalOnWebApplication // 在 web 环境下才能够加载
@ConditionalOnNotWebApplication // 不在 web 环境下才能够加载
这些注解使用 AutoConfigurationImportFilter
接口处理,排除掉不需要的对象。在 spring 的实现中,分别是 OnBeanCondition
、OnClassCondition
和 OnWebApplicationCondition
。
加载过程
介绍完这些注解,我们回到加载过程。
SpringApplication
的实例方法 run
方法,在 run
方法中,会创建一个新的上下文实例 context
,并调用 context
的 refresh
方法,在 refresh
方法中,会自动配置我们需要的默认上下文。
下面是我们非常熟悉的容器初始化的步骤:
@Override
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();
}
// ...
}
}
invokeBeanFactoryPostProcessors
会解析 @Import
注解,并根据 @Import
的属性进行下一步操作。
invokeBeanFactoryPostProcessors
的主要操作如下:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// ...
}
下面是 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
的方法代码,部分代码如下:
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);
}
}
invokeBeanDefinitionRegistryPostProcessors
函数对每一个定义类的后置处理器分别进行应用, @Configure
的解析就在这个函数中。如下:
// 从注册表中的配置类派生更多的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
函数如下:
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)
中,会调用 sourceClass = doProcessConfigurationClass(configClass, sourceClass)
方法依次解析注解,得到所有的候选集。
note
doProcessConfigurationClass
顺次解析@PropertySource
、@ComponentScan
、@Import
、@ImportResource
、@Bean
、父类
注意 process
中的 this.deferredImportSelectorHandler.process()
方法:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
this.deferredImportSelectorHandler.process();
}
解析完成之后,会找到所有以 @PropertySource
、@ComponentScan
、@Import
、@ImportResource
、@Bean
注解的类及其对象,如果有 DeferredImportSelector
,会将其加入到 deferredImportSelectorHandler
中,并调用 this.deferredImportSelectorHandler.process()
对这些 DeferredImportSelector
进行处理。
AutoConfigurationImportSelector
实际上,在 spring boot 中,容器初始化的时候,主要就是对 AutoConfigurationImportSelector
进行处理。
Spring 会将 AutoConfigurationImportSelector
封装成一个 AutoConfigurationGroup
,用于处理。最终会调用 AutoConfigurationGroup
的 process
方法。
@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,如下:
@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
adapter.setIgnoreDefaultModelOnRedirect(
this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
return adapter;
}
==spring 通过读取所有所需要的 AutoConfiguration
,就可以加载默认的上下文容器,实现自动注入。==