java中的依赖注入方式@RequiredArgsConstructor

之前我们项目中用到的依赖注入方式是通过@Autowired 或者@Resource来实现依赖注入。下面是通过lombok提供的@RequiredArgsConstructor实现依赖注入:

以下两种代码的区别

– 代码片段1

@Service
@Slf4j
@RequiredArgsConstructor
public class UniversalViewService {

    private final StringRedisTemplate redis;
}

– 代码片段2

@Service
@Slf4j
public class UniversalViewService {
    @Autowired
    private StringRedisTemplate redis;
}

核心区别对比

特性写法一 (构造函数注入 + Lombok)写法二 (字段注入)
工作原理Lombok 在编译时生成一个包含所有 final 字段的构造函数。Spring 在创建 Bean 时调用此构造函数来注入依赖。Spring 在创建完 Bean 实例后,通过反射机制直接为标记了 @Autowired 的字段赋值。
不可变性支持。字段被声明为 final,必须在对象创建时初始化,之后不可更改。更安全,线程友好。不支持。字段不是 final 的,理论上可以在任何时候被修改(尽管通常不会这么做)。
代码简洁性非常简洁。无需编写构造函数,只需一个注解和一个 final 关键字。简洁。只需一个注解。
明确定义明确。清晰地声明了“这些依赖是我正常工作所必需的”。隐含。依赖关系被隐藏了,无法一眼看出哪些是必需的。
测试友好度更容易测试。在单元测试中,你可以通过调用构造函数轻松传入模拟(Mock)依赖。更困难。你必须使用反射(如 ReflectionTestUtils)来注入模拟依赖,或者依赖 Spring 的测试上下文。
循环依赖容易暴露问题。构造函数注入能更早地发现循环依赖,迫使你重构设计。隐藏问题。Spring 通过三级缓存等机制可以解决部分循环依赖,可能掩盖了糟糕的设计。
Spring 官方推荐推荐。这是 Spring 团队自 4.x 版本以来推荐的方式。不推荐。Spring 官方文档已不推荐使用字段注入。
空指针安全更安全。对象被创建时,所有依赖都已就绪,不会出现依赖为 null 的情况。有风险。如果你在类的内部方法(如 @PostConstruct 方法)中使用了依赖,而 Spring 尚未注入,则可能引发 NPE。
依赖过多时构造函数参数会变多,这是一个信号,提醒你这个类可能职责过重(违反单一职责原则),需要重构。没有明显的信号。类可以轻易地注入很多依赖,而无法直观地意识到问题。

详细解释与最佳实践

为什么写法一更好?

  1. 不可变性 (Immutability)final 关键字保证了依赖项在对象生命周期内不会被意外改变或重新赋值,这使得对象的状态更加稳定和可预测,尤其是在并发环境中。

  2. 完全初始化的对象: 当 UniversalViewService 的实例被创建后,你可以百分之百确定 redis 字段已经被正确赋值。你不必担心在调用 @PostConstruct 方法或其他初始化方法时遇到 NullPointerException

  3. 更好的测试体验: 假设你要对 UniversalViewService 进行单元测试,而不启动整个 Spring 容器。

    • 写法一:你可以直接 new UniversalViewService(mockRedis),非常简单。
    • 写法二:你必须使用 ReflectionTestUtils.setField(service, "redis", mockRedis),这更繁琐且容易出错。
  4. 遵循最佳实践: 构造函数注入明确地定义了类所需的依赖合同。如果一个类有 5 个依赖,它的构造函数就有 5 个参数,这清楚地表明了类的复杂度,并促使开发者思考是否违反了单一职责原则。

字段注入的缺点:

字段注入的主要问题是它隐藏了依赖关系,并且破坏了对象的封装性。你无法通过公共接口(构造函数或 Setter 方法)来了解创建一个对象需要什么,而是必须深入到类的内部实现中去查看。

结论

你应该优先选择写法一(@RequiredArgsConstructor + final 字段)。

这是一种现代、简洁、安全且符合最佳实践的依赖注入方式。它结合了 Lombok 的便利性和 Spring 构造函数注入的所有优点。

写法二(字段注入)虽然看起来更简单,但因其诸多缺点,在现代 Spring 应用开发中已被视为一种反模式(anti-pattern),应避免在新项目中使用。

核心逻辑:分工明确

  1. Spring 的职责(实现注入): Spring 容器负责创建 Bean 实例、管理 Bean 的生命周期,并根据配置(如 @Autowired,构造函数等)将所需的依赖项(其他 Bean)“注入”到目标 Bean 中。这是依赖注入的真正实现者。
  2. Lombok 的职责(生成代码): Lombok 是一个代码生成库,它在编译时运行。@RequiredArgsConstructor 的职责是:为一个类的所有 final 字段或者标记了 @NonNull 的字段自动生成一个公共构造函数。它不关心这些依赖从哪里来,它只负责生成接收这些依赖的构造函数的代码。

当两者结合时,就实现了简洁且可靠的依赖注入。


具体工作流程(以构造函数注入为例)

假设我们有一个 MyService 类,它依赖于 MyRepository

没有 Lombok 的传统写法:

@Service
public class MyService {

    private final MyRepository myRepository;

    // 程序员需要手动编写这个构造函数
    @Autowired // Spring 4.3 以后,如果只有一个构造函数,可以省略 @Autowired
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }

    public void doSomething() {
        myRepository.findData();
    }
}

使用 @RequiredArgsConstructor 的写法:

@Service
@RequiredArgsConstructor // Lombok 注解
public class MyService {

    private final MyRepository myRepository;

    public void doSomething() {
        myRepository.findData();
    }
}

发生了什么?

  1. 代码编写阶段:你只在 MyService 中声明了一个 finalmyRepository 字段,并添加了 @RequiredArgsConstructor 注解。
  2. 编译阶段(Lombok 介入): Lombok 在 Java 编译器编译源码之前,会读取这个注解,并自动生成以下代码:
    public class MyService {
        private final MyRepository myRepository;
    
        // Lombok 自动生成的构造函数
        public MyService(MyRepository myRepository) {
            this.myRepository = myRepository;
        }
        // ... 其他方法
    }
    
  3. 运行时(Spring 介入): Spring 容器启动时,要创建 MyService 这个 Bean。它发现 MyService 有一个构造函数需要一个 MyRepository 类型的参数。
  4. 依赖查找与注入: Spring 会在自己的容器中查找类型为 MyRepository 的 Bean(比如 MyRepository 接口的一个实现类 MyRepositoryImpl,通常用 @Repository 注解标记),找到后,Spring 会调用 Lombok 生成的那个构造函数,并将 MyRepository 的实例作为参数传入,从而完成依赖注入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值