說明
在上篇《springboot學習(十五):Kafka的使用》博文中,通過簡單的示例介紹瞭如何在springboot項目中使用kafka。代碼十分簡單,通過配置文件和註解就可以操作kakfa集羣。本篇博文將通過springboot的源碼來了解springboot如何自動裝配kafka。
創建SpringApplication
從程序啓動入口入手閱讀代碼:
@SpringBootApplication
public class Kafka2Application {
public static void main(String[] args) {
SpringApplication.run(Kafka2Application.class, args);
}
}
在以上代碼中,使用了@SpringBootApplication註解,在main方法中調用了SpringApplication的run方法。
@SpringBootApplication註解是一個組合註解:
它主要包含了@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan等註解,通過這三個註解實現了bean的配置和加載。
其中@EnableAutoConfiguration註解中通過@import引入了AutoConfigurationImportSelector類,該類對springboot的自動裝配十分重要。
@ComponentScan註解通過掃描包中的@Bean註解來實現bean加載,當沒有指定包的位置時,會默認掃描該註解所在包,所以啓動類被放置在“最外面”。
在調用SpringApplication的run方法時,將啓動類作爲參數,創建SpringApplication對象。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
在創建SpringApplication對象的方法中,通過判斷classpath中類的類型來設置WebApplication的類型,並設置項目初始化器和監聽器。
this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 設置WebApplication的類型
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 設置初始化器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); // 設置監聽器
在設置初始化器和監聽器時,都調用了getSpringFactoriesInstances方法,並設置了不同類類對象做爲參數,而在該改方法中使用了SpringFactoriesLoader的loadFactoryNames方法,獲取META-INF/spring.factories文件中配置的對應類的全限定名。
在springboot2.0版本中對loadFactoryNames方法做了改進,一次加載spring.factories文件中的所有內容並緩存起來。在之前的版本中會不斷重複加載,只獲取當前需要的內容。
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");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
調用run()方法
首先獲取並啓動應用運行監聽器:
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
創建配置環境environment:
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
根據之前推斷的WebApplication的類型創建應用上下文類型對象applicationContextClass:
context = this.createApplicationContext();
接下來,調用prepareContext方法準備上下文:
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
在該方法中,調用了之前設置的初始化器,並在beafactory中註冊了啓動類。
this.applyInitializers(context);
...
this.load(context, sources.toArray(new Object[0]));
在調用refreshContext方法刷新上下文,該方法就是spring的正常啓動流程,進行註冊bean等動作(這裏不詳解spring的啓動流程):
this.refreshContext(context);
在refresh方法中,調用了beanfactory的後置處理器,在這裏將會讀取/META-INF/spring.factories文件中配置的所有EnableAutoConfiguration實現類。
this.invokeBeanFactoryPostProcessors(beanFactory);
之前我們提到在@EnableAutoConfiguration註解中引入了AutoConfigurationImportSelector類,該類對springboot的自動裝配十分重要,這裏我們可以詳細瞭解下spring是何時如何調用該類的selectImports方法的。
selectImports()
在run方法中,在調用refreshContext()方法前,先調用了createApplicationContext()方法來創建應用上下文。在該方法中會根據推斷的WebApplicationType來創建對應的上下文類,這裏創建了AnnotationConfigServletWebServerApplicationContext類對象作爲應用上下文對象。
public AnnotationConfigServletWebServerApplicationContext() {
this.annotatedClasses = new LinkedHashSet();
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
在該類的無參構造函數中,我們可以看到創建了兩個bean定義讀取類,分別是AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner,在創建AnnotatedBeanDefinitionReader對象時,使用AnnotationConfigUtils在beanFactory中註冊了AnnotationConfigProcessors,其中beanName爲internalConfigurationAnnotationProcessor的解析類用來解析註解配置。就是該類來解析啓動類上的註解,該bean實際是ConfigurationClassPostProcessor。
上面提到,在refresh方法中需要調用invokeBeanFactoryPostProcessors方法,在該方法中,首先調用了invokeBeanDefinitionRegistryPostProcessors方法,發現並註冊所有的bean。
在ConfigurationClassPostProcessor類的processConfigBeanDefinitions方法中,使用了ConfigurationClassParser工具類來解析已經註冊的bean,上面我們提到在創建StringApplication對象時,將啓動類作爲source參數傳遞,該類也被註冊到beanFactory。
使用ConfigurationClassParser的parse方法,處理所有的configCandidates類。啓動類是一個註解類,會調用第一個parse(AnnotatedBeanDefinition)方法。
public void parse(Set<BeanDefinitionHolder> configCandidates) {
Iterator var2 = configCandidates.iterator();
while(var2.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName()); // 處理註解bean
} else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)bd).hasBeanClass()) {
this.parse(((AbstractBeanDefinition)bd).getBeanClass(), holder.getBeanName());
} else {
this.parse(bd.getBeanClassName(), holder.getBeanName());
}
} catch (BeanDefinitionStoreException var6) {
throw var6;
} catch (Throwable var7) {
throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", var7);
}
}
this.deferredImportSelectorHandler.process();
}
在processConfigurationClass方法中,啓動類被作爲SourceClass類處理:
ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass);
do {
sourceClass = this.doProcessConfigurationClass(configClass, sourceClass);
} while(sourceClass != null);
在doProcessConfigurationClass方法中,通過以下代碼獲取並處理所有通過@Import主解引入的類:
this.processImports(configClass, sourceClass, this.getImports(sourceClass), true);
在getImports方法中,以遞歸的方式調用collectImports處理所有的註解,獲取所有@Import引入的類:
private void collectImports(ConfigurationClassParser.SourceClass sourceClass, Set<ConfigurationClassParser.SourceClass> imports, Set<ConfigurationClassParser.SourceClass> visited) throws IOException {
if (visited.add(sourceClass)) {
Iterator var4 = sourceClass.getAnnotations().iterator();
while(var4.hasNext()) {
ConfigurationClassParser.SourceClass annotation = (ConfigurationClassParser.SourceClass)var4.next();
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
this.collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
在processImports方法中,循環處理得到的所有importCandidates。 注意:由於AutoConfigurationImportSelector實現了DeferredImportSelector接口,所以在該方法中並不會立即調用selectImports方法:
while(var5.hasNext()) {
ConfigurationClassParser.SourceClass candidate = (ConfigurationClassParser.SourceClass)var5.next();
Class candidateClass;
if (candidate.isAssignable(ImportSelector.class)) {
candidateClass = candidate.loadClass();
ImportSelector selector = (ImportSelector)BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector); // 調用該方法將所有的DeferredImportSelector先保存起來
} else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<ConfigurationClassParser.SourceClass> importSourceClasses = this.asSourceClasses(importClassNames);
this.processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}......
deferredImportSelectorHandler的handle方法,將發現的所有DeferredImportSelector保存到它的list集合中:
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
ConfigurationClassParser.DeferredImportSelectorHolder holder = new ConfigurationClassParser.DeferredImportSelectorHolder(configClass, importSelector);
if (this.deferredImportSelectors == null) {
ConfigurationClassParser.DeferredImportSelectorGroupingHandler handler = ConfigurationClassParser.this.new DeferredImportSelectorGroupingHandler();
handler.register(holder);
handler.processGroupImports();
} else {
this.deferredImportSelectors.add(holder); // 保存到list集合中
}
}
當這些方法都調用完後,我們再回到最初的parse方法,將自動類作爲AnnotatedBeanDefinition調用了parse(AnnotatedBeanDefinition)方法,在該方法執行完畢後,在這裏調用了之前保存的DeferredImportSelector:
this.deferredImportSelectorHandler.process();
在process方法中,創建了DeferredImportSelectorGroupingHandler對象,註冊deferredImports後調用了該對象的processGroupImports方法:
public void process() {
List<ConfigurationClassParser.DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
ConfigurationClassParser.DeferredImportSelectorGroupingHandler handler = ConfigurationClassParser.this.new DeferredImportSelectorGroupingHandler();
deferredImports.sort(ConfigurationClassParser.DEFERRED_IMPORT_COMPARATOR);
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
} finally {
this.deferredImportSelectors = new ArrayList();
}
}
在processGroupImports中處理了handler中所有的DeferredImportSelectorGrouping,注意:這裏調用了DeferredImportSelectorGrouping的getImports方法,並循環又調用了processImports方法:
while(var1.hasNext()) {
ConfigurationClassParser.DeferredImportSelectorGrouping grouping = (ConfigurationClassParser.DeferredImportSelectorGrouping)var1.next();
grouping.getImports().forEach((entry) -> {
ConfigurationClass configurationClass = (ConfigurationClass)this.configurationClasses.get(entry.getMetadata());
try {
ConfigurationClassParser.this.processImports(configurationClass, ConfigurationClassParser.this.asSourceClass(configurationClass), ConfigurationClassParser.this.asSourceClasses(entry.getImportClassName()), false);
} catch (BeanDefinitionStoreException var4) {
throw var4;
} catch (Throwable var5) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", var5);
}
});
}
在getImports方法中,循環調用了process方法,最後調用了selectImports方法:
public Iterable<Entry> getImports() {
Iterator var1 = this.deferredImports.iterator();
while(var1.hasNext()) {
ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
}
return this.group.selectImports();
}
在process方法中,調用了getAutoConfigurationEntry方法獲取所有的配置類:
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(this.getAutoConfigurationMetadata(), annotationMetadata);
在getAutoConfigurationEntry方法中又調用了getCandidateConfigurations方法獲取配置類,在該方法中,使用SpringFactoriesLoader工具類,從/META-INF/spring.factories配置文件中獲取EnableAutoConfiguration配置的所有自動配置類:
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
在spring.factories配置文件中,我們可以看到org.springframework.boot.autoconfigure.EnableAutoConfiguration所有的自動配置類的全限定類名,Kafka的自動配置類爲:org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
經過一系列處理後,調用了selectImports方法,返回所有配置類集合,再對每個配置類調用processImports方法,對類註解再進行類型的處理,直到發現所有的bean,並註冊。
至此,通過走讀springboot的啓動源碼,我們得到了kafka的自動配置類,KafkaAutoConfiguration。接下來,我們再看該類,爲配置kafka引入創建了哪些bean。
KafkaAutoConfiguration
在KafkaAutoConfiguration類中,通過@Import註解引入了KafkaAnnotationDrivenConfiguration和KafkaStreamsAnnotationDrivenConfiguration,並依據KafkaProperties配置類,創建了KafkaTemplate,ProducerListener,ConsumerFactory,ProducerFactory等bean對象。
@Configuration
@ConditionalOnClass({KafkaTemplate.class})
@EnableConfigurationProperties({KafkaProperties.class})
@Import({KafkaAnnotationDrivenConfiguration.class, KafkaStreamsAnnotationDrivenConfiguration.class})
public class KafkaAutoConfiguration {
......
}
在KafkaAnnotationDrivenConfiguration類中,又創建了ConcurrentKafkaListenerContainerFactoryConfigurer,kafkaListenerContainerFactory等bean對象。並且該類又依賴EnableKafka,通過@EnableKakfa註解又引入了KafkaBootstrapConfiguration類:
@Configuration
@ConditionalOnClass({EnableKafka.class})
class KafkaAnnotationDrivenConfiguration {
...
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({KafkaBootstrapConfiguration.class})
public @interface EnableKafka {
}
在KafkaBootstrapConfiguration配置類中,又創建了name爲internalKafkaListenerAnnotationProcessor,internalKafkaListenerEndpointRegistry的bean對象。
@Configuration
public class KafkaBootstrapConfiguration {
public KafkaBootstrapConfiguration() {
}
@Bean(
name = {"org.springframework.kafka.config.internalKafkaListenerAnnotationProcessor"}
)
@Role(2)
public KafkaListenerAnnotationBeanPostProcessor kafkaListenerAnnotationProcessor() {
return new KafkaListenerAnnotationBeanPostProcessor();
}
@Bean(
name = {"org.springframework.kafka.config.internalKafkaListenerEndpointRegistry"}
)
public KafkaListenerEndpointRegistry defaultKafkaListenerEndpointRegistry() {
return new KafkaListenerEndpointRegistry();
}
}
至此,springboot自動裝配kafka所有的先提配置類都已創建。
在這個過程中,我首先對springboot的啓動流程進行了簡單介紹,這部分源碼十分複雜,感興趣的同學可以多次斷點閱讀源碼,瞭解springboot腳手架是如何工作的。
接下來,我將繼續閱讀spring kafka的源碼,學習spring對kafka的支持是如何實現的,通過閱讀源碼瞭解其中的原理。
參考資料:
面試官:能說下 SpringBoot 啓動原理嗎?