前言
在前面我们讲到@Conditional注解可以根据不同的条件来载入不同的Bean,这样我们就可以用来做自动配置。举个简单的例子,比如我们需要用到缓存,我们可以定义当检测Spring context中存在缓存的某个类时,我们就把缓存相关的配置Bean载入到Spring context中,从而达到自动配置的功能。那么SpringBoot究竟是怎样完成自动配置的呢?下面我们来通过源码来具体看一下
自动配置原理
要研究SpringBoot的自动配置原理,当然还得从它的入口注解@SpringBootApplication入手(以下源码全部基于SpringBoot2.1.7)
@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 {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
从以上源码可以看出
- 两个重要的注解@SpringBootConfiguration、@EnableAutoConfiguration
- @SpringBootConfiguration很简单,是个组合注解,组合了@Configuration,这个都很熟悉
- @EnableAutoConfiguration是完成自动配置的核心
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
通过@EnableAutoConfiguration的源码可以看到,核心是@Import({AutoConfigurationImportSelector.class})
//源码很长,我只摘取核心
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//重点在这句,调用loadFactoryNames方法
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
//org.springframework.core.io.support.SpringFactoriesLoader
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
//org.springframework.core.io.support.SpringFactoriesLoader
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//这里可以看出从META-INF/spring.factories加载类
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
从以上源码可以看出来,默认是从META-INF/spring.factories目录下加载自动配置类,下面我们看一下这个文件里面到底长啥样?
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# 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,\
.
.
.
文件很长,省略
.
.
从spring.factories文件中,我们可以看到这句org.springframework.boot.autoconfigure.EnableAutoConfiguration,下面配置了很多以**AutoConfiguration结尾的文件,从字面意思可以看出来这就是一系列的自动配置类,下面随便挑一个文件看一下里面具体是如何实现的,比如ElasticsearchDataAutoConfiguration
@Configuration
@ConditionalOnClass({Client.class, ElasticsearchTemplate.class})
@AutoConfigureAfter({ElasticsearchAutoConfiguration.class})
public class ElasticsearchDataAutoConfiguration {
public ElasticsearchDataAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean({Client.class})
public ElasticsearchTemplate elasticsearchTemplate(Client client, ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
} catch (Exception var4) {
throw new IllegalStateException(var4);
}
}
@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}
@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}
}
从源码可以看出,里面大量用到@Conditional相关的注解,比如,@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnBean等等,那这些注解是啥意思呢?在spring-boot-autoconfigure包中定义了如下一些条件注解,他们都是以@Conditional注解为基础的
条件注解 | 描述 |
---|---|
@ConditionalOnBean | 当容器中有指定的Bean时 |
@ConditionalOnClass | 当类路径下有指定的类时 |
@ConditionalOnCloudPlatform | 运行在指定的云平台,才加载Bean |
@ConditionalOnExpression | 基于spEL表达式作为判断条件 |
@ConditionalOnJava | 基于JVM版本作为判断条件 |
@ConditionalOnJndi | 在JNDI存在的条件下查找指定的位置 |
@ConditionalOnMissingBean | 当容器里没有指定的Bean |
@ConditionalOnMissingClass | 当类路径下没有指定的类 |
@ConditionalOnNotWebApplication | 当前项目不是Web项目 |
@ConditionalOnProperty | 指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径是否有指定的值 |
@ConditionalOnSingleCandidate | 当指定Bean在容器中只有一个或者指定首选的Bean |
@ConditionalOnWebApplication | 当前项目是Web项目 |
SpringBoot的自动配置其实就是依靠这些条件注解来完成的,只要理解了这个过程,其实自动配置也没有那么神奇。
总结一下
- SpringBoot是依靠入口注解@SpringBootApplication来启动整个自动配置体系的
- SpringBoot是通过读取META-INF/spring.factories文件中配置的自动配置类来完成自动配置的
- 自动配置类大多以**AutoConfiguration结尾
- **AutoConfiguration类主要是通过springboot自定义的条件注解来完成自动配置的
既然Springboot自动配置只需要完成以上几步,那么我们是否可以把自己的服务类做成自动配置的形式呢?答案是可以的
详情见 自定义starter