自Spring Boot出道以来,就以快速构建服务而著称。往往只需要在pom文件引入一个dependency就可以快速构建一个web服务,甚至都用不到xml文件。那么Spring Boot时如何做到的呢?主要分两个部分:自动配置和起步依赖。今天就来简单了解一下Spring Boot的自动配置。
主要就是根据classpath路径下的一些类或者一些环境上下文的配置来对Spring Boot程序做一些配置。这一系列的动作都是自动的,所以也称为自动配置。Spring Boot提供的自动配置相关的核心代码在一个叫spring-boot-autoconfiguration的jar包里。下面简单分析下自动配置时如何实现的。
开启自动配置
Spring Boot使用自动配置依赖一个注解:@EnableAutoConfiguration,可能会有疑问,我在使用Spring Boot构建服务时候也没有使用这个注解呀,这是因为我们使用的启动类注解@SpringBootApplication已经带上了这个注解,下面这段代码就是@EnableAutoConfiguration的源码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
发现通过@Import注入了AutoConfigurationImportSelector的Bean,可以看到该类实现了ImportSelector的selectImports方法。
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
private static final String[] NO_IMPORTS = {};
private static final Log logger = LogFactory
.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
......
}
在容器启动时会处理selectImports方法,重点关注一下getAutoConfigurationEntry方法,该方法主要用来导入使用了@Configuration的类。
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);
}
可以看到主要通过getCandidateConfigurations方法获取需要自动配置的类名。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 获取所有自动配置的类名
List<String> configurations = SpringFactoriesLoader.loadFacotryNames(
getSpringFactoriesLoaderFactoryClass(), 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;
}
然后最终通过调用SpringFactoriesLoader的loadFactoryNames方法读取该Jar包目录的文件:META-INF/spring.factories,传入的第一个参数是EnableAutoConfiguration.class。
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 读取META-INF/spring.factories文件
Enumeration<URL> urls = (classLoader != null ?
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);
}
}
下面就是META-INF/spring.factories中EnableAutoConfiguration属性对应的所有的自动配置类全路径类名,这里只截取了部分。
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.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
然后再根据具体自动配置类的信息以及容器上下文的一些配置来决定是否自动配置某个配置类。这里以RedisAutoConfiguration为例简单说明下。
自动配置实现原理
下面是RedisAutoConfiguration的源码
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
分析之前需要先了解以下几个注解:
- @Conditional注解是spring4新特性,它会根据运行环境来动态注入Bean,spring boot在这个基础上扩展了下面几个注解
- @ConditionalOnClass:当classpath出现某个类时候,才会生效
- @ConditionalOnBean:spring容器中出现特定的Bean时候才会生效
- @ConditionalOnMissingBean:spring容器中不存在某个Bean时候才会生效,也就是这个Bean之前没有定义过,首次加载
- @ConditionalOnProperty:当配置了指定配置后才会生效
上面的RedisAutoConfiguration起作用的前提就是项目classpath出现了RedisOperations就会生效,第一个@Bean表示之前没有定义过redisTemplate时候该Bean才会起作用。我们可以在项目的配置文件application.properties配置RedisProperties对应的变量,指定连接Redis的host,port,password等信息。也就是说需要满足各个配置类上的@Conditional条件,才会把对应的自动配置类加载到容器。
了解自动配置信息
虽然我们通过分析了解了自动配置的实现原理,但是Spring Boot在启动后具体哪些配置类会生效呢?我们可以通过–debug来查询所有的匹配的自动配置以及没有匹配的自动配置。这里以Idea为例,简单说明下,如下图配置:
我们就可以在控制台看的我们想要看的的信息,其中主要包括以下四种配置类信息:
- Positive matches 哪些配置类匹配了
- Negative matches 哪些没有匹配
- Exclusions 哪些排除了
- Unconditional classes 。 哪些配置类是无条件注入的
Positive matches:
CodecsAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)
CodecsAutoConfiguration.JacksonCodecConfiguration matched:
- @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
CodecsAutoConfiguration.JacksonCodecConfiguration#jacksonCodecCustomizer matched:
- @ConditionalOnBean (types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) found bean 'jacksonObjectMapper' (OnBeanCondition)
......
Negative matches:
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
AopAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)
ArtemisAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
......
Exclusions:可以通过exclude属性指定排除哪些类
None
Unconditional classes:
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration