文章目录
一、什么是@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
字段作为参数,并在构造函数内部完成这些字段的初始化。
大致的实现步骤如下:
-
收集所有的
final
字段。 -
生成一个包含这些
final
字段作为参数的构造函数。 -
在构造函数内,将参数的值赋给对应的字段。
以下是一个示意代码(需注意,这只是为了演示,实际实现要复杂得多):
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
必定会报错。我们新建两个类 LoginService
和 UserService
,并让它们相互引用以重现这个场景。
@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);
}
接着,新建两个实现类 SystemUserServiceImpl
和 CustomerUserServiceImpl
,代码如下:
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博客