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 自动配置原理,你应该能明白了吧

发布了111 篇原创文章 · 获赞 33 · 访问量 19万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览