同步操作将从 huifer/Code-Analysis 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
在这一章笔者将和各位一起探讨 Spring 是如何对配置类(Spring Configuration Bean) 进行解析。
parse
方法分析对于 Spring 配置类的解析笔者在第二十三章的时候简单提到过一个方法。
ConfigurationClassPostProcessor#processConfigBeanDefinitions
中关于配置类的解析 // ..省略其他
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 候选的需要解析的 Bean Definition 容器
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// 已经完成解析得 Spring Configuration Bean
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 解析候选 Bean Definition Holder 集合
parser.parse(candidates);
// ..省略其他
}
在这段代码中我们对于 parser.parser
当时的了解不是很深入,现在笔者将对这个方法做出更详细的分析。下面我们先来看这个方法的代码
ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
方法详情public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
this.deferredImportSelectorHandler.process();
}
在这段 parse
方法中主要执行两个任务:
ImportSelector
相关解析操作。本章主要针对第一个任务进行分析,第二个任务的分析在第二十七章中有详细分析。
processConfigurationClass
分析跟着 parse
方法我们继续向下追踪源代码,不难发现它依赖了 processConfigurationClass
方法。
parse
方法对 processConfigurationClass
方法的依赖protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
下面我们就先对 processConfigurationClass
方法进行分析看看在这个方法中做了那些操作。
processConfigurationClass
方法详情protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 条件注解解析,阶段为配置解析阶段
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// 尝试从配置类缓存中获取配置类
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
// 缓存中的配置类存在
if (existingClass != null) {
// 判断 配置类是否是导入的
if (configClass.isImported()) {
// 判断缓存中的配置类是否是导入的
if (existingClass.isImported()) {
// 合并配置信息
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
// 从配置类缓存中移除当前配置类
this.configurationClasses.remove(configClass);
// 从已知的配置类中移除当前的配置类
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
// 将配置类转换成 SourceClass
SourceClass sourceClass = asSourceClass(configClass);
do {
// 解析配置类
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
// 放入配置类缓存
this.configurationClasses.put(configClass, configClass);
}
通过这段代码的阅读我们来整理整个方法的处理流程
对配置类进行条件注解的条件判断
从配置类缓存中获取配置类对应的缓存配置类信息 existingClass
如果 existingClass
存在
如果当前需要处理的配置类是导入的
如果缓存中的配置类 existingClass
是导入的
existingClass
将和当前正在处理的配置类进行 import
数据合并
如果当前需要处理的配置类不是导入的的
将配置类转换成 SourceClass
对象
解析配置类
将配置类放入配置类缓存中
在这个方法中我们出现了几个变量,下面我们来看这些变量的信息
变量名称 | 变量类型 | 变量作用 |
---|---|---|
conditionEvaluator |
ConditionEvaluator |
条件注解的解析器 |
configurationClasses |
Map<ConfigurationClass, ConfigurationClass> |
存储配置类的缓存结构,key value 相同 |
knownSuperclasses |
Map<String, ConfigurationClass> |
已知的配置类 key:父类名称 value:配置类 |
下面笔者来对这三个变量进行数据展示,为了演示我们需要准备下面的代码
@Configuration
public class SupperConfiguration {
}
@Configuration
public class ExtendConfiguration extends SupperConfiguration {
}
@Configuration
@Import(ExtendConfiguration.class)
public class ThreeVarBeans {
}
@Test
void testThreeVar() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ThreeVarBeans.class);
}
一切准备就绪下面我们来看当前容器中的数据情况
configurationClasses
变量和 knownSuperclasses
变量数据doProcessConfigurationClass
方法分析现在我们了解了解析后的一个结果,下面我们将对这个处理解析的核心方法 doProcessConfigurationClass
进行分析。我们先来看 doProcessConfigurationClass
中的源代码
doProcessConfigurationClass
方法详情@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// 配置类是否存在 Component 注解
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
}
// 处理 PropertySource 和 PropertySource 注解
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 处理 ComponentScans ComponentScan 注解
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// 处理 Import 注解
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
// 处理 ImportResource 注解
// Process any @ImportResource annotations
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 处理 Bean 注解
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 处理 Bean Method
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// 父类配置处理
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
通过阅读这段代码后我们来整理处理顺序
@Component
注解@PropertySource
和 @PropertySources
注解@ComponentScans
和 @ComponentScan
注解@Import
注解@ImportResource
注解@Bean
注解现在我们了解了这七个处理顺序,接下来我们需要做的事情就是对这七个处理行为做更进一步的分析。
@Component
注解我们先来看注解 @Component
的处理过程,先看入口代码
@Component
注解处理的入口代码// 配置类是否存在 Component 注解
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
}
这段代码我们需要继续向下追踪找到 processMemberClasses
,下面我们来看 processMemberClasses
的代码
processMemberClasses
方法详情private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
// 找到当前配置类中存在的成员类
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
// 成员类列表不为空
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
for (SourceClass memberClass : memberClasses) {
// 成员类是否符合配置类候选标准 Component ComponentScan Import ImportResource 注解是否存在
// 成员类是否和配置类同名
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
// 排序候选类
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
// 判断 importStack 中是否存在当前配置类
if (this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// 解析成员类
processConfigurationClass(candidate.asConfigClass(configClass));
}
finally {
this.importStack.pop();
}
}
}
}
}
阅读这段代码后我们来整理这段代码的处理流程
提取当前配置类中的成员类
对成员类列表进行条件过滤,将符合下面条件的类做收集
条件:成员类存在 Component
、 ComponentScan
、 Import
、 ImportResource
或者 Bean
注解
对收集到的内部配置类进行排序。
对符合条件的内部类进行配置类解析。
为了更好的进行这段代码的调试和代码的理解,我们可以编写下面这样的代码来进行测试。
@Component
注解标记@Configuration
public class BigConfiguration {
public class SmallConfigA {
}
@Component
public class SmallConfigB {
}
}
注解上下文编写完成,下面我们来编写测试用例
@Test
void testProcessMemberClasses(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BigConfiguration.class);
}
下面我们来看这个测试用例中的一些数据情况,首先我们要看的是 memberClasses
变量,该对象表示了当前配置类下有那些内部类
memberClasses
数据信息接下来我们看 candidates
变量,该对象表示通过条件过来的内部类,这些内部类会被当做 Spring Configuration Bean 处理
candidates
数据信息最后这些符合条件的内部类会进行配置类解析。这里对于配置类的解析其实就是本文所讨论的内容。
@PropertySource
和 @PropertySources
注解接下来我们来看 @PropertySource
和 @PropertySources
注解的处理入口
@PropertySource
和 @PropertySources
注解处理入口for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
通过注释我们了解到了这是对两个注解的处理,下面我们需要来编写这两个注解的使用案例,有了这个使用案例我们就可以更好的进行分析。
首先我们来编写一个配置文件 data.properties
a=123
接下来我们编写测试类及测试方法
@PropertySource(value = "classpath:data.properties")
@Configuration
public class PropertySourceTest {
@Test
void testPropertySource() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertySourceTest.class);
assert context.getEnvironment().getProperty("a").equals("123");
}
}
这样我们就做好了一个测试类下面我们就开始源码分析吧。
首先我们来看这两个注解的定义
PropertySource
注解定义@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
String name() default "";
String[] value();
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
PropertySources
注解定义@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources {
PropertySource[] value();
}
通过阅读注解@PropertySources
我们可以发现该注解内部包含的属性 value
是注解 @PropertySource
的集合,那么我们对于这段代码的理解就容易了。
AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)
这段代码将两个注解的数据都进行读取得到一个 PropertySources
注解数据集合,我们来改造一下测试类上的注解
@PropertySource(value = "classpath:data.properties")
@PropertySources(value = @PropertySource("classpath:InPropertySources"))
@Configuration
public class PropertySourceTest {
@Test
void testPropertySource() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertySourceTest.class);
assert context.getEnvironment().getProperty("a").equals("123");
}
}
通过这样的改造我们来看这个方法得到的数据情况
PropertySources
读取后的数据可以看到这里所有的注解会被整合在一个集合中这个集合的数据是注解@PropertySource
的全属性,由于笔者没有编写 InPropertySources
文件我们将测试类还原
@PropertySource(value = "classpath:data.properties")
@Configuration
public class PropertySourceTest {
@Test
void testPropertySource() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertySourceTest.class);
assert context.getEnvironment().getProperty("a").equals("123");
}
}
现在我们了解了 propertySource
中的数据,下面我们来看 processPropertySource
方法的处理过程。
processPropertySource
方法详情private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
// 提取注解属性中的 name 属性
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
// 提取注解属性中的 encoding 属性
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
// 提取注解属性中的 value 属性
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
// 提取注解属性中的 ignoreResourceNotFound 属性
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
// 提取注解属性中的 factory 属性
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
// 循环处理每个配置文件
for (String location : locations) {
try {
// 解析配置文件地址
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
// 资源加载其加载资源
Resource resource = this.resourceLoader.getResource(resolvedLocation);
// 加入配置
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
阅读源代码后我们先来对这个方法进行流程整理。
@PropertySource
注解的属性locations
进行单个处理
location
进行路径解析location
解析后的结果进行资源对象读取转换成 Resource
对象对于 location
的解析请翻阅第十二章
对于 location
转换成 Resoruce
的解析请翻阅第十八章
最后添加属性我们还没没有对其进行分析,下面我们来看这里的处理。首先我们需要理解 factory.createPropertySource(name, new EncodedResource(resource, encoding))
这段代码。这里我们对于 factory
变量需要先做认识,在提取注解属性的时候 Spring 对这个变量的获取做出如下操作
PropertySourceFactory
对象的确认PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
这里我们可以看到它是对注解配置中 factory
是否存在做出两种处理。
factory
属性不存在则采用默认的 PropertySourceFactory
,具体类是 org.springframework.core.io.support.DefaultPropertySourceFactory
factory
属性存在通过 BeanUtils
反射创建该对象我们的测试用例中符合第一种处理情况, 我们来看 DefaultPropertySourceFactory
中的实现
DefaultPropertySourceFactory
类详情public class DefaultPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
}
这里就是创建一个对象 ResourcePropertySource
,我们继续深入挖掘 ResourcePropertySource
的构造函数
name
和 resource
的构造函数public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
// 设置 name + map 对象
// map 对象是 资源信息
super(name, PropertiesLoaderUtils.loadProperties(resource));
// 获取 resource name
this.resourceName = getNameForResource(resource.getResource());
}
resource
的构造函数public ResourcePropertySource(EncodedResource resource) throws IOException {
// 设置 key: name, resource 的 name
// 设置 value: resource 资源信息
super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
this.resourceName = null;
}
源码追踪到这里相信各位发现了这两段构造函数的共同点: PropertiesLoaderUtils.loadProperties(resource)
,该方法将资源文件解析转换成Properties
对象。
现在我们对于 factory.createPropertySource(name, new EncodedResource(resource, encoding))
方法的分析完成了,我们来看看这个方法的执行结果
factory.createPropertySource(name, new EncodedResource(resource, encoding))
执行结果可以看到 data.properties
中的数据被解析出来了,并存储在 source
中。PropertySource
对象的获取我们已经完成,接下来我们需要做的事情是将这个数据放入到 Spring 中,具体方法是 addPropertySource
addPropertySource
方法详情private void addPropertySource(PropertySource<?> propertySource) {
// 获取配置名称
String name = propertySource.getName();
// 提取环境对象这种的 MutablePropertySources 对象
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
// 当前处理的配置名称是否在 propertySourceNames 存在
if (this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
// 从 propertySources 中获取当前配置名称对应的 PropertySource
PropertySource<?> existing = propertySources.get(name);
if (existing != null) {
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
return;
}
}
// 如果 propertySourceNames 为空
if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
this.propertySourceNames.add(name);
}
在这段代码中我们可以看到我们操作的对象有两个,一个是 environment
中的 MutablePropertySources
属性,另一个是 propertySourceNames
,先来看这两个变量的存储。
propertySourceNames
中的存储比较简单,存储形式:List<String>
,存储内容是配置名称
MutablePropertySources
存储多个 PropertySource<?>
,具体结构如下
MutablePropertySources
中的存储内容
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
}
addPropertySource
的处理逻辑主要是围绕配置名称是否存在于 propertySourceNames
中和 配置对象的类型进行处理,下面我们来看处理流程
MutablePropertySources
对象。MutablePropertySources
中提取当前配置名称对应的 PropertySource
,命名为existing
。existing
存在。
PropertySource
进行类型判断并转换,判断是否是 ResourcePropertySource
类型。existing
进行类型判断。
CompositePropertySource
,采用头插法插入数据。propertySourceNames
为空进行尾插法插入数据。propertySourceNames
不为空进行指定标记后往前插入。propertySourceNames
中。最后我们来看整个方法的处理结果
propertySourceNames
数据信息environment
数据信息@ComponentScans
和 @ComponentScan
注解接下来我们来看 @ComponentScans
和 @ComponentScan
注解的处理入口
@ComponentScans
和 @ComponentScan
注解的处理入口// 处理 ComponentScans ComponentScan 注解
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
通过注释我们了解到了这是对两个注解的处理,下面我们需要来编写这两个注解的使用案例,有了这个使用案例我们就可以更好的进行分析。
首先我们来编写一个需要被扫描到的 Spring Configuration Bean
@Configuration
public class ScanBeanA {
@Bean
public AnnPeople annPeople() {
AnnPeople annPeople = new AnnPeople();
annPeople.setName("scanBeanA.people");
return annPeople;
}
}
其次我们来编写一个测试用例,在这个测试用例中我们希望 ScanBeanA
被扫描到
@ComponentScan(basePackageClasses = ScanBeanA.class)
@Configuration
public class ComponentScanTest {
@Test
void scan(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentScanTest.class);
AnnPeople bean = context.getBean(AnnPeople.class);
assert bean.getName().equals("scanBeanA.people");
}
}
完成这些编辑之后我们来看 @ComponentScans
注解和 @ComponentScan
注解的关系
@ComponentScans
注解详细信息@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {
ComponentScan[] value();
}
@ComponentScan
注解详细信息@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
可以看到 @ComponentScans
注解中的属性是多个 @ComponentScans
注解,下面我们来看提取数据的这段代码
@ComponentScans
和 @ComponentScan
数据Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
通过前文对 @PropertySources
注解 和 @PropertySource
注解的分析,我们可以做出猜测:此时 AnnotationAttributes
中的数据是 ComponentScan
的属性表,下面我们来看其中的数据情况。
componentScans
数据信息下面我们来梳理单个ComponentScan
的处理过程
componentScanParser
进行解析,解析候得到 BeanDefinitionHolder
BeanDefinitionHolder
根据条件筛选后进行注册下面我们来看第一步的处理方法 org.springframework.context.annotation.ComponentScanAnnotationParser#parse
ComponentScanAnnotationParser#parse
方法详情public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
在这个方法中做了两件事,
ClassPathBeanDefinitionScanner
对象并从注解属性中提取数据赋值到对应字段中ClassPathBeanDefinitionScanner
进行解析对于 ClassPathBeanDefinitionScanner
的分析各位可以翻阅第十七章。
@Import
注解下面我们来看 @Import
注解的处理方法
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
// 判断是否存在需要处理的 ImportSelector 集合, 如果不需要则不处理直接返回
if (importCandidates.isEmpty()) {
return;
}
// 判断是否需要进行import循环检查
// 判断但钱配置类是否在 importStack 中
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
// 向 importStack 中加入当前正在处理的配置类
this.importStack.push(configClass);
try {
// 循环处理参数传递过来的 importSelector
for (SourceClass candidate : importCandidates) {
// 判断
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 将 class 转换成对象
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
// 如果类型是 DeferredImportSelector
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 获取需要导入的类
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// 将需要导入的类从字符串转换成 SourceClass
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归处理需要导入的类
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
// 处理类型是 ImportBeanDefinitionRegistrar 的情况
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
// 其他情况
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
// 从 importStack 删除配置类
this.importStack.pop();
}
}
}
下面我们来将这个方法处理流程整理出来
判断参数 importCandidates
是否存在数据,如果不存在数据就不进行后续处理。
判断参数 checkForCircularImports
是否为 true
,同时验证 importStack
中是否存在当前参数 configClass
。
如果 checkForCircularImports
为 true
同时验证通过就会抛出异常。
处理 importCandidates
集合中的每个 ImportSelector
对象,处理方式如下:
处理一:类型是 ImportSelector
将 SourceClass
转换成 ImportSelector
对象,得到 ImportSelector
后分两种情况处理
第一种:类型是 DeferredImportSelector
重复 handler
的操作
第二种:类型不是 DeferredImportSelector
调用 ImportSelector#selectImports
方法得到需要导入的类,将类名转换成 SourceClass
,最后解析每个 SourceClass
处理方法是本方法(processImports
) ,递归调用。
处理二:类型是 ImportBeanDefinitionRegistrar
将 SourceClass
转换成 ImportBeanDefinitionRegistrar
实例,将这个实例放入 importBeanDefinitionRegistrars
容器。
处理三:类型既不是 ImportSelector
也不是 ImportBeanDefinitionRegistrar
这部分流程在第二十七章中有所提及,第二十七章介绍了 ImportSelector
相关处理,本节为表层分析,关于 ImportSelector
处理细节需要详细了解需要查看第二十七章,比如 handler
操作
@ImportResource
注解接下来我们来看 @ImportResource
注解的处理
// Process any @ImportResource annotations
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
这段代码的处理是将注解元数据中关于 ImportResource
的属性,得到属性后将其转换成 importedResources
中的数据
importedResources
存储结构private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
new LinkedHashMap<>();
存储结构说明:
key:资源文件地址。
value:Bean Definition Reader 。
有关 @ImportResource
后续的解析各位可以参考第二十八章,在这里的处理仅仅只是提取数据。
@Bean
注解接下来我们来看 @Bean
注解的处理,先来看代码
Bean
注解处理代码// 处理 Bean 注解
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 处理 Bean Method
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
这里我们先不进入 retrieveBeanMethodMetadata
方法,我们先来看 processInterfaces
方法中的处理。
private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
// 获取接口
for (SourceClass ifc : sourceClass.getInterfaces()) {
// 接口上获取方法元数据
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
for (MethodMetadata methodMetadata : beanMethods) {
if (!methodMetadata.isAbstract()) {
// A default method or other concrete method on a Java 8+ interface...
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
}
processInterfaces(configClass, ifc);
}
}
在这段代码中我们看到了一个类似的操作代码,这段操作代码就是在该方法调用前进行的一个操作
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
现在我们可以归纳出这两个处理方法的大致流程
下面我们来看第一步的处理操作
retrieveBeanMethodMetadata
方法详情private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
// 提取元数据
AnnotationMetadata original = sourceClass.getMetadata();
// 提取带有 Bean 标签的数据
Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
// Try reading the class file via ASM for deterministic declaration order...
// Unfortunately, the JVM's standard reflection returns methods in arbitrary
// order, even between different runs of the same application on the same JVM.
try {
// 类的注解元数据
AnnotationMetadata asm =
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
// 提取带有 Bean 注解的方法元数据
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) {
// 选中的方法元数据
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
// 循环两个方法元数据进行 methodName 比较如果相同会被加入到选中集合中
for (MethodMetadata asmMethod : asmMethods) {
for (MethodMetadata beanMethod : beanMethods) {
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod);
break;
}
}
}
if (selectedMethods.size() == beanMethods.size()) {
// All reflection-detected methods found in ASM method set -> proceed
beanMethods = selectedMethods;
}
}
}
catch (IOException ex) {
logger.debug("Failed to read class file via ASM for determining @Bean method order", ex);
// No worries, let's continue with the reflection metadata we started with...
}
}
return beanMethods;
}
阅读了这个方法后我们来整理其中的处理细节。
步骤一:提取类当中的注解元数据。
步骤二:提取带有 Bean
注解的数据,这里提取的都是方法上的注解数据,变量名称为 beanMethods
。
步骤三:返回值处理
情况一:当步骤二得到的数据数量大于1并且类的元数据类型是 StandardAnnotationMetadata
进行下面的处理
提取注解元数据。
提取注解全数据中方法被 Bean 注解标记的方法元数据 ,变量名称为asmMethods
。
方法元数据筛选,通过筛选条件的加入到候选集合中。
筛选条件:对比 asmMethods
中的数据和 beanMethods
中的数据方法名称是否相同,相同就是符合条件的。
情况二:不符合情况一直接返回步骤二得到的数据。
下面我们来编写这段代码的测试用例
首先我们需要编写一个接口编写这个接口的目的是为了将 processInterfaces
中的代码覆盖到
public interface InterA {
@Bean
default AnnPeople annp() {
AnnPeople annPeople = new AnnPeople();
annPeople.setName("InterPeop");
return annPeople;
}
}
接下来我们将这个接口的实现类做出来
@Configuration
public class InterAImpl implements InterA {
@Bean
public AnnPeople annPeople() {
return new AnnPeople();
}
@Bean
public AnnPeople annPeople2() {
AnnPeople annPeople = new AnnPeople();
annPeople.setName("InterPeop2");
return annPeople;
}
}
在这个实现类中我们再定义一个 Bean 这里是为了覆盖外层的解析,写出两个 Bean 是为了测试 beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata
条件
最后我们编写测试方法
@Test
void testInterface(){
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(InterAImpl.class);
InterA bean = context.getBean(InterA.class);
}
下面我们来看调试阶段的一些数据信息
beanMethods
的数据,(非processInterfaces
处理)这这里我们可以看到三个,这里最后一个是从接口 default
中继承过来的,如果不需要请将代码修改成下面的样子
public interface InterA {
@Bean
AnnPeople annp();
}
但是这样编写就不会得到这个 Bean 它不符合 processInterfaces
中 !methodMetadata.isAbstract()
条件,使用 default
符合。为了做到一个完整的测试将代码进行修改变成如下内容
InterA
详细信息public interface InterA {
@Bean
default AnnPeople annpc() {
AnnPeople annPeople = new AnnPeople();
annPeople.setName("interface bean 1");
return annPeople;
}
@Bean
abstract AnnPeople annp();
}
InterAImpl
详细信息@Configuration
public class InterAImpl implements InterA {
@Bean
public AnnPeople annPeople() {
AnnPeople annPeople = new AnnPeople();
annPeople.setName("bean1");
return annPeople;
}
@Bean
public AnnPeople annPeople2() {
AnnPeople annPeople = new AnnPeople();
annPeople.setName("bean2");
return annPeople;
}
public AnnPeople annp() {
AnnPeople annPeople = new AnnPeople();
annPeople.setName("interface implements bean");
return annPeople;
}
}
最后我们来看解析后的 BeanMethod
最后我们来看看容器中的数据,通过 context.getBeansOfType(AnnPeople.class)
进行查询
最后我们来看父类配置的处理,先来看代码
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
在这段代码中主要目的是向 knownSuperclasses
插入数据,插入数据的前提是当前正在处理的类存在父类,同时父类名称是java开头同时knownSuperclasses
不存在
下面我们来编写这部分代码的测试用例
@Configuration
public class SupperConfiguration {
}
@Configuration
public class ExtendConfiguration extends SupperConfiguration {
}
@Configuration
@Import(ExtendConfiguration.class)
public class ThreeVarBeans {
}
@Test
void testThreeVar() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ThreeVarBeans.class);
}
knownSuperclasses
数据信息在本章笔者和各位围绕 Spring 配置类解析做了一个详细的分析,在这一章节中我们知道了一些常规注解在配置类中的解析处理形式。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。