截止目前,我们已经写了四篇关于Spring源码解析的文章,但它们只关注了Spring和SpringBoot的启动流程,并没有回答我们日常使用中的核心问题——自动配置的实现原理,本文进行探索
在前文“Spring源码剖析 - BeanDefinition”中扫描Bean定义一节中,我们漏掉了一个关键方法调用,其调用路径如下
-> org.springframework.context.annotation.AnnotationConfigApplicationContext #AnnotationConfigApplicationContext(java.lang.Class>...) -> org.springframework.context.annotation.AnnotationConfigApplicationContext #AnnotationConfigApplicationContext() -> org.springframework.context.annotation.AnnotatedBeanDefinitionReader #AnnotatedBeanDefinitionReader(org.springframework.beans.factory.support.BeanDefinitionRegistry) -> org.springframework.context.annotation.AnnotatedBeanDefinitionReader #AnnotatedBeanDefinitionReader(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.core.env.Environment) -> org.springframework.context.annotation.AnnotationConfigUtils #registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry) -> org.springframework.context.annotation.AnnotationConfigUtils #registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)
即,在AnnotationConfigApplicationContext执行构造方法时,调用AnnotationConfigUtils.registerAnnotationConfigProcessors(),注册了一系列注解处理器,具体来说,有
-
ConfigurationClassPostProcessor
用于处理配置相关的各种注解
-
AutowiredAnnotationBeanPostProcessor
用于处理自动注入相关的注解,如@Autowired、@Value、@Inject
-
CommonAnnotationBeanPostProcessor
用于处理一些通用注解,比如@Lazy、@Primary、PreDestroy等
-
PersistenceAnnotationBeanPostProcessor
用于处理JPA相关注解
-
EventListenerMethodProcessor
用于处理@EventListener,即把方法注册成监听器
-
DefaultEventListenerFactory
结合EventListenerMethodProcessor使用,该工厂输入方法,可创建出一个ApplicationListener类
本文我们重点关注配置相关的注解
概览ConfigurationClassPostProcessor继承结构如上,属于容器构建阶段的处理器,我们再复习一下它的两个父接口的作用
-
BeanFactoryPostProcessor:在容器初始化之后,对容器做一些自定义操作。具体时机是:BeanDefinition加载完成之后,Bean实例化之前。
-
BeanDefinitionRegistryPostProcessor:在容器初始化完成之后,BeanFactoryPostProcessor执行之前对容器做一些自定义操作。
而ConfigurationClassPostProcessor在这两个时机分别做了两件事
-
容器初始化完成后,BeanFactoryPostProcessor调用前,从@Configuration注解的类中加载Bean定义,然后向容器注册。
这一点是本文的重点 ,逻辑位于org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions,进一步的确切逻辑位于
-
注解解析:
-> org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set) -> org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
-
BeanDefinition注册
-> org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions。
-
其它逻辑无助于理解,一概忽略
-
-
容器初始化完成后,对所有@Configuration注解标识的BeanDefinition进行增强
这一点不是重点,大概描述一下:使用的是CGLib的Enhancer API。添加了如下几个增强回调
- BeanMethodInterceptor:拦截所有@Bean注解的方法
- BeanFactoryAwareMethodInterceptor:拦截所有实现了BeanFactoryAware的@Configuration类的setBeanFactory()方法,目的是将容器注入
我们从ConfigurationClassParser.doProcessConfigurationClass的解析逻辑中,分析如下几个注解的工作原理。
@Conditional该注解用于条件配置,即满足条件时才生效,否则被忽略,它的实现由Conditionevaluator完成。使用的地方包括
- ConfigurationClassParser.processConfigurationClass(),对配置类有效。
- ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod,加载Bean时有效,即作用在@Bean注解的方法上
逻辑落地在Conditionevaluator.shouldSkip()
- 如果没有被@Conditional注解,则不应被跳过
- 获取@Conditional注解的属性值,即其指定的Condition类,可能有多个
- 依次调用这些Condition类的matches()方法,只要有一个匹配,则判定为应该被跳过,可见,多条件是与的关系
SpringBoot还定义了ConditionalOnClass、ConditionalOnMissingBean之类的注解,其实看一眼他们的定义就知道。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class>[] value() default {};
String[] name() default {};
}
比如上面这个注解,它是@Conditional注解加上OnClassCondition这个条件构成的。而在OnClassCondition的匹配方法的逻辑如下(有继承关系,matches()方法在父类,这里只给出核心逻辑):类加载器能加载出来就匹配,否则就不匹配
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypemetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
// 获取ConditionalOnClass指定的那些类名
List onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
// 用ClassLoader加载指定的类名,加载不出来则说明缺失
List missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
// 缺失就报不匹配
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
}
}
// 否则就是匹配
return ConditionOutcome.match(matchMessage);
}
@PropertySource
该注解能够将指定的properties文件加载到容器的Environment中,使用参考。
通过源码解读可以看出它的逻辑
- 读取@PropertySource的value值,作为配置文件的位置location
- 解析location中的占位符,这意味着location中可以存在动态字段
- 使用resourceLoader将location解析成Resource对象
- 使用PropertySourceFactory创建PropertySource对象,并加入Environment
这里有一个能够自定义的东西:属性源工厂,如果我们指定自己的属性源工厂,则能够按照自己的需求解析,因此我们能够自定义出解析yaml文件的逻辑。默认是DefaultPropertySourceFactory,它创建的是一个ResourcePropertySource。
我们大致看一下它的核心逻辑
// 解析注解的属性:name、encoding、value、ignoreResourceNotFound、factory
String name = propertySource.getString("name");
... ...
String encoding = propertySource.getString("encoding");
... ...
String[] locations = propertySource.getStringArray("value");
... ...
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
// 如果有指定factory,构建该工厂对象,否则,使用默认的工厂
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
for (String location : locations) {
... ...
// 解析location中的占位符
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
// 从文件系统中加载出来
Resource resource = this.resourceLoader.getResource(resolvedLocation);
// factory创建出属性源,加入environment
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
... ...
}
@ComponentScan
该注解用于扫描指定包、类所属的包下的Bean定义,其核心逻辑挂在ComponentScanAnnotationParser.parse()上,如下
-
构建ClassPathBeanDefinitionScanner扫描器,用于执行Bean定义扫描
-
读取@ComponentScan的各类属性,写入扫描器,属性包括
- nameGenerator:Bean名生成器
- scopedProxy:scopedProxy的方式:不代理、JDK代理、cglib代理,关于scopedProxy,可以参考这里。大致来说,就是当scope范围更宽的bean1引用scope范围较小的bean2时,由于他们的作用范围不一致,导致bean1的生存时间大于bean2的生存时间,直接引用会发生问题,此时可以将bean2用一个代理包装起来,由代理负责引用实际的Bean,每当bean2的生命结束时,由代理自动创建新的实例,对bean1做到透明。
- scopeResolver:Scope解析器
- resourcePattern:扫描目标文件的通配符,如*class
- includeFilters:过滤器
- excludeFilters:过滤器
- lazyInit:延迟加载
- basePackages:基包
- basePackageClasses:基类
-
调用扫描器的扫描方法,这个在前面介绍扫描BeanDefinition时有描述过,这里不再赘述。
源码也简单,只要注意一点:如果没有指定基包或基类,就是用当前配置类所在的包作为扫描位置
public Set@importparse(AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); Class extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class extends ScopemetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); scanner.setScopemetadataResolver(BeanUtils.instantiateClass(resolverClass)); } scanner.setResourcePattern(componentScan.getString("resourcePattern")); for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } boolean lazyInit = componentScan.getBoolean("lazyInit"); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true); } Set 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)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); }
该配置用于导入其它配置,定义如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface import {
Class>[] value();
}
如注释所言,支持导入的对象有四种
-
被@Configuration注解的类,即一个普通的配置类,这是最常用的。对它的处理方式就是,再当做配置类执行一遍processConfigurationClass()方法逻辑
-
importSelector,顾名思义,导入选择器,这是一个接口,定义了要导入的资源,导入的资源依旧是这四类,但最终会是普通的注解类。
对于该类对象的处理方式,是找出其选择的导入对象,递归执行导入逻辑
-
importBeanDefinitionRegistrar,顾名思义,用于导入BeanDefinition注册器,其逻辑是向ConfigurationClassBeanDefinitionReader中注入该注册器,后面在加载BeanDefinition时会用到它。
if (candidate.isAssignable(importSelector.class)) {
// Candidate class is an importSelector -> delegate to it to determine imports
Class> candidateClass = candidate.loadClass();
// 初始化选择器
importSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, importSelector.class,
this.environment, this.resourceLoader, this.registry);
// 排除过滤器
Predicate selectorFilter = selector.getExclusionFilter();
... ...
// 调用选择器的关键方法selectimports,实际上就是一个类名数组
String[] importClassNames = selector.selectimports(currentSourceClass.getmetadata());
// 类名封装一下
Collection importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 递归执行processimports()
processimports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
所以如果导入对象是importSelector,它所做的工作其实只有选择待导入的配置对象。
导入BeanDefinition集合else if (candidate.isAssignable(importBeanDefinitionRegistrar.class)) {
// Candidate class is an importBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class> candidateClass = candidate.loadClass();
// 构建对象
importBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, importBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
// 将其当做属性加入配置类
configClass.addimportBeanDefinitionRegistrar(registrar, currentSourceClass.getmetadata());
}
在读取Bean定期时是有调用的,在ConfigurationClassBeanDefinitionReader中可以看到,它的调用在概览中有描述,在下一节也会描述
// 在类ConfigurationClassBeanDefinitionReader中 private void loadBeanDefinitionsForConfigurationClass( ... ... loadBeanDefinitionsFromimportedResources(configClass.getimportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getimportBeanDefinitionRegistrars()); }导入普通配置
else {
// 递归调用processConfigurationClass(),很好理解
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
小结
总的来说,@import原理上其实非常简单,最终目的是导入其它配置类或BeanDefinition集合。
@importResource该注解用于指定导入源,Spring会将这些源当做配置源进行读取,他们可以是xml、groovy,这是根据资源文件的后缀自动决定的。接口定义如下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@documented
public @interface importResource {
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
Class extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
}
在注解扫描阶段,对它只是进行了读取
AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getmetadata(), importResource.class);
if (importResource != null) {
// 获取位置信息
String[] resources = importResource.getStringArray("locations");
// 获取读取器
Class extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
// 解析位置信息中的占位符
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
// 注册到configClass
configClass.addimportedResource(resolvedResource, readerClass);
}
}
什么时候执行呢?和前面说的BeanDefinition集合一样,都是在加载阶段,后文详述。
@Bean该注解用在方法上,将方法的返回值构建成一个新的Bean定义,解析阶段只是向configClass注册了待加载的方法,解析阶段后文详述。
// 从类中加载出所有被Bean注解的方法 SetbeanMethods = retrieveBeanMethodmetadata(sourceClass); for (Methodmetadata methodmetadata : beanMethods) { // 把它们加入configClass configClass.addBeanMethod(new BeanMethod(methodmetadata, configClass)); }
此外,如果配置类实现的接口的方法有被@Bean注解,且该配置类实现了该方法,则该方法也会被加载为Bean。
BeanDefinition注册我们从ConfigurationClassBeanDefinitionReader.loadBeanDefinitions大致看一下加载过程中做了什么事情。
核心逻辑位于:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionevaluator trackedConditionevaluator) {
... ...
if (configClass.isimported()) {
registerBeanDefinitionForimportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromimportedResources(configClass.getimportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getimportBeanDefinitionRegistrars());
}
- 如果配置类是被导入的,则先将该配置类注册成为一个Bean定义
- 将所有被@Bean注解的方法构建成Bean定义
- 从@importResource指定的资源中加载Bean定义
- 从@import导入的BeanDefinitionRegistrar中导入Bean定义
private void registerBeanDefinitionForimportedConfigurationClass(ConfigurationClass configClass) {
Annotationmetadata metadata = configClass.getmetadata();
AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
// 解析scope
Scopemetadata scopemetadata = scopemetadataResolver.resolveScopemetadata(configBeanDef);
configBeanDef.setScope(scopemetadata.getScopeName());
// 解析beanName
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
// 处理通用注解
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
// 搞定收工
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopemetadata, definitionHolder, this.registry);
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
configClass.setBeanName(configBeanName);
}
如上,比较简单,不过这里有个值得说的点:处理通用注解AnnotationConfigUtils.processCommonDefinitionAnnotations(),它主要用于处理如下注解
- Lazy
- Primary
- DependsOn
- Role
- Description
都不需要怎么解释,看一下代码就能了解
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypemetadata metadata) {
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
} else if (abd.getmetadata() != metadata) {
lazy = attributesFor(abd.getmetadata(), Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
}
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {
abd.setDependsOn(dependsOn.getStringArray("value"));
}
AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null) {
abd.setRole(role.getNumber("value").intValue());
}
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null) {
abd.setDescription(description.getString("value"));
}
}
@Bean
关于Bean方法的处理,看起来一长串,其实就两个关键的逻辑
- 使用ConfigurationClassBeanDefinition作为Bean定义,它将被注解的方法注册成了Bean定义的工厂方法。这样在Bean创建时走的完全是正常的创建流程
- 以代理的方式创建,代理模式为CGLIB
具体参考ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod,这里也没必要列出来源码。
@importResource创建指定的BeanDefinitionReader实例,然后读取指定的location即可。
private void loadBeanDefinitionsFromimportedResources(MapBeanDefinitionRegistrar> importedResources) { Map , BeanDefinitionReader> readerInstanceCache = new HashMap<>(); importedResources.forEach((resource, readerClass) -> { // 决定要使用的BeanDefinitionReader // Default reader selection necessary? if (BeanDefinitionReader.class == readerClass) { if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) { // When clearly asking for Groovy, that's what they'll get... readerClass = GroovyBeanDefinitionReader.class; } else if (shouldIgnoreXml) { throw new UnsupportedOperationException("XML support disabled"); } else { // Primarily ".xml" files but for any other extension as well readerClass = XmlBeanDefinitionReader.class; } } BeanDefinitionReader reader = readerInstanceCache.get(readerClass); // 创建Reader if (reader == null) { // Instantiate the specified BeanDefinitionReader reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry); // Delegate the current ResourceLoader to it if possible if (reader instanceof AbstractBeanDefinitionReader) { AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader); abdr.setResourceLoader(this.resourceLoader); abdr.setEnvironment(this.environment); } readerInstanceCache.put(readerClass, reader); } // reader读,走你 reader.loadBeanDefinitions(resource); }); }
更简单,在第一篇Spring源码剖析的文章中我们提到过Registry和Registrar的区别,前者是注册器,用于接收资源的注册并持有;而后者只是一个资源集合,持有一堆资源,一般调用方式是传入Registry,向其中倾泻自己持有的资源。
这里的BeanDefinitionRegistrar就是如此,持有一堆BeanDefinition集合,在本阶段向注册器中注册
private void loadBeanDefinitionsFromRegistrars(Map自动配置怎么实现registrars) { registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator)); }
上一篇介绍SpringBoot启动流程的文章中,我们知道了SpringApplication在快速启动中做了什么,这其中,最最重要的是通过meta/spring.factories加载指定类的实现的机制,即SpringFactoriesLoader.loadFactory()方法。但上一篇文章并没有介绍自动配置的原理,因为还缺少@import注解的说明。
本文我们补上了这一点,于是可以探索@SpringBootApplication这一SpringBoot的另一核心的原理,其定义如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
... ...
}
层层深入,可以发现它最终落在三个注解上
- @Configuration:表明这是一个配置类
- @import(AutoConfigurationimportSelector.class) + @import(AutoConfigurationPackages.Registrar.class):导入配置
- @ComponentScan:表明从当前类的包的子包下扫描
进一步,重点落在AutoConfigurationimportSelector和AutoConfigurationPackages.Registrar上,重点看前者,后者与自动配置无关,暂且忽略。
AutoConfigurationimportSelector该选择器,调用了SpringFactoriesLoader.loadFactory(),从spring.factories文件中加载键为org.springframework.boot.autoconfigure.EnableAutoConfiguration所对应的值,核心逻辑追踪如下
-> org.springframework.boot.autoconfigure.AutoConfigurationimportSelector#selectimports -> org.springframework.boot.autoconfigure.AutoConfigurationimportSelector#getAutoConfigurationEntry -> org.springframework.boot.autoconfigure.AutoConfigurationimportSelector#getCandidateConfigurations -> org.springframework.boot.autoconfigure.AutoConfigurationimportSelector#getSpringFactoriesLoaderFactoryClass
再看最后这个方法的实现,显而易见了
protected Class> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
看看自动配置
官方的spring-boot-autoconfigure下的meta-INF/spring.factories中,定义了非常多的自动配置类
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration= org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration, org.springframework.boot.autoconfigure.aop.AopAutoConfiguration, org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration, org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration, org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration, ... ...
我们选取一个比较熟的org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,浏览一下其代码。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, ServerContainer.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@AutoConfigureBefore(ServletWebServerFactoryAutoConfiguration.class)
public class WebSocketServletAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Tomcat.class, WsSci.class })
static class TomcatWebSocketConfiguration {
@Bean
@ConditionalOnMissingBean(name = "websocketServletWebServerCustomizer")
TomcatWebSocketServletWebServerCustomizer websocketServletWebServerCustomizer() {
return new TomcatWebSocketServletWebServerCustomizer();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebSocketServerContainerInitializer.class)
static class JettyWebSocketConfiguration {
@Bean
@ConditionalOnMissingBean(name = "websocketServletWebServerCustomizer")
JettyWebSocketServletWebServerCustomizer websocketServletWebServerCustomizer() {
return new JettyWebSocketServletWebServerCustomizer();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(io.undertow.websockets.jsr.Bootstrap.class)
static class UndertowWebSocketConfiguration {
@Bean
@ConditionalOnMissingBean(name = "websocketServletWebServerCustomizer")
UndertowWebSocketServletWebServerCustomizer websocketServletWebServerCustomizer() {
return new UndertowWebSocketServletWebServerCustomizer();
}
}
}
总结
通过spring.factories机制和@import + importSelector的形式,SpringBoot为实现自动注解提供了最基础的能力,但根据各个具体不同的场景,Spring还在上面提到的基本注解之上构建了非常多具体的注解,比如条件注解就好多。一个个看是不现实的,但我们了解了本文提到的基础知识,再去看就很好理解了。



