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