前言
//Service无法注入跑出异常
Field xxxService in com.xx.xx.service.impl.XxXServiceImpl required a bean of type 'com.xx.xx.service.XxxService' that could not be found.
这个错误其实就是这个Bean在Spring容器中找不到,发生这种错误时,常见的有两种情况:
-
@ComponentScan注解里的扫描路径没包含这个类
-
使用的类的没加@Component注解
思考
为什么@ComponentScan扫描到了并且加了@Component注解就能注入到Spring容器中?
@ComponentScan扫描是做了什么?
过程大概是这样的:Spring通过扫描指定包下的类,解析这些类的信息,转化成为BeanDefinition,注册到beanDefinitionMap中。
那么这个过程的详情情况又是如何呢?
我们先来了解一下这个过程中涉及到的角色:
- 1、BeanDefinition:Bean定义,内含Class的相关信息
- 2、ConfigurationClassPostProcessor:配置类处理器,查找配置类,创建配置类解析器
- 3、ConfigurationClassParser:配置类解析器,解析配置类,创建@ComponentScan注解解析器
- 4、ComponentScanAnnotationParser:@ComponentScan注解解析器,解析@ComponentScan注解,创建Bean定义扫描器
- 5、ClassPathBeanDefinitionScanner:Bean定义扫描器,扫描指定包下的所有类,将符合的类转化为BeanDefinition
- 6、BeanDefinitionRegistry:BeanDefinition注册器,注册BeanDefinition
1.配置类处理器
配置类处理器主要做了3件事
- 1、查找配置类
- 2、创建配置类解析器并调用
- 3、加载配置类解析器所返回的@Import与@Bean注解的类
1.1查找配置类
你可能会有疑惑,配置类不是我们传入的吗?为什么还需要去查找配置类呢?
这是因为Spring整个调用链路十分复杂,不可能说把配置类往下层层传递,而是一开始时就将配置类注册到BeanDefinitonMap中了。
查找配置类大致有两个过程:
- 1、从BeanFactory中获取到所有的BeanDefiniton信息
- 2、判断BeanDefiniton是否为配置类
第一步很好解决,所有的BeanDefiniton是放在BeanFactory的BeanDefinitonMap中,直接从中获取就可以了。
而对于第二点,首先我们要知道什么是配置类?
在Spring中,有两种配置类:
- 1、full类型:标识了@Configuration注解的类
- 2、lite类型:标识了@Component @ComponentScan @Import @ImportResource @Bean注解的类(其中之一就行)
他们唯一的区别就在于:full类型的类会在后置处理步骤中进行动态代理
举个例子:
@Configuraiton
public class MyConfiguration{
@Bean
public Car car(){
return new Car(wheel());
}
@Bean
public Wheel wheel(){
return new Wheel();
}
}
问:Wheel对象在Spring启动时,被new了几次?
答案是一次,因为MyConfiguration对象实际上会被进行cglib动态代理,所以就算被this.的方式调用依旧会触发代理逻辑
注意:只有在这个情况下是这样,平常我们进行cglib代理时this调用依旧直接调用本类方法。
当查找出所有的配置类信息之后,紧接着就是创建配置类解析器,并将所有的配置类交由配置类解析器进行解析
1.2流程图
2.配置类解析器
配置类解析器的职责如下
- 1、判断该类是否应该跳过解析
- 2、解析内部类信息
- 3、解析@PropertySources注解信息
- 4、解析@ComponentScan注解信息
- 5、解析@Import注解信息
- 6、解析@Bean注解信息
2.1判断该类是否应该跳过解析
所谓判断类是否应该跳过解析,其实就是判断类是否标识了@Conditional注解并且是否满足该条件。如果标识了该注解并且不满足条件,那么则跳过解析步骤。
常见的@Profile,@ConditionalOnMissBean等都是由此控制。
2.2解析内部类信息
有时候我们的配置类里面有内部类,并且内部类也是个配置类,那么就需要用此方式进行解析。
2.3解析@ComponentScan注解信息
该步骤主要是利用@ComponentScan注解解析器进行解析@ComponentScan注解,从而获取到BeanDefinition列表,再判断这些BeanDefinition是否是个配置类,是则再次调用配置类解析器**进行递归解析。
3.@ComponentScan注解解析器
在该步骤中,Spring会将我们配置在@ComponentScan注解上的所有信息提取出来,存入到Bean定义扫描器中,再利用Bean定义扫描器得到符合条件的BeanDefiniton。
excludeFilter和includeFilter用于扫描时判断class是否符合要求。
默认的excludeFilter:扫描时排除掉自己这个class
默认的includeFilter: 扫描时判断该class是否标识@Component注解
4.Bean定义扫描器
BeanDefinitionScanner主要做了三件事:
- 1、扫描包路径下的类
- 2、给BeanDefiniton设值
- 3、使用BeanDefinition注册器将BeanDefiniton注册到容器中
4.1扫描包路径下的类
扫描包路径的步骤可以简单理解为遍历class文件的过程,遍历包下的每个class,判断该class是否满足条件标识了@Component注解,将满足条件的class转化为BeanDefiniton,此时BeanDefiniton只有metedata信息,还没有具体设值。
4.2给BeanDefiniton设值
如果我们在类上加了类似这些注解:@Lazy @Primary @DependsOn,那么就需要将这些注解转化为实际的属性设到BeanDefiniton中。
5.BeanDefinition注册器
BeanDefinitionRegistry的作用就是将BeanDefiniton放到BeanDefinitonMap中