SpringBoot學習系列之一:配置自動化
引言
大家都知道SpringBoot簡化了Spring開發工作,讓開發者不用再去面對繁瑣的配置,可以使我們可以迅速上手進行開發,將重點放在業務邏輯的實現上。但也正因為這樣,使得開發者容易忽略對於其背後原理的理解。我們可能知道怎麼用,但是實際上並不知道SpringBoot如何實現自動配置以及如何通過內建tomcat進行啟動等等的原理。為了探究SpringBoot背後的技術原理,特地將學習的過程記錄下來形成一個文章系列,另外希望對這方面有相同困惑的同學有所裨益。
- 自動配置介紹
- Kafka自動配置原始碼分析
- 總結
一、自動配置介紹
我們都知道,在沒有SpringBoot
之前,利用Spring
Spring
最初使用Bean Factory
以及動態代理實現各模組之間的解耦,它通過配置檔案將bean掃描到Spring容器中。而SpringBoot
將這種xml解析配置的過程,通過註解自動配置的方式來進行替換,它根據定義在classpath下的類,自動生成對應的bean
,同時將其載入到Spring
的context
中。SpringBoot
通過條件化配置來啟動某個能力項。
在SpringBoot啟動類WebApplication中,可以看到很多個註解。我們知道SpringBoot專案是高度依賴註解的,它可以在main函式中啟動整個應用。
@SpringBootApplication(scanBasePackages = {"com.test"})
@MapperScan("com.test.module.mapper")
@ImportResource(locations = {"classpath:springMVC-servlet.xml"})
@ServletComponentScan
public class WebApplication extends SpringBootServletInitializer{
static Logger logger = LoggerFactory.getLogger(WebApplication.class);
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
以上程式碼中,@SpringBootApplication
是SpringBoot
的核心註解,它是一系列註解的集合。它對應的原始碼如下所示。在這些註解當中@EnableAutoConfiguration
即為當前的專案提供自動配置功能,它也是一系列註解的集合。該註解可以讓Spring Boot
根據類路徑中的jar包依賴為當前專案進行自動配置。
@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 {};
}
通過註解的方式實現配置的自動化,主要在spring-boot-autoconfigure-1.4.3.RELEASE-sources.jar這個jar包中提供了對於SpringBoot自動化配置的支援。這個jar包中包含了如下包,篇幅有限只列出了部分包。
在這個jar包中的META-INF資料夾中,可以看到spring.factories檔案
在spring.factories檔案中我們看到了一些初始化的類、監聽器以及構建類等等。
我們具體看一下@EnableAutoConfiguration
這個註解裡面的內容。Spring中有很多@Enable-*開頭的註解,類似@EnableScheduling
以及@EnableCaching
等等,這類註解即為該修飾的類賦予某項能力,在每個該類註解中都會通過@Import註解來匯入實現對應功能的類。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.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 {};
}
在該註解中引入了EnableAutoConfigurationImportSelector
這個類,按照這個類的字面理解為自動配置匯入選擇器,它實現了以下幾個介面。
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered{
......
}
在這個類中,使用SpringFactoriesLoader.loadFactoryNames方法來掃描具有META-INF/spring.factories檔案的jar包,spring-boot-autoconfigure-x.x.x.x.jar裡就有一個spring.factories檔案,這個檔案中聲明瞭有哪些類要自動配置。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
//獲取
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
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;
}
二、Kafka自動配置原始碼分析
下面分析下Kafka自動配置類,貼上其原始碼,相關原始碼已經加上註釋。
//配置註解
@Configuration
//KafkaTemplate類在classpath目錄下存在時,才會去解析KafkaAutoConfiguration自動配置類
@ConditionalOnClass(KafkaTemplate.class)
//自動注入屬性,如果在application.properties配置檔案中定義,則會將配置檔案中key對應的value值注入到KafkaProperties中
@EnableConfigurationProperties(KafkaProperties.class)
//匯入KafkaAnnotationDrivenConfiguration
@Import(KafkaAnnotationDrivenConfiguration.class)
public class KafkaAutoConfiguration {
private final KafkaProperties properties;
private final RecordMessageConverter messageConverter;
public KafkaAutoConfiguration(KafkaProperties properties,
ObjectProvider<RecordMessageConverter> messageConverter) {
this.properties = properties;
this.messageConverter = messageConverter.getIfUnique();
}
//向Spring容器注入bean
@Bean
//在上下文中沒有KafkaTemplate時,才會例項化bean
@ConditionalOnMissingBean(KafkaTemplate.class)
public KafkaTemplate<?, ?> kafkaTemplate(
ProducerFactory<Object, Object> kafkaProducerFactory,
ProducerListener<Object, Object> kafkaProducerListener) {
KafkaTemplate<Object, Object> kafkaTemplate = new KafkaTemplate<>(
kafkaProducerFactory);
if (this.messageConverter != null) {
kafkaTemplate.setMessageConverter(this.messageConverter);
}
kafkaTemplate.setProducerListener(kafkaProducerListener);
kafkaTemplate.setDefaultTopic(this.properties.getTemplate().getDefaultTopic());
return kafkaTemplate;
}
@Bean
@ConditionalOnMissingBean(ProducerListener.class)
public ProducerListener<Object, Object> kafkaProducerListener() {
return new LoggingProducerListener<>();
}
@Bean
@ConditionalOnMissingBean(ConsumerFactory.class)
public ConsumerFactory<?, ?> kafkaConsumerFactory() {
return new DefaultKafkaConsumerFactory<>(
this.properties.buildConsumerProperties());
}
@Bean
@ConditionalOnMissingBean(ProducerFactory.class)
public ProducerFactory<?, ?> kafkaProducerFactory() {
DefaultKafkaProducerFactory<?, ?> factory = new DefaultKafkaProducerFactory<>(
this.properties.buildProducerProperties());
String transactionIdPrefix = this.properties.getProducer()
.getTransactionIdPrefix();
if (transactionIdPrefix != null) {
factory.setTransactionIdPrefix(transactionIdPrefix);
}
return factory;
}
@Bean
@ConditionalOnProperty(name = "spring.kafka.producer.transaction-id-prefix")
@ConditionalOnMissingBean
public KafkaTransactionManager<?, ?> kafkaTransactionManager(
ProducerFactory<?, ?> producerFactory) {
return new KafkaTransactionManager<>(producerFactory);
}
@Bean
@ConditionalOnProperty(name = "spring.kafka.jaas.enabled")
@ConditionalOnMissingBean
public KafkaJaasLoginModuleInitializer kafkaJaasInitializer() throws IOException {
KafkaJaasLoginModuleInitializer jaas = new KafkaJaasLoginModuleInitializer();
Jaas jaasProperties = this.properties.getJaas();
if (jaasProperties.getControlFlag() != null) {
jaas.setControlFlag(jaasProperties.getControlFlag());
}
if (jaasProperties.getLoginModule() != null) {
jaas.setLoginModule(jaasProperties.getLoginModule());
}
jaas.setOptions(jaasProperties.getOptions());
return jaas;
}
@Bean
@ConditionalOnMissingBean
public KafkaAdmin kafkaAdmin() {
KafkaAdmin kafkaAdmin = new KafkaAdmin(this.properties.buildAdminProperties());
kafkaAdmin.setFatalIfBrokerNotAvailable(this.properties.getAdmin().isFailFast());
return kafkaAdmin;
}
}
三、總結
將SpringBoot自動配置過程用流程圖進行表示,更能形象化的理解自動配置的流程。