Spring/Spring学习 自动装配@Autowired 和 @Resource

Posted 龙龙爱阿喵!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring/Spring学习 自动装配@Autowired 和 @Resource相关的知识,希望对你有一定的参考价值。

@Autowired

以下内容转自 Spring框架使用@Autowired自动装配引发的讨论

问题分析

前提:@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...
}

构造多个Bean

然后我们在 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 配置类需要被注解扫描器扫描到,即要在扫包的配置里加上这个类所在的包。 所以现在Student 类有4个 Bean,student、student02、student03和student04。

(注:这里体现了用两种方式来实现一个类型多个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;
    }
}

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

 

 

是不是很奇怪?和上面说的不符合啊!这里 Student 类有4个实例,为啥没报错。

 

非但没有在调用时抛出 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。

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

验证一下

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

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

控制台错误信息:

1 严重: Context initialization failed
 2 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 2: [student, student02]
 3     at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:231)
 4     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:978)
 5     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
 6     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:485)
 7     at java.security.AccessController.doPrivileged(Native Method)
 8     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:455)
 9     at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251)
10     at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:169)
11     at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248)
12     at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:170)
13     at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:413)
14     at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:735)
15     at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:369)
16     at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:332)
17     at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:266)
18     at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:236)
19     at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:126)
20     at javax.servlet.GenericServlet.init(GenericServlet.java:158)
21     at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1279)
22     at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1192)
23     at org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:864)
24     at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:134)
25     at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
26     at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
27     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
28     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
29     at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
30     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
31     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
32     at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
33     at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
34     at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2441)
35     at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2430)
36     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
37     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
38     at java.lang.Thread.run(Thread.java:745)
39 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 2: [student, student02]
40     at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredElement.inject(AutowiredAnnotationBeanPostProcessor.java:375)
41     at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:61)
42     at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:228)
43     ... 35 more
44 Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found 2: [student, student02]
45     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveDependency(AbstractAutowireCapableBeanFactory.java:425)
46     at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredElement.inject(AutowiredAnnotationBeanPostProcessor.java:361)
47     ... 37 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报错(2.x的最后一个版本)。

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

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

  • 容器中有该类型的候选Bean

  • 容器中可以含有多个该类型的候选Bean(Spring 3.x以后)

  • Spring 3.x以后,单独使用 @Autowired 时变量名一定要和该类型多个 Bean 的其中一个相同(即上文中的@Autowired private Student student;,student 就是多个Bean中其中一个Bean的id)

  • 若违反第三条规则,会抛出 BeanCreationException 异常

  • Spring 3.x 之前只能有一个 bean,否则抛出 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 的作用一样,可以理解为是二者的合并吧。 

 

附:为什么@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 ;     
     }     
}

 @Resource

 

以上是关于Spring/Spring学习 自动装配@Autowired 和 @Resource的主要内容,如果未能解决你的问题,请参考以下文章

从头认识Spring-2.2 默认自动装配

从头认识Spring-2.1 自动装配-byName

从头认识Spring-2.1 自动装配-constructor

Spring--Spring 注入

Spring自动装配Bean详解

从头认识Spring-2.3 注解装配-@autowired-通过构造器方法注入