Spring Boot 源码分析(一)
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 中,如下:
image.png
每一个 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
主函数中,只有一个方法,即 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
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 直接利用反射创建实例
List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
public static List 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 = https://www.it610.com/article/{"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> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = cache.get(classLoader);
if (result != null) {
return result;
} try {
Enumeration 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 beanFactoryPostProcessors) {// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set processedBeans = new HashSet<>();
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List regularPostProcessors = new ArrayList<>();
List 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 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 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 candidates = new LinkedHashSet<>(configCandidates);
Set alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 解译候选集
parser.parse(candidates);
parser.validate();
Set 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
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 configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List 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 Boot 源码分析(一)】}
推荐阅读
- Activiti(一)SpringBoot2集成Activiti6
- SpringBoot调用公共模块的自定义注解失效的解决
- 解决SpringBoot引用别的模块无法注入的问题
- Android事件传递源码分析
- 2018-07-09|2018-07-09 Spring 的DBCP,c3p0
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听
- [源码解析]|[源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3)
- ffmpeg源码分析01(结构体)
- spring|spring boot项目启动websocket
- Java程序员阅读源码的小技巧,原来大牛都是这样读的,赶紧看看!