【Java】@Autowired和@Resource

@Autowired和@Resource这两个注解最大的区别:

  • @Autowired 根据类型注入
  • @Resource 根据名称注入

以上是这两个注解最主要的装配方式,具体使用方式见下方。
以下只针对于一个接口有一个或多个实现类的情况进行讨论。没有实现类的情况不讨论,个人觉得自动注入允许为空的情况是十分不安全的,实际开发中也没有太大意义。

@Autowired

1.接口与实现类一一对应

接口:

1
2
3
4
5
6
7
8
9
package com.example.demo.service;

/**
* @author chenhy
* @date 2021/4/11
*/
public interface IUserService {
void say();
}

实现类01:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo.service.impl;

import com.example.demo.service.IUserService;
import org.springframework.stereotype.Service;

/**
* @author chenhy
* @date 2021/4/11
*/
@Service
public class UserServiceImpl01 implements IUserService {
@Override
public void say() {
System.out.println("I am UserServiceImpl01.................");
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.demo.test;

import com.example.demo.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @author chenhy
* @date 2021/4/11
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {

@Autowired
IUserService userService;

@Test
public void test() {
userService.say();
}
}

运行测试方法,控制台输出“I am UserServiceImpl01……………..”。
此时,Spring容器中只有一个IUserService 的实现类UserServiceImpl01 ,byType进行注入时,会自动找到实现类UserServiceImpl01进行注入。从控制台的打印结果我们也可以看出,调用的是类UserServiceImpl01的方法。验证了@Autowired是根据类型注入的观点。

2.接口与实现类的关系为一对多

新建实现类02:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo.service.impl;

import com.example.demo.service.IUserService;
import org.springframework.stereotype.Service;

/**
* @author chenhy
* @date 2021/4/11
*/
@Service
public class UserServiceImpl02 implements IUserService {
@Override
public void say() {
System.out.println("I am UserServiceImpl02.................");
}
}

运行测试方法,控制台报错如下:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.example.demo.service.IUserService’ available: expected single matching bean but found 2: userServiceImpl01,userServiceImpl02
意思就是有多个实现类,程序不知道到底该注入哪个实现类了。

测试类处理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.example.demo.test;

import com.example.demo.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @author chenhy
* @date 2021/4/11
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {

@Autowired
@Qualifier("userServiceImpl02")
IUserService userService;

@Test
public void test() {
userService.say();
}
}

运行测试方法,控制台输出“I am UserServiceImpl02……………..”。
我们新增了@Qualifier注解,该注解与@Autowired结合使用,可以在一个接口有多个实现类的情况下,指定注入的实现类的名称。此处,而我们注入了第二个实现类UserServiceImpl02。(注意,Qualifier注解中的名称首字母为小写)。

@Autowired+@Qualifier的使用,实现了bean自动注入时,先按照类型再按照名称进行注入的功能。

@Resource

1.根据名称注入(byName)

新建实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo.service.impl;

import com.example.demo.service.IUserService;
import org.springframework.stereotype.Service;

/**
* @author chenhy
* @date 2021/4/11
*/
@Service
public class UserService implements IUserService {
@Override
public void say() {
System.out.println("I am UserService.................");
}
}

修改测试类如下:

.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.example.demo.test;

import com.example.demo.service.IUserService;
import com.example.demo.service.impl.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

/**
* @author chenhy
* @date 2021/4/11
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {

// @Autowired
// @Qualifier("userServiceImpl02")
@Resource
IUserService userService;

@Test
public void test() {
userService.say();
}
}

其他文件不做改动。
现在的整体情况是,IUserService 有3个实现类,如果按类型注入的话肯定是失败的。

运行测试方法,控制台输出“I am UserService……………..”。
由此可见,@Resource在进行bean注入时,首先会按照名称(byName)进行装配。

2.根据类型注入

将UserService.java全部注释掉,再次运行测试方法进行测试。

运行测试程序,此时控制台报错如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘com.example.demo.test.UserTest’: Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.example.demo.service.IUserService’ available: expected single matching bean but found 2: userServiceImpl01,userServiceImpl02
与之前测试Autowired注解的情形相同,按照类型注入失败了。
测试类处理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.example.demo.test;

import com.example.demo.service.IUserService;
import com.example.demo.service.impl.UserServiceImpl01;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

/**
* @author chenhy
* @date 2021/4/11
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {

// @Autowired
// @Qualifier("userServiceImpl02")
@Resource(type = UserServiceImpl01.class)
IUserService userService;

@Test
public void test() {
userService.say();
}
}

运行测试程序,控制台输出“I am UserServiceImpl01……………..”。
通过在@Resource注解中指定注入的实现类来实现bean的注入。
此外,还可以在@Resource注解中指定类名称。

总结

对比项 @Autowired @Resource
注解来源 Spring注解 JDK注解(JSR-250标准注解,属于J2EE)
装配方式 默认byType,其次byName 默认byName,其次byType
属性 required name、type
作用范围 字段、setter方法、构造器 字段、setter方法

1.处理这2个注解的BeanPostProcessor不一样

CommonAnnotationBeanPostProcessor是处理@ReSource注解的;
AutoWiredAnnotationBeanPostProcessor是处理@AutoWired注解的。

2.注入方式不同

@Autowired只按照byType注入;
@Resource默认按byName自动注入,也提供按照byType注入。

3.属性不同

@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。
@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。

4.装配顺序不同

@Autowired:首先通过类型来查找bean,如果只找到一个,则直接注入,如果没有找到,则抛出异常;如果找到多个bean也会抛出异常。
解决方法一:可以在配置bean的时候加上@Primary注解,来提高优先级,这样就不会报错;
解决方法二:会默认使用字段名来匹配,如果没有匹配上,抛出异常。
如果需要直接通过bean的id来查找,可以配合@Qualifier来使用,没有找到抛出异常。
这里重点需要指出,使用@Autowired 是有优先级的,@Qualifier > 按类型找(如果找到多个继续使用之后的策略) > @Primary > 按名字找
@Autowired通过设置required=false,在没有找到bean的情况下,不会抛出异常。

@Resource装配顺序:
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。