Spring之什么是配置类

什么是配置类

一、将自定义配置类注册到容器中

首先从三行代码开始说起

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AppConfig.class);
applicationContext.refresh();

对应的AppConfig如下所示:

@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig {}

在org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions中来完成配置类的解析

方法说明如下所示:

从方法中可以看到构建并校验配置模型基于容器中的配置类

然后拿到此时容器中的BeanDefinition的Name,挨个判断哪些是配置类

1.1、根据BeanDefinition的attributes属性判断是否解析成过配置类

判断BeanDefinition的attributes属性是为了判断当前配置类是否已经解析过,根据日志的打印

logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);

表示已经将当前的BeanDefinition解析成了配置类,而BeanDefinition的attributes属性为Null的时候,表示还没有来进行解析过,对于没有解析过的BeanDefinition,需要来解析判断是否是一个配置类

1.2、判断BeanDefinition类型

因为在这个阶段的上一个阶段是for循环,所以这里是对每个BeanDefinition来做判断,判断当前BeanDefinition是否是配置类。

断点展示一下,当前容器中的所有BeanDefinition哪些是配置类

断点发现,只有自己注册进去的AppConfig类才是AnnotatedGenericBeanDefinition且类名和源文件中的类名一致,所以走第一个解析过程:

1.3、根据源文件信息判断是否存在Configuration注解

利用metadata来进行判断

如果类上标注有Configuration注解且注解中的属性proxyBeanMethods为true,那么在对应的BD属性attributes中会添加是FULL配置类;

但是如果类上没有Configuration注解且注解中的属性proxyBeanMethods为false,表示一个Lite配置类

或者根本就没有Configuration注解,而是存在着以下情况中的一种,也是一个Lite配置类

①如果类是一个接口的话,肯定不是配置类;

②如果类上存在Component、ComponentScan、Import、ImportResource中的一个注解,那么是一个配置类

③如果类上都没有,但是类中方法存在@Bean方法,那么说明是一个配置类

1.3.1、流程图说明BD如何判断是配置类小结

用一个流程图进行表示

1.3.2、例子

LITE配置类:

public class AService {
    @Bean
    public BService bService(){
        return new BService();
    }
}

LITE配置类:

@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig {}

LITE配置类:

@Configuration(proxyBeanMethods = false)
public class BService {}

LITE配置类:

@Import(AService.class)
public class BService {}

LITE配置类:

@Component
public class BService {}

LITE配置类:

@ImportResource(value = {"classpath:spring.xml"})
public class BService {}

FULL配置类:

@Configuration(proxyBeanMethods = true)
public class BService {}

二、解析配置类

2.1、配置类解析结果分类

既然已经判断出来了配置类,那么就要来进行解析。那么解析出来的结果是什么

两种结果:①解析出来BeanDefinition;②还是配置类

因为最终的目标是BeanDefinition,所以如果还是配置类的话,还需要继续进行解析,所以可能存在递归

2.2、配置类解析成BeanDefinition源码

解析出来的配置类对应的BD会被放置到configCandidates集合中保存起来,方便在下面来进行操作。

具体解析源码

2.2.1、构建解析器解析

首先构建一个配置类的解析器,然后对保存起来的配置类对应的BD来进行解析;然后将已经将BD解析成配置类的BD保存起来,方便后续判断;

2.2.2、遍历循环所有候选配置类对应的BD

遍历循环所有候选配置类对应的BD,调用配置类的解析器来进行解析

对应当前第一轮循环中的BD是AppConfig对应的BD肯定是AnnotatedBeanDefinition类型的,所以直接看这个解析方法

2.2.2.1、创建配置类盛放当前BD,然后解析

一定要注意这里的do...while循环

2.2.2.2、解析@Component

解析类上标注有@Component的类

检查当前类是否有嵌套内部类

找一个类中的内部类中是否能够满足是配置类的条件。代码如下所示:

@Component
public class AService {
     public class A{
        @Bean
        public BService bService(){
            return new BService();
        }
    }
}

此时的A也是一个配置类,再如下所示:

@Component
public class AService {
    @Component
     public class A{
         public class B{
             @Bean
             public BService bService(){
                 return new BService();
             }
         }
    }
}

B也是一个配置类。

但是一般人谁会这样子来写呢?所以一般类上有一个@Component注解即可

2.2.2.3、解析@PropertySources

@PropertySource注解用于指定资源文件读取的位置,它不仅能读取properties文件,也能读取xml文件,并且通过YAML解析器,配合自定义PropertySourceFactory实现解析YAML文件。

将解析出阿里的结果添加到环境变量中去

2.2.2.4、解析@ComponentScans和@ComponentScan

这里就是spring的扫描逻辑了,之前分析过,这里就先不来进行分析

2.2.2.5、解析@Import注解

解析导入的资源注解

1、首先获取得到所有的添加了@Import注解的配置类

这里只是获取得到@Import注解中的value的值,获取得到这里的字符串而已,并没有执行对应的方法

2、然后找到具体的解析方法

然后又将步骤进行拆解

①检查是否存在循环引入

代码如下所示:

@Import(value={B.class})
public class A{}

@Import(value={A.class})
public class B{}

如果存在这种情况,那么spring直接抛出异常信息。

但是这样子做没有任何效果!!!

①@Import使用注意事项

因为是解析配置类过程中,但是配置类当前就只有AppConfig,而没有A和B。

这就表明需要将@Import注解添加到配置类上来

如下所示:

@ComponentScan(basePackages = "com.guang.spring")
@Import(value = {A.class})
public class AppConfig {}

然后不修改上面的A、B的逻辑,可以看到抛出异常:

A circular @Import has been detected: Illegal attempt by @Configuration class 'B' to import class 'A' as 'A' is already present in the current import stack [B->A->AppConfig]
Offending resource: com.guang.spring.service.B

所以一般开发过程中,都是将@Import注解添加到配置类上来,防止失效情况产生。

②找到所有类上添加@Import进行过滤

一、如果候选配置类实现了ImportSelector接口,那么直接通过反射创建对应的对象。然后再次判断是否实现了DeferredImportSelector接口,如果没有实现DeferredImportSelector接口,那么直接调用ImportSelector导入的字符串数组,创建配置类信息,再次来进行解析

二、如果候选配置类实现了ImportBeanDefinitionRegistrar接口,那么直接通过反射创建对应的对象,然后会直接调用Aware方法

然后将对象和对象的源信息保存到ConfigurationClass对象中的importBeanDefinitionRegistrars属性中,方便下次来进行执行。

三、如果既没有实现ImportSelector接口,也没有实现ImportBeanDefinitionRegistrar接口,那么就再次进行查找

2.2.2.6、解析@ImportResource

解析导入的资源文件,这里可以导入配置文件,然后从配置文件中解析得到对应的BD。无非也就是解析XML配置文件而已。

2.2.2.7、解析@Bean方法

解析配置类上的@Bean方法

然后保存到配置类属性beanMethods中

2.2.2.8、处理接口中加了@Bean的默认方法

配置类实现了接口中的默认方法

具体源码如下所示:

也会将对应的默认方法添加到configClass中的beanMethod属性中来

对应的代码如下所示:

public interface UserService {

    @Bean
    default AService aService(){
        return new AService();
    }
}

然后在配置类中配置如下:

@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig implements UserService {}

所以这个处理太过于鸡肋,不宜处理。

2.2.2.9、处理父类中加了@Bean的默认方法

对应代码如下所示:

public class BService {
    @Bean
    public AService aService(){
        return new AService();
    }
}

对应的配置类修改如下所示:

@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig extends BService {}

通过测试可以看到,在容器中是可以拿到对应的bean的,也就说明了是可以拿到对应的BD的。

2.3、单个BD解析完成

单个BeanDefinition当前配置类解析完成,那么退出之后

循环遍历,直到当前配置类中没有配置类再来进行解析,然后保存到到configurationClasses保存起来。

因为对于还需要对保存到configurationClasses中属性中的集合来做进一步的处理。

如:

1、@Bean保存到configurationClasses中的beanMethods属性中;

2、@ImportResource保存到configurationClasses中的importedResources属性中;

3、实现了ImportBeanDefinitionRegistrar接口的BD保存到configurationClasses中的importBeanDefinitionRegistrars属性中;

需要在最终来进行解析

2.4、递归遍历所有的BD直到不再产生新的BD为止

2.4.1、一轮循环结束对ConfigurationClass做处理

每轮循环结束之后,对保存到configurationClasses中属性中的集合来做进一步的处理。

this.reader.loadBeanDefinitions(configClasses);

如:

1、@Bean保存到configurationClasses中的beanMethods属性中;

2、@ImportResource保存到configurationClasses中的importedResources属性中;

3、实现了ImportBeanDefinitionRegistrar接口的BD保存到configurationClasses中的importBeanDefinitionRegistrars属性中;

三、流程图和配置类扫描总结

参考地址:
1、Spring配置类解析流程
2、配置类解析具体流程

posted @ 2023-01-04 21:57  写的代码很烂  阅读(815)  评论(0编辑  收藏  举报