Running with Spring Boot v2.5.7
Spring自2003年发布至今已有近20年的历史,在发布后的短短几年便已成为开发Java应用程序的事实标准,影响着无数国内外Java开发人员。尽管如此,Spring团队并没有放慢自己的脚步,于2014年,发布了Spring Boot。Spring Boot能用寥寥数行代码构建出一套Hello World应用程序,不费吹灰之力!这主要得益于它的两大重量级特性:起步依赖(starter dependency
) 和自动配置(auto configuration
);此外,Spring Boot还自带了metrics、health checks、embeded servlet container 和 externalized configuration等特性,这些都让选择Spring Boot成为了一件顺理成章的事情。可以说,Spring Boot正是Java社区十几年来所探寻的那种能够让Java开发变得趣味横生的框架。
虽然起步依赖不是本文的重点,但起步依赖和自动配置是一种相辅相成的关系,所以有必要简单介绍一下起步依赖的基本概念。在Spring Boot还未问世前,向Spring应用中添加依赖是一件富有挑战性的工作,主要体现在:1) 需要哪些依赖;2) 依赖组件的版本信息是否与当前应用中的其他依赖相匹配。起步依赖就是为了解决这俩痛点问题的,Spring Boot预置了若干不同功能的starter
组件,每个starter组件通过依赖传递可以将需要的外部依赖聚合起来,而且这些外部依赖的版本经过官方严格测试,不会导致依赖冲突。举个例子,如果大家需要基于Spring Boot开发Java Web应用,只需要在项目中引入spring-boot-starter-web
这一个依赖即可,它会自动将tomcat、jackson、spring-web和spring-webmvc等依赖聚合到当前应用中,如下图所示:
1 自动配置的概念
对于第三方starter组件而言,自动配置相关内容一般存放在各自autoconfigure
模块中,当然也有直接封装在starter模块内的;不同于第三方starter组件,Spring Boot内置的starter组件本身并不会包含自动配置类,而是统一将自动配置类存放于spring-boot-autoconfigure
模块中,在该模块classpath:META-INF
目录下的spring.factories
文件中存在100多个以AutoConfiguration
为后缀的自动配置类,如下:
还记得RestTemplate
吗?它是Spring为开发人员提供的一款访问外部RESTful API的利器,其针对每个HTTP方法进行了更高级别的抽象。在Spring Boot应用中使用RestTemplate极其简单,只需要像下面这样声明一个配置类,然后通过@Resource
或@Autowired
注解将RestTemplate实例注入到业务类中即可。
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
restTemplateBuilder.setConnectTimeout(Duration.ofSeconds(15))
.setReadTimeout(Duration.ofSeconds(15));
return restTemplateBuilder.build();
}
}
不知道大家有没有问过自己,这个RestTemplateBuilder
类型的Bean是从哪里蹦出来的?答案就是自动配置。一起来看一下spring-boot-autoconfigure模块内RestTemplateAutoConfiguration
中的内容,如下所示:
package org.springframework.boot.autoconfigure.web.client;
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)
@ConditionalOnClass(RestTemplate.class)
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestTemplateAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
ObjectProvider<HttpMessageConverters> messageConverters,
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
configurer.setHttpMessageConverters(messageConverters.getIfUnique());
configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().collect(Collectors.toList()));
configurer.setRestTemplateRequestCustomizers(restTemplateRequestCustomizers.orderedStream().collect(Collectors.toList()));
return configurer;
}
@Bean
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
RestTemplateBuilder builder = new RestTemplateBuilder();
return restTemplateBuilderConfigurer.configure(builder);
}
}
上述源码交代了三件事:
- 针对RestTemplate自动配置的触发时机要在
HttpMessageConvertersAutoConfiguration
之后。 - 针对RestTemplate自动配置的触发条件有两个:一是当前classpath下必须要有RestTemplate.class文件;二是当前应用必须基于Servlet API构建,说人话就是只能存在
spring-webmvc
依赖,不能含有spring-webflux
依赖。 - 声明了
RestTemplateBuilderConfigurer
与RestTemplateBuilder
这两个Bean,而且还特意标注了@ConditionalOnMissingBean
注解,真是体贴周到啊!在该注解的加持下,我们就可以重新声明这俩Bean,而弃用预置的,这往往很有必要。如果有人疑惑:为什么官方的自动配置模块没有一步到位,直接声明一个RestRemplate类型的Bean呢?天空飞来十个字:授人以鱼不如授人以渔。
一句话,自动配置指的是在若干条件下为Spring Boot应用自动声明一个或多个开箱即用的、具备某一功能的Bean!
2 自动配置的实现原理
众所周知,一个Bean在达到可用条件前,往往需要经历实例化、属性填充和初始化这三个阶段,通过自动配置所声明的Bean也不例外。而BeanDefinition
封装了这三个阶段的具体行为逻辑,显然,BeanDefinition是触发Bean生命周期的前提,因此要想搞明白自动配置的实现原理,我们首先要清楚与自动配置Bean相关联的BeanDefinition是从何而来的;至于Bean的实例化、属性填充和初始化,这些内容应该被归纳到Bean的生命周期这一话题中更为合理。
在主角登场前,先简单介绍下Spring中的两位大哥,分别是:BeanFactoryPostProcessor
和BeanDefinitionRegistryPostProcessor
,后者继承自前者。BeanFactoryPostProcessor
public interface BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}
BeanFactoryPostProcessor
与BeanPostProcessor
均是Spring中常用的IoC拓展点,与BeanPostProcessor不同的是,BeanFactoryPostProcessor只能与BeanDefinition交互,而非与Bean实例交互,比如,我们可以通过BeanFactoryPostProcessor将某一BeanDefinition实例的beanClass属性值替换为CGLIB所生成的Class实例,这样后期通过该BeanDefinition实例所生成的Bean对象就得到增强了。BeanDefinitionRegistryPostProcessor
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry);
}
BeanDefinitionRegistryPostProcessor
是对BeanFactoryPostProcessor的进一步拓展,主要体现在其不仅继承了BeanFactoryPostProcessor的能力,还独自引入了一个postProcessBeanDefinitionRegistry(registry)
方法,该方法的入参类型为BeanDefinitionRegistry
,那肯定奔着BeanDefinitionRegistry的registerBeanDefinition(beanName, beanDefinition)
方法去的,也就是说BeanDefinitionRegistryPostProcessor具备向BeanDefinitionRegistry注册BeanDefinition的能力。
回归正题!在官方的spring-boot-autoconfigure模块亦或第三方的xxxx-spring-boot-autoconfigure中,凡是以AutoConfiguration为后缀的自动配置类都会由@Configuration
标注,而负责引导处理@Configuration注解的正是ConfigurationClassPostProcessor
,没错,主角大佬来了!
从上述继承关系图来看,ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,而后者又继承了BeanFactoryPostProcessor接口,关于这俩接口很熟悉对不对?ConfigurationClassPostProcessor会先通过BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry(registry)
方法向BeanDefinitionRegistry注册BeanDefinition,然后通过BeanFactoryPostProcessor的postProcessBeanFactory(beanFactory)
方法与BeanDefinition实例交互。主干内容如下:
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
/**
* Derive further bean definitions from the configuration classes in the registry.
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
processConfigBeanDefinitions(registry);
}
/**
* Prepare the Configuration classes for servicing bean requests at runtime
* by replacing them with CGLIB-enhanced subclasses.
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
}
下面分小节对这俩核心方法进行解读。
2.1 postProcessBeanDefinitionRegistry(registry)
代码语言:javascript复制public class ConfigurationClassPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
processConfigBeanDefinitions(registry);
}
}
接下来,进入processConfigBeanDefinitions()
方法一探究竟。一句卧槽走天下的杜小头看了源码内容之后,还能说啥呢?于是在对源码进行删减的基础上,将整个内容划分为三个部分,具体如下:
public class ConfigurationClassPostProcessor {
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
/*
* Part 1
*/
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// 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 {
/*
* Part 2
*/
parser.parse(candidates);
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
}
/*
* Part 3
*/
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
} while (!candidates.isEmpty());
}
}
Part 1 | configCandidates.add(new BeanDefinitionHolder(beanDef, beanName))
Part 1 主要围绕一个for
循环展开:
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
显然,当前for循环用于从BeanDefinitionRegistry中的BeanDefinition实例中筛选出配置类,一经发现,立即将候选者放入List类型的configCandidates
变量中。what?难道这个时候BeanDefinition注册表中就已经持有所有的BeanDefinition实例了?别慌!大概也就数十个优先级比较高的BeanDefinition实例而已。换句话说,在执行postProcessBeanDefinitionRegistry(registry)方法前,BeanDefinitionRegistry中已经驻留若干BeanDefinition实例了;在这若干BeanDefinition实例中,绝大多数扮演着ROLE_INFRASTRUCTURE
的角色,即基础设施,用于保障Spring框架相关功能正常流转的(答应我,千万别读完本文后只记得这个单词了,好吗);抛开这些基础设施,还有一个普通角色的BeanDefinition实例值得我们关注,如下图所示:
关于配置类的筛选条件是一个重点内容,内容如下:
代码语言:javascript复制public class ConfigurationClassUtils {
public static final String CONFIGURATION_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
/**
* Check whether the given bean definition is a candidate for a configuration class
*/
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
} else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
metadata = AnnotationMetadata.introspect(beanClass);
}
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full");
} else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite");
} else {
return false;
}
return true;
}
/**
* Check the given metadata for a configuration class candidate
*/
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
}
// Any of the typical annotations found?
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
return hasBeanMethods(metadata);
}
}
筛选逻辑挺严谨,一大坨!但耐心读完后,应该很容易得出以下结论:
- 如果某一BeanDefinition所表征的Bean属于
BeanFactoryPostProcessor
、BeanPostProcessor
、AopInfrastructureBean
和EventListenerFactory
这四种类型之一,则不属于配置类; - 如果某一BeanDefinition所表征的Bean直接或间接地由
@Configuration
、@Component
、@ComponentScan
、@Import
和@ImportResource
这五种注解中的一种标注或者含有由@Bean
标注的方法,那就属于配置类; - 如果某一BeanDefinition所表征的Bean属于配置类而且由
@Configuration(proxyBeanMethods = true)
标注,那么该BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE
属性值就为full
,否则为lite
。
Part 2 | parser.parse(candidates)
Part 2 中的内容是全文核心所在,主要围绕ConfigurationClassParser
展开,主要内容为:
public class ConfigurationClassParser {
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
ConfigurationClass configurationClass = null;
if (bd instanceof AnnotatedBeanDefinition) {
configurationClass = new ConfigurationClass(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
} else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
configurationClass = new ConfigurationClass(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
} else {
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(bd.getBeanClassName());
configurationClass = new ConfigurationClass(reader, beanName);
}
// 核心内容
processConfigurationClass(configurationClass);
}
// 核心内容
deferredImportSelectorHandler.process();
}
}
显然,核心内容最终由processConfigurationClass()
和deferredImportSelectorHandler.process()
这两个方法承载,咱们一个一个来分析。
I. processConfigurationClass()
public class ConfigurationClassParser {
protected void processConfigurationClass(ConfigurationClass configClass) {
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
} while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
}
ConfigurationClass
是processConfigurationClass()
方法的入参类型,它是对配置类的封装,它持有若干成员变量,如:beanName
、与方法级注解@Bean相关的beanMethods
、与类级注解@ImportResource相关的importedResources
、与类级注解@Import(ImportBeanDefinitionRegistrar.class)相关的importBeanDefinitionRegistrars
和表征该配置类由谁引入的importedBy
等。
从上述源码来看,每执行一次processConfigurationClass()方法,最终都会往ConfigurationClassParser中的成员变量configurationClasses
中插入一个ConfigurationClass实例,这很重要!但doProcessConfigurationClass()
方法同样别有洞天。对于每一个配置类,它们都会先被转换为ConfigurationClass实例,然后经doProcessConfigurationClass()方法处理。在doProcessConfigurationClass()方法中,会依次处理与当前配置类相关的nested member class
、@PropertySource
、@ComponentScan
、@Import
、@ImportResource
和@Bean
等内容。
对于nested member class
,首先从当前ConfigurationClass实例中探测出嵌套成员类;然后通过ConfigurationClassUtils判断嵌套成员类是否是配置类,若是配置类,则再次将嵌套成员配置类交由processConfigurationClass()方法处理。
对于@PropertySource
注解,首先从当前ConfigurationClass实例中拿到@PropertySource注解的元数据;然后在ConfigurableEnvironment
中插入PropertySource实例。
对于@ComponentScan
注解,首先从当前ConfigurationClass实例中拿到@ComponentScan注解的元数据;然后委派ComponentScanAnnotationParser
进行扫描;紧接着,ComponentScanAnnotationParser会根据@ComponentScan注解中basePackages
与basePackageClasses
这俩属性来探测出所要扫描的包名,如果没有获取到,就根据@ComponentScan注解所依附宿主类的包名为扫描目标;最后将目标包下所有由@Component
注解标注的类扫描出来并封装为BeanDefinition实例,逐个将BeanDefinition实例注册到BeanDefinitionRegistry中。在默认情况下,由于Spring Boot的启动类由@ComponentScan注解来标注,因此Spring Boot启动类所在包就是最终的扫描目标,那么Spring Boot工程classpath下所有与该包匹配的通过@Component注解声明的类将会被优先扫描到并注册到BeanDefinition注册表中,这是一个重要知识点,同时这也正是为什么启动类总处于顶级包名下的答案!顺便提一句,@Controller
、@RestController
、@Service
、@Repository
和@Configuration
等注解都是由@Component
标注的哈!核心内容如下:
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
Set<BeanDefinitionHolder> BeanDefinitionHolderSet = scanner.doScan(StringUtils.toStringArray(basePackages));
对于@Import
注解,由于其只能引入ImportSelector
、ImportBeanDefinitionRegistrar
和@Configuration
这三种类型,那自然是针对它们进行处理。processImports()
方法即用于处理@Import注解,内容如下:
public class ConfigurationClassParser {
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates) {
if (importCandidates.isEmpty()) {
return;
}
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
} else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归
processImports(configClass, currentSourceClass, importSourceClasses);
}
} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
} else {
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
}
从上述源码内容来看,针对由@Import注解标注的配置类,则根据所引入对象的类型分为四个分支:1) 如果引入的是纯ImportSelector,则递归调用processImports()方法;2) 如果引入的是DeferredImportSelector,则调用DeferredImportSelectorHandler
中的handle()
方法,一般就是构造一个DeferredImportSelectorHolder
而已;3) 如果引入的是ImportBeanDefinitionRegistrar,则填充ConfigurationClass实例中的importBeanDefinitionRegistrars属性;4) 如果引入的是普通@Configuration配置类,则继续委派processConfigurationClass()方法进行处理。也许大家已经察觉到了:只有当@Import注解引入的是普通@Configuration配置类和纯ImportSelector实现类时,processImports()方法才能往成员变量configurationClasses
中插入一个ConfigurationClass实例。
在很多配置类和组合注解中都有@Import
注解的身影,比如组合注解@EnableAutoConfiguration
,而@EnableAutoConfiguration
正是开启Spring Boot自动配置特性的开关,如下:
-------------------------
@EnableAutoConfiguration
-------------------------
< @AutoConfigurationPackage
< @Import(AutoConfigurationPackages.Registrar.class)
-------------------------
< @Import(AutoConfigurationImportSelector.class)
在Spring Boot中,启动类也会由@EnableAutoConfiguration标注,那么当processConfigurationClass()
方法处理启动类这一配置类时,就会以AutoConfigurationImportSelector
为突破口,将自动配置类解析出来?大错特错!因为AutoConfigurationImportSelector属于DeferredImportSelector系,它是一种延迟解析类型的ImportSelector。
继续!对于@ImportResource
注解,首先从当前ConfigurationClass实例中拿到@ImportResource注解的元数据;然后将ImportResource实例填充到当前ConfigurationClass实例的成员变量importedResources中。
对于@Bean
注解,主要是将BeanMethod回填到当前ConfigurationClass实例中的beanMethods属性中;
最后,根据当前ConfigurationClass获取其所实现的接口,只要父接口中含有@Bean方法,那就再次将BeanMethod回填到当前ConfigurationClass实例中的beanMethods属性中。关于这一点,笔者也是看到这里才知道的,如下所示,InterfaceConfig
接口中声明了一个由@Bean注解标注的默认方法(Java 8引入的一个新特性)。
public interface InterfaceConfig {
@Bean
default Demo demo() {
return new Demo();
}
}
@Configuration
public AppConfig implements InterfaceConfig {
}
II. deferredImportSelectorHandler.process()
deferredImportSelectorHandler.process() 用于将所有自动配置模块
classpath:META-INF
目录下spring.factories
文件中的自动配置类解析出来,然后将其追加到ConfigurationClassParser中的configurationClasses
这一成员变量中。所谓所有自动配置模块,包括官方 spring-boot-autoconfigure 和第三方 xxoo-spring-boot-autoconfigure/starter 模块。
插播一条广告!在Spring Boot启动初期,就已经通过getSpringFactoryNames()
方法提前将各模块下classpath:META-INF/spring.factories
文件中的内容拿到了,并缓存起来。具体逻辑落在SpringFactoriesLoader
中,如下:
public final class SpringFactoriesLoader {
static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
public static <T> List<T> loadFactories(Class<T> factoryType, ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryImplementationNames = loadSpringFactories(factoryType, classLoaderToUse);
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
private static List<String> loadSpringFactories(Class<?> factoryType, ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
Map<String, List<String>> result = cache.get(classLoaderToUse);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from META-INF/spring.factories", ex);
}
return result.getOrDefault(factoryTypeName, Collections.emptyList());
}
}
继续!在ConfigurationClassParser中若干嵌套内部类,其中DeferredImportSelectorHandler
就是其中之一,process()
方法内容如下:
public class ConfigurationClassParser {
private class DeferredImportSelectorHandler {
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
} finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
}
}
接着,进入DeferredImportSelectorGroupingHandler
的processGroupImports()
方法中,如下:
public class ConfigurationClassParser {
private class DeferredImportSelectorGroupingHandler {
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter), Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)));
});
}
}
}
}
从上述内容来看,最终依然是委派processImports()
方法对ConfigurationClass进行处理,但这里应该重点关注grouping.getImports()
中的内容。
接着,进入DeferredImportSelectorGrouping
的getImports()
方法中,如下:
public class ConfigurationClassParser {
private static class DeferredImportSelectorGrouping {
private final DeferredImportSelector.Group group;
private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
DeferredImportSelectorGrouping(DeferredImportSelector.Group group) {
this.group = group;
}
public void add(DeferredImportSelectorHolder deferredImport) {
this.deferredImports.add(deferredImport);
}
public Iterable<DeferredImportSelector.Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
}
}
从上述内容来看,DeferredImportSelector.Group
接口中的process()
方法和selectImports()
方法可能就是最终的处理逻辑了,内容如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector {
private static class AutoConfigurationGroup implements DeferredImportSelector.Group {
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
AutoConfigurationEntry autoConfigurationEntry = new AutoConfigurationEntry(configurations, exclusions);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
@Override
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
processedConfigurations.removeAll(allExclusions);
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
}
}
在process()
方法中,首先从SpringFactoriesLoader
的缓存变量中以EnableAutoConfiguration
作为key取出所有自动配置类;然后再从SpringFactoriesLoader
中缓存变量中以AutoConfigurationImportFilter
作为key取出所有自动配置过滤器,主要是OnBeanCondition
、OnClassCondition
和OnWebApplicationCondition
,借助过滤器过滤掉无效的自动配置类;最后,将存活下来的自动配置类填充到AutoConfigurationEntry
列表中。在selectImports()
方法中并无啥重要内容,主要是将AutoConfigurationEntry
列表中的有效自动配置类转换为Iterable<Entry>
,最后遍历该Iterable,依次交由processImports()
方法处理,processImports()方法在 Part 2 部分介绍 processConfigurationClass()方法的时候已经介绍过了!
Part 3 | this.reader.loadBeanDefinitions(configClasses)
Part 2 执行完毕后,最终的自动配置类都已经解析出来并保存在ConfigurationClassParser中的configurationClasses这一成员变量中了,但还没有将其注册到BeanDefinitionRegistry中。Part 3 主要围绕ConfigurationClassBeanDefinitionReader
中的loadBeanDefinitions()
方法展开,如下:
public class ConfigurationClassBeanDefinitionReader {
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
}
具体内容也就不展开了,主要是逐个遍历ConfigurationClass实例,每个ConfigurationClass实例中的属性在 Part 2 阶段都已填充完毕,下面要做的无非是将ConfigurationClass实例中所封装的配置类注册到BeanDefinitionRegistry中罢了。
2.2 postProcessBeanFactory(beanFactory)
代码语言:javascript复制public class ConfigurationClassPostProcessor {
/**
* Prepare the Configuration classes for servicing bean requests at runtime
* by replacing them with CGLIB-enhanced subclasses.
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
}
接下来,进入enhanceConfigurationClasses()
方法一探究竟,注意:此时beanFactory中已经持有所有的BeanDefinition实例了。第一个for循环主要用于筛选出哪些BeanDefinition所对应的Bean需要后期为其进行CGLIB
增强,如果某一BeanDefinition的ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE
属性值为full
,那就将其填充进configBeanDefs
变量中;第二个for循环用于遍历configBeanDefs
变量,为每一个BeanDefinition生成全新的CGLIB增强类同时替换beanClass
属性值,这样后期在通过该BeanDefinition实例化Bean的时候,Bean就是一个增强型代理类了。换句话说,IoC容器中驻留的就不是本尊了,而是CGLIB代理类。相关逻辑如下:
public class ConfigurationClassPostProcessor {
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
if ("full".equals(configClassAttr)) {
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// Set enhanced subclass of the user-specified bean class
Class<?> configClass = beanDef.getBeanClass();
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
beanDef.setBeanClass(enhancedClass);
}
}
}
}
值得一提的是:这里通过CGLIB所生成的代理类都会自动实现EnhancedConfiguration
接口,该接口是一个marker interface
,即一种标识性的空接口。之所以能够自动实现该接口,是因为在构建Enhancer
实例的时候, 通过setInterfaces()
方法显式设置的,如下所示:
public class ConfigurationClassEnhancer {
public Class<?> enhance(Class<?> configClass, ClassLoader classLoader) {
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
return configClass;
}
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
return enhancedClass;
}
private Enhancer newEnhancer(Class<?> configSuperClass, ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
}
前文提到:只有在由@Configuration(proxyBeanMethods = true)
直接修饰的配置类所对应的BeanDefinition实例中,ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE
的值才为full
,即最终会每个@Configuration(proxyBeanMethods = true)
配置类生成CGLIB代理,为什么呢?上图!
仔细观察上图,operatorService()
方法和logRecordPersistenceService()
方法均由@Bean
标注,那么这两个方法会分别通过OperatorService和LogRecordPersistenceService的构造方法来实例化Bean;可是在operationLogPointcutAdvisor()
方法中,这两方法又被调用一次,难道构造方法又要执行一次?如果是的话,那单例Bean不就是笑话吗?事实上,OperatorService和LogRecordPersistenceService的构造方法只会被执行一次,因为OperationLogAutoConfigurationImportSelector会被CGLIB增强,即该Bean在IoC容器中是一个CGLIB代理类。注意一点,@Configuration
注解接口中有一个属性:boolean proxyBeanMethods() default true
,即默认为@Configuration
配置类生成CGLIB代理。事实上,还是尽量不要生成CGLIB代理吧,因为这会延长Spring Boot工程的启动时间。
在enhanceConfigurationClasses()
方法执行完毕之后,紧接着向BeanFactory
中插入了一个ImportAwareBeanPostProcessor
实例,这是一个BeanPostProcessor实现类,核心内容如下:
private static class ImportAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
private final BeanFactory beanFactory;
public ImportAwareBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof ImportAware) {
ImportRegistry importRegistry = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
AnnotationMetadata importingClass = importRegistry.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
if (importingClass != null) {
((ImportAware) bean).setImportMetadata(importingClass);
}
}
return bean;
}
}
从上述源码来看,ImportAwareBeanPostProcessor主要服务于ImportAware
类型的Bean。具体地,在对ImportAware类型的Bean执行初始化逻辑之前,通过ImportAware的setImportMetadata()
方法为该Bean透传AnnotationMetadata
实例对象。一脸懵逼?ImportAware类型的Bean一般要满足两个条件:1) 实现ImportAware接口;2) 由@Configuration
注解接口标记。ProxyTransactionManagementConfiguration
就是一个标准ImportAware类型的Bean,它的父类AbstractTransactionManagementConfiguration
实现了ImportAware接口,详情如下:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
// ignore contents
}
那为什么ImportAware类型的Bean需要显式地为其透传AnnotationMetadata实例对象呢?回忆一下,Import
注解接口既可以引入ImportSelector
也可以引入ImportBeanDefinitionRegistrar
,还可以引入@Configuration
;但前两者均持有参数类型为AnnotationMetadata的方法,这样就能够接收传进来的AnnotationMetadata实例对象;可由@Configuration
修饰的配置类要想拥有接收AnnotationMetadata实例对象的能力,只能实现ImportAware接口。
3 总结
应该说,Spring Boot自动配置这一特性是对Spring 4中@Conditional
注解的完美落地,但关于这方面知识本文并未涉及,但并不代表不重要。
参考文档
- https://docs.spring.io/spring-boot/docs/2.5.7/reference/htmlsingle/
- Spring Boot in Action | Craig Walls