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
方法注入, 这个效果是更好的。