在上篇《springboot学习(十五):Kafka的使用》博文中,通过简单的示例介绍了如何在springboot项目中使用kafka。代码十分简单,通过配置文件和注解就可以操作kakfa集群。本篇博文将通过springboot的源码来了解springboot如何自动装配kafka。
从程序启动入口入手阅读代码:
@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> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration 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);
}
}
}
首先获取并启动应用运行监听器:
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方法的。
在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 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 imports, Set 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 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 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 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 getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List 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类中,通过@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 自动配置原理,你应该能明白了吧