写点什么

深入了解 Spring 之 ConfigurationClassPostProcessor

用户头像
邱学喆
关注
发布于: 50 分钟前
深入了解Spring之ConfigurationClassPostProcessor

一. 概述

该 ConfigurationClassPostProcessor 类实现了 BeanDefinitionRegistryPostProcessor 接口,可以拿到 BeanDefinitionRegistry 去做额外的对象实例注入。其是专门处理在类头部标注 @Configuration 注解的对象;

二. 原理

其是解析标有 @Configuration 注解的实例对象,解析该实例类中的 @PropertySources、@ComponentScans、@Import、@ImportResource、@Bean 等注解。主要的代码实现在 ConfigurationClassPostProcessor.processConfigBeanDefinitions 方法;

其大体的流程如下:

然而现在却很复杂,但总体的逻辑跟上面的逻辑是差不多的;

我们将重点解读 ConfigurationClassParser 类的解析过程,以及如何注入过程的;我们先按照注解的形式去拆分去解读;这里大量采用的递归的形式去实现,我们跳过递归,重点看关键代码进行解读;

2.1 @PropertySources

该注解是解析对应的配置文件,从而将文件的内容注入到环境对象 Environment 中去;解析一下 PropertySources 注解的属性:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Repeatable(PropertySources.class)public @interface PropertySource {	/**	 * 注入到Environment中的Map集合中的key的值;这个不是很重要,如果不填,系统会自动填充;   * 该名称只是充当标识而已;   */	String name() default "";
/** * 文件路径 */ String[] value();
/** * 如果没有找到文件文件,是否忽略; */ boolean ignoreResourceNotFound() default false;
/** * 文件的编码格式 */ String encoding() default "";
/** * 文件解析器,默认是properties解析器 */ Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
复制代码

该代码相对于比较简单,其入口 ConfigurationClassParser.processPropertySource 方法;感兴趣的可以去查阅;

2.2 @ComponentScan

该注解是扫描指定的包下的类文件,判断是否满足对应的条件,如果满足,则将实例对象注入到容器中;默认情况(不修改任何配置时),满足条件下列任何一个条件,如下:

  • 实例对象头部有标注 @org.springframework.stereotype.Component 注解

  • 实例对象头部有标注 @javax.annotation.ManagedBean 注解

  • 实例对象头部有标注 @javax.inject.Named 注解

看一下 @ComponentScan 注解的属性,进行介绍:

public @interface ComponentScan {
/** * 基础包名 */ @AliasFor("basePackages") String[] value() default {};
/** * 基础包名 */ @AliasFor("value") String[] basePackages() default {};
/** * 指明某个类所在的包 */ Class<?>[] basePackageClasses() default {};
/** * 命名生成器。基本默认即可 */ Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/** * scope解析器,默认即可 */ Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
/** * scope代理模式 */ ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
/** * 文件资源匹配规则,不清楚,默认即可; */ String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/** * 是否使用默认过滤器,一般不改这个配置项; * 当为true时,会自动填充基于@Component、@ManagedBean、@Named注解的过滤器。 */ boolean useDefaultFilters() default true;
/** * 自定义过滤器 */ Filter[] includeFilters() default {};
/** * 自定义排他过滤器 */ Filter[] excludeFilters() default {};
/** * 是否懒加载 */ boolean lazyInit() default false;
}
复制代码

基本上我们常规的配置,是配置基础包或者指定某个类所在的包;至于其他属性,保留即可;如需修改其他配置项,还需进一步解读其代码。目前没有过多解读。所以不在阐述。待有这方面的需求时,在进一步去了解;

关键的代码:ConfigurationClassParser.parse

2.3 @Import

public @interface Import {
/** * 指定的类 */ Class<?>[] value();
}
复制代码

该注解自动导入指定的类,实例该对象注入到容器;然而注解指定的类有三种类型,类型如下

  • ImportSelector 类型,会调用该 ImportSelector 类中的 selectImports 方法来决定注入哪些类对象;

  • ImportBeanDefinitionRegistrar 类型,会调用该 ImportBeanDefinitionRegistrar 类中的 registerBeanDefinitions 方法去注入自己想要注入的对象;

  • 其他类型,将其认为是 @Configuration,接着进一步解析;

关键的代码:

  • ConfigurationClassParser.processImports

  • ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass

  • -> ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars

  • ConfigurationClassBeanDefinitionReader.registerBeanDefinitionForImportedConfigurationClass

  • ConfigurationClassParser.processDeferredImportSelectors

2.4 @ImportResource

public @interface ImportResource {
/** * 文件路径 */ @AliasFor("locations") String[] value() default {};
/** * 文件路径 */ @AliasFor("value") String[] locations() default {};
/** * 文件解析器 */ Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
}
复制代码

这个就是我们没有早期通过 xml 文件去配置的 Bean;

通过解析指定的路径,解析文件中定义的 Bean 注入到容器内;

关键的代码:

  • ConfigurationClassParser.processImports

  • ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass

  • -> ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources

2.5 @Bean

public @interface Bean {
/** * */ @AliasFor("name") String[] value() default {};
/** * bean的实例名称 */ @AliasFor("value") String[] name() default {};
/** * 注入类型 */ Autowire autowire() default Autowire.NO;
/** * 初始化方法 */ String initMethod() default "";
/** * 销毁方法 */ String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
复制代码

解析方法中标有该注解的方法,封装成 BeanMethod 对象,接着将其再次封装成 Definition 对象注入到容器中;

三. Conditional

在看前面的代码的总会看到相关 conditionEvaluator.shouldSkip 的地方;该类去判断类对象中标有 @Conditional 注解以及其子注解。然后加载对应的 Condition 类型的对象去去执行 matches 判断条件是否满足;其逻辑相对于比较简单;

感兴趣的,可以查看 conditionEvaluator 类代码,以及 @Conditional 下的子注解,还有 Condition 实现类;

@Conditional(OnResourceCondition.class)public @interface ConditionalOnResource {
/** * The resources that must be present. * @return the resource paths that must be present. */ String[] resources() default {};
}
复制代码

四. 运用

在 spring-boot-autoconfigure 包中有大量都是基于使用的用法;随便那个类进行介绍;

@Configuration@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })@EnableConfigurationProperties(DataSourceProperties.class)@Import({ DataSourcePoolMetadataProvidersConfiguration.class,		DataSourceInitializationConfiguration.class })public class DataSourceAutoConfiguration {
@Configuration @Conditional(EmbeddedDatabaseCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import(EmbeddedDataSourceConfiguration.class) protected static class EmbeddedDatabaseConfiguration {
}
@Configuration @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration {
}
/** * {@link AnyNestedCondition} that checks that either {@code spring.datasource.type} * is set or {@link PooledDataSourceAvailableCondition} applies. */ static class PooledDataSourceCondition extends AnyNestedCondition {
PooledDataSourceCondition() { super(ConfigurationPhase.PARSE_CONFIGURATION); }
@ConditionalOnProperty(prefix = "spring.datasource", name = "type") static class ExplicitType {
}
@Conditional(PooledDataSourceAvailableCondition.class) static class PooledDataSourceAvailable {
}
}
/** * {@link Condition} to test if a supported connection pool is available. */ static class PooledDataSourceAvailableCondition extends SpringBootCondition {
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage.Builder message = ConditionMessage .forCondition("PooledDataSource"); if (getDataSourceClassLoader(context) != null) { return ConditionOutcome .match(message.foundExactly("supported DataSource")); } return ConditionOutcome .noMatch(message.didNotFind("supported DataSource").atAll()); }
/** * Returns the class loader for the {@link DataSource} class. Used to ensure that * the driver class can actually be loaded by the data source. * @param context the condition context * @return the class loader */ private ClassLoader getDataSourceClassLoader(ConditionContext context) { Class<?> dataSourceClass = DataSourceBuilder .findType(context.getClassLoader()); return (dataSourceClass == null ? null : dataSourceClass.getClassLoader()); }
}
/** * {@link Condition} to detect when an embedded {@link DataSource} type can be used. * If a pooled {@link DataSource} is available, it will always be preferred to an * {@code EmbeddedDatabase}. */ static class EmbeddedDatabaseCondition extends SpringBootCondition {
private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage.Builder message = ConditionMessage .forCondition("EmbeddedDataSource"); if (anyMatches(context, metadata, this.pooledCondition)) { return ConditionOutcome .noMatch(message.foundExactly("supported pooled data source")); } EmbeddedDatabaseType type = EmbeddedDatabaseConnection .get(context.getClassLoader()).getType(); if (type == null) { return ConditionOutcome .noMatch(message.didNotFind("embedded database").atAll()); } return ConditionOutcome.match(message.found("embedded database").items(type)); }
}
}
复制代码

五. 总结

通过上面的粗糙的介绍,对其 ConfigurationClassPostProcessor 原理有了一定的认识;目前阶段,只是了解其是如何解析的,具体代码细节以及各种条件判断的,并没有过多解读;待后期,进一步思考其代码背后的设计思想,才去完善该篇文章。

发布于: 50 分钟前阅读数: 2
用户头像

邱学喆

关注

计算机原理的深度解读,源码分析。 2018.08.26 加入

在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan

评论

发布
暂无评论
深入了解Spring之ConfigurationClassPostProcessor