1. 写在前面
在 Spring 中,最容易出问题的地方之一就是 “依赖注入”。
今天在工作中,遇到了一个问题,
@Resource
private P4pAeAdCampaignDAO p4pAeAdCampaignDAO;
结果最终应用启动的时候,报错:
Bean named 'p4pAeAdCampaignDAO' is expected to be of type 'com.alibaba.global.ad.intl.dao.IntlP4pAeAdCampaignDAO' but was actually of type 'com.sun.proxy.$Proxy276'
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:321)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1268)
今天就结合自己实际使用中遇到的case,来再谈一下依赖注入的问题,主要区别一下 @Resource
& @Autowired
的不同。
2. Spring 中的注入
需要明白的是,Spring中的注入事实上是做这样的事情:
- 指明接口类的引用
- 运行期,Spring 会替换该引对象用为特定的实现类
因此,需要告诉Spring到底是哪个实现类,这里也是注入的核心点。
3. @Resource
& @Autowired
二者都可以用在 Bean
的注入,一般大多数情况(一个接口只有一个实现类)下没有什么区别。
3.1 @Resource 注解
- 是 JDK 本身带有的注解,在
Annotation
包下。 - 默认通过
Bean Name
进行装配(反射机制) - 有
name
&type
属性,可以指定 byName / byType 机制装配
3.2 @Autowired 注解
- 是 Spring 框架带的注解
- 默认通过 byType 的模式进行装配
- 可以通过
@Qualifier
&@Primary
进行指定
4. 一个栗子
我们这里看一个例子
// 一个接口
public Interface Human{
public void dress();
}
// 一个实现
public class Man Implements Human{
@Override
public void dress(){
sout("Man dress pants");
}
}
// 注入
public Controller{
@Resource
private Human human;
@Autowired
private Human human;
}
不出意外,可以看到两种注入方式都是可以的。
此时,我们增加一个实现类
public Woman Implements Human{
@Override
public void dress(){
sout("woan dress skirts");
}
}
// 注入
public Controller{
@Resource
private Human human;
@Autowired
private Human human;
}
由于 Human 有两个实现类,那么这时候就有问题了。两种注入方式都会拿到多个实现类,因此会报错注入Bean失败。
此时可以通过 增加一个 @Qualifier
注解,指明需要装配的实现类变量的名字(Spring是通过变量名去反向拿类名的)。
public Controller{
@Resource
@Qualifier("man")
private Human human;
@Autowired
@Qualifier("woman")
private Human human;
}
或者通过 @Primary
注解给到想要的实现类上,指定 byType 策略首先装配的类。
@Primary
public class Man Implements Human{
// 具体逻辑
}
事实上,一般都用
@Qualifier
的方法,这样更加灵活。
5. 装配逻辑
@Autowired
默认 byType 策略装配,逻辑如下
@Resource
默认 byName 策略装配,无法找到则通过 byType 逻辑装配;逻辑如下
回到文章最开始你遇到的问题,为什么遇到这个问题呢,
因为 @Resource
注解默认按照 byName 来找IOC容器中的 Bean,通过 p4pAeAdCampaignDAO
找到了 bean,但是这个 bean 确是 IntlP4pAeAdCampaignDAO
类,就导致与原有的类不匹配,因此跑异常。