同事火急火燎的走了过来,说:敖丙快帮我看看这个错误,啥情况啊?
我一看报错:
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容器中找不到,发生这种错误时,常见的有两种情况:
1、@ComponentScan注解里的扫描路径没包含这个类
2、这个类的头上没加@Component注解
那么问题就来了:为什么@ComponentScan没扫描到或者没加@Component注解就注入不到Spring容器中?这个问题有点无厘头(没加@Component注解你还想注入到Spring容器中?)
我换种问法:为什么@ComponentScan扫描到了并且加了@Component注解就能注入到Spring容器中?
当然你可以直接回答:因为Spring规定这样做的
当然我也会接着反问你:Mybatis的Mapper就没用@Component注解,凭啥它就能注入到Spring容器中?
傻瓜,回答不了了吧?回答不了就赶紧往下看吧~
问题分析要回答:为什么@ComponentScan扫描到了并且加了@Component注解就能注入到Spring容器中?
我们首先需要对问题进行拆解:
1、@ComponentScan扫描是做了什么?
2、加了@Component注解又代表了什么?
回答了这两个问题我们再进行猜想:以上过程是否可以进行自定义?如何自定义?否则就没有办法说明Mapper是如何注入到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。