springboot學習(十六):瞭解springboot自動裝配kafka原理

說明

在上篇《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 啓動原理嗎?

Spring 工具類 ConfigurationClassParser 分析得到配置類

這樣講 SpringBoot 自動配置原理,你應該能明白了吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章