Spring @Resource、@Inject、@Autowired的区别及使用情况
引言:@Resource、@Inject、@Autowired 三种注解方式都支持基于 Setter 和字段的注入方式, 三者之间存在相似的地方但也存在一些差异。以下是我个人在学习这三种注入方式时了解到的内容。
-
@Resource注入@Resource是javax.annotation包下的注解, 随着Jakarta EE(Java平台企业版(Java Platform Enterprise Edition)) 一起打包。@Resourece注解支持基于字段的注入和基于Setter方法的注入。 这个注解按照以下顺序进行Bean的注入:以下示例都是基于字段的注入。
1) Match by Name : 按照Bean的名称匹配,Bean的名称可以在声明一个Bean的时候设置, 如果不设置Bean的name属性, 则会将Bean的方法名作为Bean的name属性。
配置类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
@Configuration
public class ResourceDependency {
@Bean(name = "defaultFileBean")
public File defaultFile(){
return new File("resource.txt");
}
}
按照指定的名称查找 Bean:
import com.example.demo.Configuration.ResourceDependency;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import javax.annotation.Resource;
import java.io.File;
@RunWith(SpringRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = {ResourceDependency.class}
)
public class ResourceInjectTest {
@Resource(name = "defaultFileBean")
private File fileBean;
@Test
public void testResourceByName(){
if (null == fileBean) {
throw new IllegalArgumentException("file Bean is null.");
}
System.out.println("file Bean load success.");
}
}
运行这个测试, 可以看到, 这个测试是通过的。
2) Match by Type : 按照 Bean 的类型来查找, 比如说 File 类的 Bean, 则会在 Spring 容器中查找对应类型的 Bean 注入。
按照类型来查找对应的 Bean,移除对应的 name 属性即可。
@Resource
private File fileBean;
再次运行这个测试程序, 可以看到, 这个测试运行依旧是可行的。
现在, 修改原来的配置类, 添加一个额外的、类型一致的 Bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
@Configuration
public class ResourceDependency {
@Bean(name = "defaultFileBean")
public File defaultFile(){
return new File("resource.txt");
}
@Bean(name = "anotherFileBean")
public File anotherFile() {
return new File("response.txt");
}
}
现在, 再次运行我们原来的测试, 可以发现, 它未通过并且抛出了一个异常, 可能类似如下图所示:

这是由于我们定义了两个类型一致的 Bean, Spring 在按照类型查找时发现了这两个 Bean, 无法选择将哪个 Bean 注入。
3) Match by Qualifier :当存在多个类型匹配 Bean 时, 通过添加对应的 @Qualifier 选择指定的 Bean.
在存在多个类型一致的 Bean 时, 添加额外的 Qualifier 注解可以解决这个问题, 在 Qualifier 注解中添加注入的 Bean 的名称可以解决这个问题。现在, 把原来的测试代码改为如下所示:
import com.example.demo.Configuration.ResourceDependency;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import javax.annotation.Resource;
import java.io.File;
@RunWith(SpringRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = {ResourceDependency.class}
)
public class ResourceInjectTest {
@Resource
@Qualifier("defaultFileBean")
private File fileBean1;
@Resource
@Qualifier("anotherFileBean")
private File fileBean2;
@Test
public void testResourceByName(){
if (null == fileBean1 || null == fileBean2) {
throw new IllegalArgumentException("file Bean is null.");
}
System.out.println("file Bean load success.");
}
}
运行这个测试代码, 可以看到对应的 Bean 已经加载成功。
另外, 也可以在使用 @Resource 注解时指定对应的 Bean 的名称, 由于使用 @Resource 注解时的优先级是 name -> Type -> Qualifier,因此会首先加载 @Resource 中指定的 Bean, 如下示例所示:
import com.example.demo.Configuration.ResourceDependency;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import javax.annotation.Resource;
import java.io.File;
@RunWith(SpringRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = {ResourceDependency.class}
)
public class ResourceInjectTest {
@Resource(name = "defaultFileBean")
@Qualifier("anotherFileBean")
private File fileBean1;
@Resource
@Qualifier("anotherFileBean")
private File fileBean2;
@Test
public void testResourceByName(){
if (null == fileBean1 || null == fileBean2) {
throw new IllegalArgumentException("file Bean is null.");
}
System.out.println("file 1: " + fileBean1.getName());
System.out.println("file 2: " + fileBean2.getName());
System.out.println("file Bean load success.");
}
}
运行这个测试, 测试是通过的, 查看对应的输出:

可以看到, fileBean1 注入的 Bean 是 @Resource 中指定的 defaultFileBean。
使用基于字段的注入会带来许多的问题, 如:无法对使用 final 关键字修饰的变量进行注入、掩盖了单一职责的设计思想、与Spring 的 IOC 过于耦合、无法对注入的属性进行安检等问题。因此 Spring 的工作组建议:“总是在 Bean 中使用基于构造函数的注入,始终对强制性依赖使用断言”。因此, 更多的时候, 使用基于构造函数的注入会更好。但是 @Resource 注解是不支持基于构造函数的注入的。它只支持基于字段的注入或者基于 Setter 方法的注入。当然, @Resource 也可以将一个组件类声明为一个运行时资源, 以便 Spring 在运行时查找该资源。
@Resource 基于 Setter 方法的注入,使用的方法与上文一致, 只是注解放置的位置从字段到了 Setter 方法。如下所示:
import com.example.demo.Configuration.ResourceDependency;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import javax.annotation.Resource;
import java.io.File;
@RunWith(SpringRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = {ResourceDependency.class}
)
public class ResourceInjectTest {
private File fileBean1;
private File fileBean2;
/*
基于 Bean 名称匹配的 Bean 注入
*/
@Resource(name = "defaultFileBean")
public void setFileBean1(File fileBean1) {
this.fileBean1 = fileBean1;
}
/*
基于 Bean 的类型的 Bean 注入
*/
@Resource
/*
使用 @Qualifier 注解声明 Bean 名称
*/
@Qualifier("anotherFileBean")
public void setFileBean2(File fileBean2) {
this.fileBean2 = fileBean2;
}
@Test
public void testResourceByName(){
if (null == fileBean1 || null == fileBean2) {
throw new IllegalArgumentException("file Bean is null.");
}
System.out.println("file Bean load success.");
}
}
运行这个测试类, 这个依旧是可以通过的。
@Resource 会按照这个优先级来加载对应的 Bean,指定 name 属性时按照 Bean 的名称来查找 Bean; 如果没有指定 name 属性, 则按照 Bean 的类型来查找 Bean;如果存在多个相同的类型的 Bean, 则添加 @Qualifier 注解设置 Bean 的 name属性来查找指定的 Bean。因此,最好的做法就是在使用 @Resource 注解时就设置要注入的 Bean 的 name 属性。
@Inject注入
@Inject属于 JSR-330 注解集合的一部分,要使用这个注解, 首先需要添加一下的依赖项,由于一般都是使用的 Maven, 所以添加依赖项如下所示:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
其他的软件项目构建工具添加这个依赖项可以去 Maven 仓库查找:https://mvnrepository.com/artifact/javax.inject/javax.inject/1
@Inject 注解按照以下优先级进行 Bean 的注入:
1) Match by Type :按照 Bean 的类型来查找注入。
首先创建一个组件类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.LinkedList;
@Configuration
public class InjectDependency {
@Bean(name = "defaultInjectBean")
public LinkedList<String> defaultList(){
return new LinkedList<>(Arrays.asList("1", "2", "3", "4"));
}
@Bean(name = "anotherInjectBean")
public ArrayList<String> anotherList() {
return new ArrayList<>(Arrays.asList("0", "1"));
}
}
简单的 Bean 注入测试:
import com.example.demo.Configuration.InjectDependency;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import javax.inject.Inject;
import java.util.LinkedList;
@RunWith(SpringRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = {InjectDependency.class}
)
public class InjectTest {
@Inject
private LinkedList<String> injectBean1;
@Test
public void testResourceByName(){
if (null == injectBean1) {
throw new IllegalArgumentException("inject Bean is null.");
}
System.out.println("inject Bean load success.");
}
}
运行这个测试, 可以看到, 这个测试是可以通过的。
2)Match by Qualifier :当存在多个类型相同的 Bean 时, 使用 @Qualifier 注解声明指定的 Bean的名称来查找 Bean 再注入。

首先, 当我们将LinkList 改为 List 后, 由于 ArrayList 和 LinkList 都是实现了 List 接口的类, 因此它们都是 List 类型的类, 因此在这里会出现存在多个 List 类型的 Bean, 导致无法注入。
@Inject 没有类似于 @Resource 那样的 name 属性来指定对应的 Bean 名称。使用 @Named (指定对应的 Bean 名)或 @Qualifier 注解指定 Bean 的名称是一个很好的解决方案。
import com.example.demo.Configuration.InjectDependency;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import javax.inject.Inject;
import java.util.List;
@RunWith(SpringRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = {InjectDependency.class}
)
public class InjectTest {
@Inject
@Qualifier("defaultInjectBean")
private List<String> injectBean1;
@Test
public void testResourceByName(){
if (null == injectBean1) {
throw new IllegalArgumentException("inject Bean is null.");
}
System.out.println("inject Bean load success.");
}
}
运行这个测试, 这个测试是可以通过的。
3)Match by Name :按照 Bean 的名称来查找 Bean 注入 , 添加 @Named 注解设置对应的 Bean 名即可按照名称来查找指定的 Bean。
import com.example.demo.Configuration.InjectDependency;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.List;
@RunWith(SpringRunner.class)
@ContextConfiguration(
loader = AnnotationConfigContextLoader.class,
classes = {InjectDependency.class}
)
public class InjectTest {
@Inject
@Named("anotherInjectBean")
private List<String> injectBean1;
@Test
public void testResourceByName(){
if (null == injectBean1) {
throw new IllegalArgumentException("inject Bean is null.");
}
System.out.println("List size: " + injectBean1.size());
System.out.println("inject Bean load success.");
}
}
运行这个测试类, 可以看到输出的 List size 为 2, 说明这是加载了第二个 Bean。
使用 Setter 方法的注入与字段注入一致, 只是将标注位置放到了 Setter 方法。
值得注意的是, 当一个属性被多个@Inject 标注时, 它将按照 构造函数——> 字段 ——> 方法 的顺序注入, 因此在使用是要注意这一点, 避免由于这个注入顺序而造成覆盖,也就是说, 如果你使用既使用了构造函数的注入方式, 也使用了字段的注入方式, 那么最后的属性值将会是基于字段注入的属性值。
此外,在父类和子类之间对统一字段都使用了 @Inject 注解, 那么将会首先注入父类的字段属性值, 然后在注入子类的字段属性值,因此最后的子类属性值依旧是子类注入的属性值。
@Autowired注入
@Autowired注解与@Inject注解是类似的, 但是它是 Spring 的一个注解, 因此当你在使用 Java EE 的时候需要考虑到这一点。@Autowired的注入是不需要访问权限的(即 private、public、protected)
@Autowired构造函数注入:
1)@Autowired的构造函数有一个参数required, 当它被设置为true时, 说明这个类的构造函数要使用 Spring Bean 来进行自动装配。因此,这个类只有一个构造函数能够使用@Autowired注解来进行构造函数的注入。
2)如果有多个构造函数使用了@Autowired注解(此时的require参数应当是false), 那么这些构造函数将会考虑将他们作为候选的@Autowired自动装配构造函数, 在构造时选择参数个数最多的, 装配的Bean存在 Spring 容器中的构造函数。如果没有任何构造函数可以选择, 那么将会选择默认的初始构造函数(不带参数的那个,如果它存在)最为注入的构造函数。
3)如果这个类声明了多个构造函数, 但是都没有使用@Autowired注解标记, 那么也会选择初始的构造函数(如果存在)作为注入的构造函数。
4)如果一个类只是声明了一个构造函数, 那么不管它是否使用了@Autowired注解标识, 都会将它作为注入的构造函数。
@Autowired构造字段注入:
1)字段注入在对应的Bean构造后,在配置方法 (Setter方法)前注入
@Autowired一般方法注入:
1)注入方法中的所有参数都会在 Spring 容器中查找匹配的Bean,这个方式不要求方法名的名称规范和参数个数。但是还是推荐使用Setter方法注入, 这个效果是更好的。
本文详细介绍了Spring框架中@Resource、@Inject和@Autowired三种注解的注入机制和使用场景。@Resource按名称或类型匹配Bean,@Inject主要按类型匹配并支持@Qualifier进行精确选择,@Autowired则同样按类型匹配,但更注重无侵入性,可在构造函数、字段和方法上使用。文章通过实例展示了各种注入方式的优先级和冲突解决策略,并强调了构造函数注入的重要性。
529

被折叠的 条评论
为什么被折叠?



