问题描述

有同事在开发新功能测试时,报了个错,大致就是,在使用 @Autowired 注入时,某个类有两个bean,一个叫a,一个叫b,Spring不知道该使用哪个bean注入。

一般这种情况应该声明注入哪个bean,他没有声明,他不知道这个类有两个bean,他说他和别人写的一样,别的都不报错。

OK,那来分析下吧。

问题分析

前提:@Autowired是根据类型(byType)进行自动装配的。

在默认情况下只使用 @Autowired 注解进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。
使用@Autowired 注解时要注意以下情况:
  • 找不到一个匹配的 Bean 时,Spring 容器将抛出 BeanCreationException 异常,并指出必须至少拥有一个匹配的 Bean。
  • 如果当Spring上下文中存在不止一个候选Bean时,就会抛出BeanCreationException异常;
  • 如果Spring上下文中不存在候选Bean,也会抛出BeanCreationException异常。
所以在使用 @Autowired 注解时要满足以下条件:
  • 容器中有该类型的候选Bean
  • 容器中只含有一个该类型的候选Bean

问题探究

什么意思呢?我们用代码说话。
首先,我们建一个实体类 Student :
public class Student{
private String name;
//getter and setter...
}

然后我们在 Spring 容器中创建多个 Student 的实例,如下:

我们通过 XML 配置文件的方式在 Spring 的配置文件里实现一个类型多个 bean。

如下,创建了两个 Student 的 bean ,id 分别为 student 和 student02,对应的bean 的name 属性 分别为小红和小明。

<bean id="student" class="com.autowiredtest.entity.Student">
<property name="name" value="小红"/>
</bean>
<bean id="student02" class="com.autowiredtest.entity.Student">
<property name="name" value="小明"/>
</bean>

我们也可以通过使用 配置类+注解 的方式实现一个类型多个 bean

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class StudentConfiguration{
@Bean
Student student03(){
Student student = new Student();
student.setName("小华");
return student;
}
@Bean
Student student04(){
Student student = new Student();
student.setName("小玲");
return student;
}
}
当然这个 StudentConfiguration 配置类需要被注解扫描器扫描到,即要在扫包的配置里加上这个类所在的包。
(注:这里体现了用两种方式来实现一个类型多个bean)
 
使用 Student 的  bean
在controller声明 Student 类型的变量,并使用@Autowired 注入
@Controller
public class AutowiredTestController{
@Autowired
private Student student; @RequestMapping("/AutowiredTest")
@ResponseBody
public String loanSign(){
String docSignUrl = "ok";
System.out.println("--------------要打印了------------");
System.out.println(student.getName());
System.out.println("--------------打印结束------------");
return docSignUrl;
}
}

(这里就是用一个简单的spring mvc的小demo来验证这个问题。)

运行web项目,期间没有报错,访问http://localhost:8080/AutowiredTest,控制台输出:
 

是不是很奇怪?和上面说的不符合啊!这里 Student 类有4个实例,分别为 student、student02、student03和student04。

非但没有在调用时抛出 BeanCreationException 异常,反而正常运行,输出【小红】,说明注入的是 id 为 student 的 bean。

大胆的猜想:多个 bean 时,是根据 Student 的变量名自动匹配 bean id!

即 :当@Autowired private Student student; 时

我们的 Student 变量名是 student ,那么在 Spring 为其注入时,如果有多个 bean 的话就默认去容器中找 bean id 为 student 得那个 bean。

验证一下 

把 Student 的变量名改为 student02,@Autowired private Student student02

重启,并访问http://localhost:8080/AutowiredTest,控制台输出:

同样,改为 student03、student04控制台相应输出小华、小玲。

所以我们的大胆猜想是正确的!这里使用的 Spring 版本为 4.2.0.RELEASE。

本文永久链接:https://www.cnblogs.com/ibigboy/p/11236729.html

大胆的猜想,和上面说的不一致,那是不是版本兼容了这个问题?

验证一下

把版本改低一点。首先,把 Spring 版本改为2.5(@Autowired第一次出现在该版本),这时候 @ResponseBody @Configuration 以及 @Bean都不能用了(更高版本才能用)。

这时候启动项目,不报错,访问http://localhost:8080/AutowiredTest,报错:

控制台错误信息:

  严重: Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'autowiredTestController': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.autowiredtest.entity.Student com.autowiredtest.controller.AutowiredTestController.student02; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found : [student, student02]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$.run(AbstractAutowireCapableBeanFactory.java:)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:)
at org.springframework.beans.factory.support.AbstractBeanFactory$.getObject(AbstractBeanFactory.java:)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:)
at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:)
at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:)
at javax.servlet.GenericServlet.init(GenericServlet.java:)
at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:)
at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:)
at org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:)
at java.lang.Thread.run(Thread.java:)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.autowiredtest.entity.Student com.autowiredtest.controller.AutowiredTestController.student02; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found : [student, student02]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredElement.inject(AutowiredAnnotationBeanPostProcessor.java:)
at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:)
... more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found : [student, student02]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveDependency(AbstractAutowireCapableBeanFactory.java:)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredElement.inject(AutowiredAnnotationBeanPostProcessor.java:)
... more

控制台错误输出图:

关键的异常信息:

这时候报了预期的错了。

我们再增大版本号,去测试一下到底是哪个版本号开始兼容了这个问题。

这是版本的发布情况,采用二分法逼近。

先从当前用的版本 4.2.0.RELEASE 换到 3.2.18.RELEASE(Spring 3.x的最后一个版本)也是没问题的,3.0.0.RELEASE也没问题,2.5.5报错,2.5.6报错,2.5.6.SEC03报错。

因此可以断定,从 Spring 3.x 开始兼容了这个问题,更加人性化。

所以上述关于 @Autowired 的使用规则要发生变化了:

  1. 容器中有该类型的候选Bean
  2. 容器中可以含有多个该类型的候选Bean(Spring 3.x以后)
  3. Spring 3.x 之前Spring容器中只能有一个Bean,否则抛出 BeanCreationException 异常
  4. Spring 3.x以后,可以有多个Bean使用 @Autowired 时变量名一定要和该类型多个Bean 的其中一个相同
    (即上文中的@Autowired private Student student;,student 就是多个Bean中其中一个bean的id)
  5. 若违反第4条规则,会抛出 BeanCreationException 异常

假如我们想自定义变量名呢?

 
如果我创建变量的时候就是想自定义变量名,咋办?
如:@Autowired private Student stu; 这样的话对于一些 IDE 这样写完就直接回给出警告或直接报错,请看:
 

idea 直接告诉你,现在有两个 bean ,一个叫 student 另一个叫 student02,你现在写的变量名不是这俩种的任一个,你写的不对,给你报错!

而对于另外一些 IDE 则是没这么智能,如 eclipse。那就只有等到测试的时候才能发现了。

回到正题,怎么自定义变量名呢?

有2种方法:

1、@Autowired 和 @Qualifier
我们可以使用@Qualifier配合@Autowired来解决这些问题。
和找不到一个类型匹配 Bean 相反的一个错误是:如果 Spring 容器中拥有多个候选 Bean,Spring 容器在装配时也会抛出 BeanCreationException 异常(Spring 3.x之后)。
 
Spring 允许我们通过 @Qualifier 注释指定注入 Bean 的名称,这样就消除歧义了。
所以 @Autowired 和 @Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。
@Autowired
@Qualifier("student")
private Student stu;

这样 Spring 会找到 id 为 student 的 bean 进行装配。

 
另外,当不能确定 Spring 容器中一定拥有某个类的 Bean 时,可以在需要自动注入该类 Bean 的地方可以使用 @Autowired(required = false),这等于告诉 Spring:在找不到匹配 Bean 时也不报错。(required默认是true)
如:
@Autowired(required = false)
public Student stu

但是idea可不惯着你,依旧给你报错提示,虽然这时候可以忽略它继续启动,但访问时还是会报 BeanCreationException:

当然,一般情况下,使用 @Autowired 的地方都是需要注入 Bean 的,使用了自动注入而又允许不注入的情况一般仅会在开发环境或测试时碰到(如为了快速启动 Spring 容器,仅引入一些模块的 Spring 配置文件),所以 @Autowired(required = false) 会很少用到。 
 
 
2、使用@Resource(name = "xxx") 注解

这个和 @Autowired 和 @Qualifier 的作用一样,可以理解为是二者的合并吧。

总结

分析了这么多,我那个同事的问题是因为啥呢?
仔细看代码,那两个 bean 一个id是类名小写,一个id是类名小写并加了别的后缀。
而他使用时起的变量名不是类名小写的,直接就是类名(很难发现写的是大写),所以匹配不到任何一个bean。
这是工作不细心导致的,同时也引发了对@Autowired注入时兼容性的研究分析。
 

附:为什么@Autowired 和 @Qualifier注解不合成一个?

@Autowired 可以对成员变量、方法以及构造函数进行注释,而 @Qualifier 的标注对象是成员变量、方法入参、构造函数入参。
正是由于注释对象的不同,所以 Spring 不将 @Autowired 和 @Qualifier 统一成一个注释类。
 //对成员变量使用 @Qualifier 注释
public class Boss {
@Autowired
private Car car; @Autowired
@Qualifier("office")
private Office office;

}
//对构造函数变量使用 @Qualifier 注释
public class Boss {
private Car car;
private Office office; @Autowired
public Boss(Car car , @Qualifier("office")Office office){
this.car = car;
this.office = office ;
}
}
 

Spring框架使用@Autowired自动装配引发的讨论的更多相关文章

  1. Spring(二)-生命周期 + 自动装配(xml) +自动装配(注解)

    1.生命周期 **Spring容器的 bean **的生命周期: 1.1 默认生命周期 1.1.1 生命周期 调用构造方法,创建实例对象: set方法,给实例对象赋值: init 初始化方法 初始化对 ...

  2. [原创]java WEB学习笔记99:Spring学习---Spring Bean配置:自动装配,配置bean之间的关系(继承/依赖),bean的作用域(singleton,prototype,web环境作用域),使用外部属性文件

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  3. spring(4)——自动装配

    set注入和构造注入有时在做配置时比较麻烦.所以框架为了提高开发效率,提供自动装配功能,简化配置.spring框架式默认不支持自动装配的,要想使用自动装配需要修改spring配置文件中<bean ...

  4. Spring实战之处理自动装配的歧义性

    仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动装配属性.构造器参数或方法参数.为了阐述自动装配的歧义性,假设我们使用@A ...

  5. 模仿 spring IOC Annotation版自动装配

    spring 有两大核心 IOC和AOP.  IOC (inversion of control) 译为 控制反转,也可以称为 依赖注入 ; AOP(Aspect Oriented Programmi ...

  6. Spring入门(八):自动装配的歧义性

    1. 什么是自动装配的歧义性? 在Spring中,装配bean有以下3种方式: 自动装配 Java配置 xml配置 在这3种方式中,自动装配为我们带来了很大的便利,大大的降低了我们需要手动装配bean ...

  7. Spring注解开发系列Ⅴ --- 自动装配&Profile

    自动装配: spring利用依赖注入和DI完成对IOC容器中各个组件的依赖关系赋值.自动装配的优点有: 自动装配可以大大地减少属性和构造器参数的指派. 自动装配也可以在解析对象时更新配置. 自动装配的 ...

  8. Spring学习总结(2)-自动装配

    上面说过,IOC的注入有两个地方需要提供依赖关系,一是类的定义中,二是在spring的配置中需要去描述.自动装配则把第二个取消了,即我们仅仅需要在类中提供依赖,继而把对象交给容器管理即可完成注入.在实 ...

  9. Spring基于的注解自动装配和依赖注入(***)

    #自动装配的小Demo: package com.gyf.annotation; //DAO层 public interface UserDao { public void save(); } pac ...

随机推荐

  1. 浏览器中查看HTTP的头部文件

    本文以chrome浏览器为例,来讲解下在浏览器中,如何查看http的头部文件. 1.打开chrome浏览器,输入地址,如下图所示. 2.鼠标右击,在右键菜单中选择[检查],如下图所示. 3.选择“Ne ...

  2. MyBatis从入门到精通(二):MyBatis XML方式的基本用法之Select

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. 明确需求 书中提到的需求是一个基 ...

  3. ajax入门级

    AJAX AJAX:即异步的JavaScript 和 XML,是一种用于创建快速动态网页的技术: 传统的网页(不使用AJAX)如果需要更新内容,必需重载整个网页面: 使用AJAX则不与要加载更新整个网 ...

  4. C#使用 SSL Socket 建立 Client 与 Server 连接

    当 Client 与 Server 需要建立一个沟通的管道时可以使用 Socket 的方式建立一个信道,但是使用单纯的 Socket 联机信道可能会担心传输数据的过程中可能被截取修改因而不够安全,为了 ...

  5. MySQL错误:The user specified as a definer (XXX@XXX) does not exist (1449错误)最简解决方案

    背景:从同事处通过备份和还原备份方法导入mysql数据库,导入成功后启动项目,发现出现以下错误:The user specified as a definer (XXX@XXX) does not e ...

  6. iOS邓白氏编码申请流程及苹果账号组织名称变更

    邓氏编码(D-U-N-S®Number,是Data Universal Numbering System的缩写).它是一个独一无二的9位数字全球编码系统,相当于企业的身份识别码 (就像是个人的身份证) ...

  7. c++汉诺塔问题

    c++解决汉诺塔问题 题目描述 约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下.由小到大顺序串着由64个圆盘构成的塔.目的是将最左边杆上的盘全部移到中间的杆上 ...

  8. c++学习书籍推荐《Advanced C++》下载

    百度云及其他网盘下载地址:点我 作者简介 James Coplien先在威斯康星大学获得电气与计算机工程学士学位,后又在该大学获得计算机科学硕士学位.他在贝尔实验室的软件产品研发部门工作,在这个部门从 ...

  9. C语言学习书籍推荐《数据结构与算法分析:C语言描述(原书第2版)》下载

    维斯 (作者), 冯舜玺 (译者) <数据结构与算法分析:C语言描述(原书第2版)>内容简介:书中详细介绍了当前流行的论题和新的变化,讨论了算法设计技巧,并在研究算法的性能.效率以及对运行 ...

  10. CF1194D 1-2-K Game (博弈论)

    CF1194D 1-2-K Game 一道简单的博弈论题 首先让我们考虑没有k的情况: 1. (n mod 3 =0) 因为n可以被分解成若干个3相加 而每个3可以被分解为1+2或2+1 所以无论A出 ...