Spring @Resource、@Inject、@Autowired的区别及使用情况

Spring @Resource、@Inject、@Autowired的区别及使用情况

引言:@Resource@Inject@Autowired 三种注解方式都支持基于 Setter 和字段的注入方式, 三者之间存在相似的地方但也存在一些差异。以下是我个人在学习这三种注入方式时了解到的内容。

  1. @Resource 注入

    @Resourcejavax.annotation 包下的注解, 随着 Jakarta EE (Java平台企业版(Java Platform Enterprise Edition)) 一起打包。@Resourece 注解支持基于字段的注入和基于 Setter 方法的注入。 这个注解按照以下顺序进行 Bean 的注入:

    以下示例都是基于字段的注入。
     1) Match by Name : 按照 Bean 的名称匹配, Bean 的名称可以在声明一个 Bean 的时候设置, 如果不设置 Beanname 属性, 则会将 Bean 的方法名作为Beanname属性。
     配置类:

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 注解设置 Beanname属性来查找指定的 Bean。因此,最好的做法就是在使用 @Resource 注解时就设置要注入的 Beanname 属性。

  1. @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 后, 由于 ArrayListLinkList 都是实现了 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 注解, 那么将会首先注入父类的字段属性值, 然后在注入子类的字段属性值,因此最后的子类属性值依旧是子类注入的属性值。

  1. @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 方法注入, 这个效果是更好的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值