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