SpringBoot的最主要的功能就是自动装配功能。通过自动配置,SpringBoot大大减少了使用java框架的复杂程度,并且可以统一的将配置文件写入application.yml中。并且,SpringBoot为常见的应用提供了一套默认的配置,使得使用者无需编写重复的配置代码,可以专心于业务的实现。
第三方框架和其他的Spring组件只要使用Spring提供的SPI规范,就可以通过编写starter的方式被通过SpringBoot的方式组合到SpringBoot中。
SpringBoot首先会从配置文件中读取所有的自动配置类,这些配置类我们称为待选配置类。然后通过筛选,仅注入项目所需要的配置类,这些配置类被注册后就会生效,为我们的项目提供自动配置功能。
SpringBoot实现自动装配的主要实现是依靠@EnableAutoConfiguration注解实现的,这个注解被包含在了Spring的入口注解@SpringBootApplication中
@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
SpringBootApplication注解包括三个重要的子注解
@EnableAutoConfiguration注解的实现如下:
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以看到,在这里SpringBoot引用了AutoConfigurationImportSelector类,这个类继承了ImportSelector接口,这个接口提供一个方法,可以在获取到IOC容器中的Bean的元信息之后,来判断是否将Bean注入IOC容器。这个方法就是selectImports方法。参数为AnnotationMeataInfo注解元信息。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
显然的,上述方法中最主要的方法是getAutoConfigurationEntry方法,这个方法,打开这个方法的实现,可以看到他通过getCandidateConfiguration方法获取了所有的待选配置的全限定名并组成了一个list。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
...
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
...
}
深入这个方法,就可以找到最终调取自动配置类的方法loadFactoryName,loadSpringFactories是一个Map
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
return configurations;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
...
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
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
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor
# 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,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
至此,我们弄明白了待选类是如何被读取的。
那么,我们是否每次都注入了所有的配置呢?显然我们应该按需注入,这里就必须提到SpringBoot提供的注解
@ConditionalOn....(一些条件))
当一个配置类被这个注解标识,那么仅在这个注解标志的条件满足的时候,这个配置类才会生效。以Redis为例,如果SpringBoot在当前的项目文件中检测不到存在RedisOperations这种Redis的核心类,那么Redis就没有被用到,也就不需要对Redis进行自动配置了。于是,通过对配置类使用@Condition系列注解,就使得组件被按需注入。
我们在使用自动配置的时候,可以发现自动配置其实是由两个方面组成的。一是SpringBoot提供了一些默认的配置,二是我们可以通过编写application.yml来自定义配置。
SpringBoot为我们编写好了许多的配置类,这些配置类已经实现了常见的配置,帮我们注入了常用的Bean,并配置好了一些默认的属性。同样以Redis为例,可以看到Redis的配置类如下:SpringBoot为我们创建了SpringRedisTemplate和RedosTemplate类,我们就无需自己注入这两个类了。
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
从上面的代码我们也可以看到SpringBoot的一个设计准则,如果我们没有配置,他就帮我们进行默认配置。如果我们有自定义的配置,就遵循我们的配置。
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
可以看到,如果我们自定义了一个redisTemplate的bean,SpringBoot就不会再注入这个Bean了。
接下来解决我们的自定义问题,我们可以在application.yml中指定redis客户端的链接地址,数据库密码等各种属性。SpringBoot通过@ConfigurationProperties,从配置文件中读取配置信息。
@ConfigurationProperties(
prefix = "spring.redis"
)
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String username;
private String password;
private int port = 6379;
private boolean ssl;
private Duration timeout;
private Duration connectTimeout;
private String clientName;
private ClientType clientType;
private Sentinel sentinel;
private Cluster cluster;
.....
}
从上面代码中可以看到,配置的前缀是spring.redis,在application.yml中修改spring.redis后的属性即可修改这个类,然后对redis进行自定义了。
综上所述,自动配置主要由一个或多个配置类和Properties类组成。一个提供Bean的注入和属性的设置,另一个从配置文件中读属性值。SpringBoot实现了第三方框架和其他Spring组件的自动配置类,使得他们可以被轻松的集成和自定义。