个人总结:
自动配置流程:判断是否引入框架(starter jar),获取配置参数,根据配置参数初始化框架相应组件。
实现:通过加载注入全限类名对应的自动配置类来完成容器启动时组件的自动装载。
例:pom.xml中是否引入redis-starter,引入了starter获取application.properties中配置注入RedisProperties(覆盖RedisProperties的默认配置)。根据RedisProperties初始化RedisConnectionFactory。RedisAutoConfiguration提供RedisTemplate模板Bean供程序使用。RedisProperties、RedisConnectionFactory是以RedisAutoConfiguration类上注解的形式加载注入spring的。
代码实现:@SpringBootApplication --> @EnableAutoConfiguration --> @AutoConfigurationPackage --> @Import({AutoConfigurationImportSelector.class}) --> 启动时调用了AutoConfigurationImportSelector.selectImports()方法 --> 去spring.factories文件中获取AutoConfiguration类的全限类名,过滤(没引入starter)后加载注入spring作为config bean --> AutoConfiguration类上@EnableConfigurationProperties将XxProperties配置类注入spring,XxProperties通过@ConfigurationProperties注解将application.properties配置文件的配置注入XxProperties对象中 --> 根据XxProperties对象初始化RedisConnectionFactory --> AutoConfiguration类中方法返回值RedisTemplate通过@Bean注入spring。(RedisAutoConfiguration->RedisProperties->RedisConnectionFactory->RedisTemplate)
@SpringBootApplication是一个复合注解:
@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 {
}
@SpringBootConfiguration : Spring Boot的配置类,标注在某个类上,表示这是一个Spring Boot的配置类;
@EnableAutoConfiguration:支持自动配置的注解;
@ComponentScan组件包扫描;
@EnableAutoConfiguration:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}
它的2个核心注解是@AutoConfigurationPackage、@Import({AutoConfigurationImportSelector.class})
@AutoConfigurationPackage:
@AutoConfigurationPackage注解的主要作用是定义自动配置的包,供容器启动时加载。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
很明显它的功能就是@Import({Registrar.class})的Registrar类实现的:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
//容器启动时执行
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册一个自动配置包的bean
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}
//容器启动时不会执行
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());注册了一个bean到spring容器中,(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()返回的是启动类的包名,容器扫描加载bean的时候只会加载这个包及其子包下的bean。启动类放在顶级目录的原因。
@Import({AutoConfigurationImportSelector.class}):
用于自动配置导入的选择。spring-boot-starter中引入了spring-boot-autoconfigure,spring-boot-autoconfigure中定义了众多自动配置加载执行的配置类。pom.xml中引入springboot的starter就是用来指定:加载spring-boot-autoconfigure中哪些配置类,AutoConfigurationImportSelector是上述过程的实现类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}
public interface DeferredImportSelector extends ImportSelector {}
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
再来看看AutoConfigurationImportSelector是如何实现selectImports()方法的:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//获取自动配置元数据
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//根据元数据获取自动配置Entry对象
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//获取spring-boot-autoconfigure中定义的所有用于支持自动配置的配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//过滤未使用的配置类
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
看一下如何获取所有配置类的 :
//获取spring-boot-autoconfigure中定义的所有用于支持自动配置的配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
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 {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
...
}
进入loadFactoryNames()方法中可以看到是从META-INF/spring.factories配置文件中获取的,参考下方spring.factories文件截图,可发现spring.factories文件中声明了spring-boot-autoconfigure为每一个starter定义的配置类的全限类名。springboot就是在SpringApplication.run(...)的内部执行selectImports()方法,找到自动配置类的全限类名去加载对应的class,然后将自动配置类注入Spring容器中,即通过加载注入全限类名对应的自动配置类来完成容器启动时组件的自动装载。
XxTemplate模板类的默认属性修改:
springboot为starter生成了xxTemplate的工具类,例如:RedisTemplate、MongoTemplate、KafkaTemplate等,如果想要更改默认注入的Template属性。
方式一:
自定义一个RedisTemplate即可。
看一下RedisAutoConfiguration中默认是如何注入redisTemplate的:
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public 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;
}
}
@ConditionalOnMissingBean(name = {"redisTemplate"}),很明显了,没有RedisTemplate Bean的时候它才会执行注入。
自定义一个RedisTemplate,修改RedisTemplate的序列化方式、开启事务:
@Bean("redisTemplate")
public RedisTemplate<Object, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 修改序列化为Jackson
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
// 开启事务
template.setEnableTransactionSupport(true);
return template;
}
方式二:
application.properties中配置。
例如,修改kafka序列化方式:去KafkaProperties中查找它序列化方法对应的属性
然后直接在application.properties修改默认序列化就好:
1 spring.kafka.producer.key-serializer=org.springframework.kafka.support.serializer.JsonSerializer
2 spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
application.properties配置是如何生效:
每一个XxAutoConfiguration自动配置类都是在某些条件下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项:
@ConditionalOnBean:当容器里有指定的bean的条件下。
@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
@ConditionalOnClass:当类路径下有指定类的条件下。
@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
@ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。
以spring.redis.host=192.168.56.10为例,观察application.properties中的全局配置是如何生效的。
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
}
观察RedisAutoConfiguration自动配置类,有一个@EnableConfigurationProperties注解:支持配置属性,而它后面的参数是一个RedisProperties类,这就是“约定大于俗成”生效的地方。
@ConfigurationProperties(
prefix = "spring.redis"
)
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
private boolean ssl;
private Duration timeout;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
public RedisProperties() {
}
}
RedisProperties类上的@ConfigurationProperties注解作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties({RedisProperties.class})负责导入这个已经绑定了属性的bean到spring容器中。那么所有和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxProperties类,它与配置文件中prefix关键字开头的一组属性是唯一对应的。
总结:Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,过滤后对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的(类中方法都是@Bean、@ConditionalOnMissingBean等注入对象到spring容器中),它实际上就是一个JavaConfig形式的Spring容器配置类,它通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:spring.redis.host,而XxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。即AutoConfiguration是一个配置类,为了添加组件的,Properties是一个属性类,为了封装配置文件中的属性的。
如果我们自定义了一个starter的话,也要在该starter的jar包中提供 spring.factories文件,并且为其配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.test.XxAutoConfiguration对应的配置类。