@Autowired 注入 Bean、@Inject 或 @Resource 区别

444 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天

我们先说一下使用 @Autowired、@Inject 和 @Resource 这三种注解注入 Bean 的方式:

  • @Autowired,是 Spring 的注解,优先按照类型注入。当无法确定具体注入类型的时候,可以通过 @Qualifier 注解指定 Bean 名称。

  • @Inject:是 JSR330 规范的实现,也是根据类型进行自动装配的,这一点和 @Autowired 类似。如果需要按名称进行装配,则需要配合使用 @Named。@Autowired 和 @Inject 的区别在于,前者可以使用 required=false 允许注入 null,后者允许注入一个 Provider 实现延迟注入。

  • @Resource:JSR250 规范的实现,如果不指定 name 优先根据名称进行匹配(然后才是类型),如果指定 name 则仅根据名称匹配。

当 Bean 产生循环依赖时,比如 BeanA 的构造方法依赖 BeanB 作为成员需要注入,BeanB 也依赖 BeanA,你觉得会出现什么问题呢?又有哪些解决方式呢?

答:Bean 产生循环依赖,主要包括两种情况:一种是注入属性或字段涉及循环依赖,另一种是构造方法注入涉及循环依赖。接下来,我分别和你讲一讲。

第一种,注入属性或字段涉及循环依赖,比如 TestA 和 TestB 相互依赖:


@Component
public class TestA {
    @Autowired
    @Getter
    private TestB testB;
}

@Component
public class TestB {
    @Autowired
    @Getter
    private TestA testA;
}

针对这个问题,Spring 内部通过三个 Map 的方式解决了这个问题,不会出错。基本原理是,因为循环依赖,所以实例的初始化无法一次到位,需要分步进行: 创建 A(仅仅实例化,不注入依赖);

创建 B(仅仅实例化,不注入依赖);

为 B 注入 A(此时 B 已健全);

为 A 注入 B(此时 A 也健全)。

第二种,构造方法注入涉及循环依赖。遇到这种情况的话,程序无法启动,比如 TestC 和 TestD 的相互依赖


@Component
public class TestC {
    @Getter
    private TestD testD;

    @Autowired
    public TestC(TestD testD) {
        this.testD = testD;
    }
}

@Component
public class TestD {
    @Getter
    private TestC testC;

    @Autowired
    public TestD(TestC testC) {
        this.testC = testC;
    }
}

这种循环依赖的主要解决方式,有 2 种:改为属性或字段注入;使用 @Lazy 延迟注入。比如如下代码:


@Component
public class TestC {
    @Getter
    private TestD testD;

    @Autowired
    public TestC(@Lazy TestD testD) {
        this.testD = testD;
    }
}

其实,这种 @Lazy 方式注入的就不是实际的类型了,而是代理类,获取的时候通过代理去拿值(实例化)。所以,它可以解决循环依赖无法实例化的问题。