@Autowied和@Resource的区别——记一次注入失败
注入方式的区别
@Autowired:默认使用byType的方式注入spring bean
@Qualifier:默认使用byName的方式注入spring bean
@Resource:默认使用byName的方式注入bean,如果没有找到,再去byType。
@Resource看起来功能完全覆盖了Spring提供的注解,而且不局限于spring的容器,更易于扩展(虽然现在基本都是spring的容器),这看起来比spring提供的那两个货要强,所以为什么还要使用上边那两个货呢?
byName还不知道,但是byType可以给出个答案了。
答案是:@Autowired在byType的时候会连同泛型一起解析,而@Resource不支持解析泛型类型。
这个答案是在项目中开发遇到的,下面会举个简化了代码的例子。
排错过程
代码示例
代码结构图
通过这个图可以看出,每一个BO的实体类都会在build层和query层有一个唯一的处理类,所以可以使用byType的方式,在AbstractMetaBuild中组合IQuery,然后根据AbstractMetaBuild子类实现的泛型获取IQuery的bean。
说起来比较抽象,看下代码。
BO
@Data
public class PetBO {
private String name;
private Integer age;
public PetBO(String name) {
this.name = name;
}
}
@Data
public class UserBO {
private String name;
private Integer age;
public UserBO(String name) {
this.name = name;
}
}
Query层
public interface IQuery<T> {
T query();
}
@Component
public class PetQuery implements IQuery<PetBO> {
@Override
public PetBO query() {
return new PetBO("小白");
}
}
@Component
public class UserQuery implements IQuery<UserBO> {
@Override
public UserBO query() {
return new UserBO("明明");
}
}
Build层
第三行通过泛型的类型在父类里注入了IQuery,T由子类确认唯一的类型。所以在理想情况下,PetMetaBuild这个子类中会自动注入PetQuery这个类型的bean。
public abstract class AbstractMetaBuild<T> {
@Resource
private IQuery<T> iQuery;
public T build(){
T t = iQuery.query();
buildOther(t);
return null;
}
/**
* 构建其他信息
* @param t 模型BO
*/
public abstract void buildOther(T t);
}
@Component
public class PetMetaBuild extends AbstractMetaBuild<PetBO> {
@Override
public void buildOther(PetBO t) {
t.setAge(2);
}
}
@Component
public class UserMetaBuild extends AbstractMetaBuild<UserBO> {
@Override
public void buildOther(UserBO userBO) {
userBO.setAge(10);
}
}
错误结果
重点错误信息
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'petMetaBuild": Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.chongsan.web.inject.query.IQuery<?>’ available: expected single matching bean but found 2: petQuery,userQuery
翻译翻译,什么** 叫**的惊喜
惊喜就是,在创建子类:PetMetaBuild的bean的时候,尝试注入com.chongsan.web.inject.query.IQuery<?>
类型的bean,但是找到了俩,分别是petQuery
、userQuery
,懵逼了,不知道注哪个了。
但是,当把第二行的@Resource换成@Autowired
public abstract class AbstractMetaBuild<T> {
@Autowired
private IQuery<T> iQuery;
public T build(){
T t = iQuery.query();
buildOther(t);
return null;
}
/**
* 构建其他信息
* @param t 模型BO
*/
public abstract void buildOther(T t);
}
这个惊喜有点**的难翻译了。
根据异常栈debug下
Resource注入分析
点击上一级的栈帧
在debug页面查看getBeanPostProcessorCache().instantiationAware
,一共有四个子类,看名字应该分别是
- configuration注入
- AspectJ切面注入
- 通用注入
- autowired注入
由此可以推测,每次注入bean都会尝试用这四种解析器循环执行,直到有成功的,责任链模式。
栈帧的栈顶就是子类,所以知道是CommonAnnotationBeanPostProcessor
这个类抛出的异常。
- 可以用debug重新执行一下CommonAnnotationBeanPostProcessor这个类的栈帧,看看究竟是哪里的原因抛出了异常。
- 也可以直接看throw的异常栈信息,由于外层有捕获,所以和在控制台看到的异常信息不一样。
来到真正抛异常的上层DefaultListableBeanFactory
,可以发现注入bean的过程是
- 发现了两个IQuery的子类
- 判断注入其中的哪一个,return的是null
问题就出在了第二步,没有判断出来应该注入哪个bean
判断注入也分三步
- 基础数据类型(我想注入的是PetQuery,所以肯定不是)
- 根据优先级注入(没有设置bean的优先级)
- 先判断缓存中有没有这个类 || byName(前边的条件可以根据下方debug的数据判断出缓存没有PetQuery。byName也是失败的,变量名为iQuery,而自动解析出来的是petQuery)
最后返回了空
看了上边查找唯一bean的所有操作,发现都没有对泛型类型的解析,所以可以得出结论:
抛异常的原因是因为
@Resource
使用的是CommonAnnotationBeanPostProcessor
获取bean,其中的核心代码在DefaultListableBeanFactory
中,在有多个bean的情况下需要决定唯一的bean并返回,但是DefaultListableBeanFactory
并不支持泛型类型的解析,所以无法获取到唯一bean,故抛出异常。
Autowired注入分析
知道了@Resource
不能注入的原因,也看下@Autowired
是怎么处理这种情况的
先更换注解
再从责任链的源头打个条件注解,只看petMetaBuild
这个bean的注入
然后直接进入AutowiredAnnotationBeanPostProcessor
一步步查看注入的方式
此处省略n多步,关键代码为org.springframework.beans.factory.support.GenericTypeAwareAutowireCandidateResolver#checkGenericTypeMatch
最后的return判断的是IQuery<PetBO>
能否转换成PetQuery
。
执行发现是true
到此已经找到了PetQuery,由此得出结论
@Autowired
使用的是AutowiredAnnotationBeanPostProcessor
获取bean,其中的核心代码在GenericTypeAwareAutowireCandidateResolver
中,这个类可以根据泛型的信息判断需要注入的是具体是哪个bean,在byType上比@Resource
更强大