探索 Lombok注解 之 @RequiredArgsConstructor :原理、实践与问题解决

一、什么是@RequiredArgsConstructor?

注解@RequiredArgsConstructor 是 Lombok 提供的一个注解,其主要作用在于简化 @Autowired 的书写过程。在编写 Controller 层或 Service 层代码时,常常需要注入众多的 mapper 接口或 service 接口。若每个接口都使用 @Autowired 进行标注,代码会显得繁琐。而 @RequiredArgsConstructor 注解能够替代 @Autowired 注解,但需注意,在类上添加 @RequiredArgsConstructor 时,需要注入的类必须使用 final 进行声明。

二、@RequiredArgsConstructor的使用方式

1、引入依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

2、代码示例

2.1、常规 @Autowired注入:

/**
 * @author fhey
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/getUser")
    public User getUser(){
        User user = userService.getUser("1");
        return user;
    }
}

2.2、常规 @Autowired注入:

/**
 * @author fhey
 */
@RestController
@RequestMapping("/user")
public class UserController {

    private UserService userService;
    
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/getUser")
    public User getUser(){
        User user = userService.getUser("1");
        return user;
    }
}

2.3、lombok中的**RequiredArgsConstructor注入:

/**
 * @author fhey
 */
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping("/getUser")
    public User getUser(){
        User user = userService.getUser("1");
        return user;
    }
}

可以看到,使用 @RequiredArgsConstructor 省去了 @Autowired@Resource 注解。

三、@RequiredArgsConstructor的实现原理

您是否好奇 @RequiredArgsConstructor 是如何运作的?让我们深入探究一番!

​ 在编译阶段,Lombok 借助“AST transformation”(抽象语法树转换)这一技术来修改 Java 源代码。这是一种在编译期间处理源代码的手段,它不会对运行时的性能造成影响,却能在生成的字节码中留下 Lombok 的运作痕迹。

​ 当在类上使用 @RequiredArgsConstructor 注解时,Lombok 会对类中的所有 final 字段进行观察,并在编译期间生成相应的构造函数。这个生成的构造函数会将这些 final 字段作为参数,并在构造函数内部完成这些字段的初始化。

​ 大致的实现步骤如下:

  1. 收集所有的 final 字段。

  2. 生成一个包含这些 final 字段作为参数的构造函数。

  3. 在构造函数内,将参数的值赋给对应的字段。

以下是一个示意代码(需注意,这只是为了演示,实际实现要复杂得多):

import java.lang.reflect.Field;

public class LombokMagic {
    public static <T> void createRequiredArgsConstructor(T instance) {
        for (Field field : instance.getClass().getDeclaredFields()) {
            if (java.lang.reflect.Modifier.isFinal(field.getModifiers())) {
                try {
                    field.setAccessible(true);
                    // 输出日志,告诉大家正在进行魔法操作
                    System.out.println("Abracadabra! Initializing field: " + field.getName());
                    // 初始化final字段,这里为了简化演示使用了空字符串和0
                    field.set(instance, field.getType() == String.class ? "" : 0);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        // 创建一个类实例
        ExampleClass example = new ExampleClass();
        // 使用Lombok的魔法方法,模拟@RequiredArgsConstructor的行为
        createRequiredArgsConstructor(example);

        // 魔法完成!现在example对象的final字段已被初始化。
    }
}

class ExampleClass {
    final String name;
    final int age;
    // 其他字段...
}

当然,实际的 Lombok 实现要复杂得多,涉及到 AST 处理、字节码生成等高深技术。但希望这个简化的模拟能让您对其有个大致的概念!

四、@RequiredArgsConstructor 在使用中遇到的几个问题

1、循环依赖问题

在实际运用中,有时会出现循环依赖的状况,即 A 引用了 B,而 B 又引用了 A。虽然 Spring 通过三级缓存的方式解决了大部分循环依赖问题,但由构造函数注入导致的循环依赖问题却未得到解决,而 @RequiredArgsConstructor 恰好是基于构造函数实现的注入。

因此,在两个类相互引用时使用 @RequiredArgsConstructor 必定会报错。我们新建两个类 LoginServiceUserService,并让它们相互引用以重现这个场景。

@Service("systemUserService")
@RequiredArgsConstructor
public class SystemUserServiceImpl implements UserService{

    private final LoginService loginService;

    @Override
    public User getUser(String userId) {
        User user = new User();
        user.setId(userId);
        user.setName("fhey");
        return user;
    }
}

@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements LoginService{

    private final UserService userService;

    @Override
    public String login(String userId, String password) {
        return null;
    }
}

项目启动时可以看到直接报错了:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   userController defined in file [D:\Project\test\fhey-test\api-test\target\classes\com\fhey\test\controller\UserController.class]
┌─────┐
|  systemUserService defined in file [D:\Project\test\fhey-test\api-test\target\classes\com\fhey\test\service\user\SystemUserServiceImpl.class]
↑     ↓
|  loginServiceImpl defined in file [D:\Project\test\fhey-test\api-test\target\classes\com\fhey\test\service\user\LoginServiceImpl.class]

那么该如何解决这个问题呢?

常规 @Autowired 注入解决循环依赖的方式通常是将 @Autowired@Lazy 组合使用,示例代码如下:

@Service("systemUserService")
public class SystemUserServiceImpl implements UserService{

    @Autowired
    @Lazy
    private final LoginService loginService;

    @Override
    public User getUser(String userId) {
        User user = new User();
        user.setId(userId);
        user.setName("fhey");
        return user;
    }
}

@Service
public class LoginServiceImpl implements LoginService{

    @Qualifier("customerUserService")
    private final UserService userService;

    @Override
    public String login(String userId, String password) {
        return null;
    }
}

@RequiredArgsConstructor 解决循环依赖同样使用 @Lazy ,为 @RequiredArgsConstructor 添加一个 onConstructor_ = {@Lazy} 参数,示例代码如下:

@Service("systemUserService")
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class SystemUserServiceImpl implements UserService{

    private final LoginService loginService;

    @Override
    public User getUser(String userId) {
        User user = new User();
        user.setId(userId);
        user.setName("fhey");
        return user;
    }
}

@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class LoginServiceImpl implements LoginService{

    private final UserService userService;

    @Override
    public String login(String userId, String password) {
        return null;
    }
}

2、@Qualifier失效问题

假设存在一个接口有两个实现类,使用 @Autowired 注入时,面对这种情况通常会使用 @Qualifier 来指定具体使用哪一个实现类。然而,使用 @RequiredArgsConstructor 时,会导致 @Qualifier 失效。

首先,创建一个接口 UserService ,代码如下:

import com.fhey.test.po.User;

public interface UserService {
    User getUser(String userId);
}

接着,新建两个实现类 SystemUserServiceImplCustomerUserServiceImpl ,代码如下:

import com.fhey.test.po.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service("systemUserService")
@RequiredArgsConstructor
public class SystemUserServiceImpl implements UserService{

    @Override
    public User getUser(String userId) {
        User user = new User();
        user.setId(userId);
        user.setName("fhey");
        return user;
    }
}

import com.fhey.test.po.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service("customerUserService")
@RequiredArgsConstructor
public class CustomerUserServiceImpl implements UserService{

    @Override
    public User getUser(String userId) {
        User user = new User();
        user.setId(userId);
        user.setName("fhey");
        return user;
    }
}

然后,新建一个控制器 UserController ,代码如下:

import com.fhey.test.po.User;
import com.fhey.test.service.user.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    @Qualifier("systemUserService")
    private final UserService userService;

    @GetMapping("/getUser")
    public User getUser(){
        User user = userService.getUser("1");
        return user;
    }
}

最后启动项目,此时项目启动报错提示:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   userController defined in file [D:\Project\test\fhey-test\api-test\target\classes\com\fhey\test\controller\UserController.class]
┌─────┐
|  systemUserService defined in file [D:\Project\test\fhey-test\api-test\target\classes\com\fhey\test\service\user\SystemUserServiceImpl.class]
↑     ↓
|  loginServiceImpl defined in file [D:\Project\test\fhey-test\api-test\target\classes\com\fhey\test\service\user\LoginServiceImpl.class]
└─────┘

这是典型的循环依赖报错,表明 @Qualifier 这个注解并未生效。实际上在 IDEA 中也有提示 "Lombok does not copy the annotation ‘org.springframework.beans.factory.annotation.Qualifier’ into the constructor ",这意味着 Lombok 没有将 @Qualifier 注解复制到构造器中,从而导致 @Qualifier 注解失效。
请添加图片描述

那么应该如何解决这个问题呢?

解决办法如下:
在项目根目录下创建一个 lombok.config 文件。
请添加图片描述

然后在里面添加一项配置:将 @Qualifier 添加进允许复制的注解列表中。

lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

随后删除 target 文件或者执行 mvn clean ,重新构建项目即可。

五、小结

本文详细介绍了 Lombok 中的 @RequiredArgsConstructor 注解。首先说明了其作用是简化 @Autowired 的书写,然后阐述了其使用方式,包括引入依赖和具体的代码示例。接着深入探讨了其实现原理,即通过在编译时利用“AST transformation”技术根据类中的 final 字段生成构造函数并进行初始化。同时,还指出了在使用中可能遇到的两个问题,一是循环依赖问题,解决方法是与 @Lazy 结合使用;二是 @Qualifier 失效问题,可通过在项目根目录创建 lombok.config 文件并添加相关配置来解决。

实现原理部分参考文章:一行注解,省却百行代码:深度解析@RequiredArgsConstructor的妙用-CSDN博客

  • 65
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Lombok的@RequiredArgsConstructor注解是用于生成一个包含被标注字段的构造方法的注解。该注解会在编译时自动生成带有被标注字段的构造方法。被@RequiredArgsConstructor注解标注的类中,所有使用final修饰的字段(以及被@NonNull注解修饰的字段)都会被包含在生成的构造方法中。而对于非final字段和非@NonNull注解修饰的字段,则不会被包含在生成的构造方法中。这样,使用@RequiredArgsConstructor注解可以方便地生成只包含特定字段的构造方法,减少了手动编写构造方法的工作量。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [vscode-lombok:Lombok注释Visual Studio Code的官方扩展](https://download.csdn.net/download/weixin_42133680/18328773)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [lombok 基础注解之 @RequiredArgsConstructor](https://blog.csdn.net/qq_39249094/article/details/121028234)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知北游z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值