一. 概述
该 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 原理有了一定的认识;目前阶段,只是了解其是如何解析的,具体代码细节以及各种条件判断的,并没有过多解读;待后期,进一步思考其代码背后的设计思想,才去完善该篇文章。
评论