springboot的自動配置是使用springboot的一大原因,那自動配置具體是如何加載運行的呢?主要是依靠@SpringBootApplication註解。
@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容器中,通過加載注入全限類名對應的類來完成容器啓動時配置的自動裝載。
XxxTemplate模板類的默認屬性修改:
springboot爲starter生成了xxTemplate的工具類,例如:RedisTemplate、MongoTemplate、KafkaTemplate等,如果想要更改默認注入的Template屬性。
方式一:
修改RedisTemplate的序列化方式、開啓事務,向容器中注入一個新的RedisTemplate即可,它會覆蓋默認的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;
}
}
@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;
}
方式二:
kafka消費接收的數據默認是字符串,如何改成一個對象?
1.首先去KafkaAutoConfiguration(XxxAutoConfiguration)中查看是否有Serializer屬性的配置
@Configuration
@ConditionalOnClass({KafkaTemplate.class})
@EnableConfigurationProperties({KafkaProperties.class})
@Import({KafkaAnnotationDrivenConfiguration.class, KafkaStreamsAnnotationDrivenConfiguration.class})
public class KafkaAutoConfiguration {
private final KafkaProperties properties;
private final RecordMessageConverter messageConverter;
}
2.發現沒有,再去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
3 spring.kafka.consumer.key-deserializer=com.example.common.MyJson
4 spring.kafka.consumer.value-deserializer=com.example.common.MyJson
application.properties配置是如何生效:
每一個XxxAutoConfiguration自動配置類都是在某些條件下才會生效的,這些條件的限制在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自動配置類(XxxAutoConfiguration),有一個@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容器中。那麼所有其他的和這個類相關的屬性都可以在全局配置文件中定義,也就是說,真正“限制”我們可以在全局配置文件中配置哪些屬性的類就是這些XxxProperties類,它與配置文件中prefix關鍵字開頭的一組屬性是唯一對應的。
總結:Spring Boot啓動的時候會通過@EnableAutoConfiguration註解找到META-INF/spring.factories配置文件中的所有自動配置類,過濾後對其進行加載,而這些自動配置類都是以AutoConfiguration結尾來命名的(類中方法都是@Bean、@ConditionalOnMissingBean等注入對象到spring容器中),它實際上就是一個JavaConfig形式的Spring容器配置類,它通過以Properties結尾命名的類中取得在全局配置文件中配置的屬性如:spring.redis.host,而XxxProperties類是通過@ConfigurationProperties註解與全局配置文件中對應的屬性進行綁定的。即AutoConfiguration是一個配置類,爲了添加組件的,Properties是一個屬性類,爲了封裝配置文件中的屬性的。
如果我們自定義了一個starter的話,也要在該starter的jar包中提供 spring.factories文件,並且爲其配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.test.XxAutoConfiguration對應的配置類。所有框架的自動配置流程基本都是一樣的,判斷是否引入框架,獲取配置參數,根據配置參數初始化框架相應組件。