You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot自定义请求类注入Repository做请求校验失败问题

问题描述

尝试在UserCreateRequest类中校验email和name字段是否已存在于数据库,但通过@Autowired注入的userRepository始终为null。希望在请求类中完成该数据库校验逻辑,而非在控制器中处理。

相关代码

UserCreateRequest.java

@Getter
@Setter
@Component
public class UserCreateRequest {

    private UserRepository userRepository;

    public UserCreateRequest() {
    }

    @Autowired
    public UserCreateRequest(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @NotBlank(message = "Name is mandatory")
    @Size(min = 2, max = 20, message = "Name should be between 2 and 20 characters")
    private String name;


    @NotBlank(message = "Email is mandatory")
    @Email(message = "Email should be valid")
    private String email;

    @NotNull(message = "Date of birth is mandatory")
    @DateFormatRule(message = "Date of birth should be in yyyy-MM-dd format")
    private String dob;

    public User validated() {
        if (userRepository.findUserByEmail(email).isPresent()) {
            throw new RuntimeException("Email already exists");
        }
        if (userRepository.findUserByName(name).isPresent()) {
            throw new RuntimeException("Name already exists");
        }


        User user = new User();
        user.setName(name);
        user.setEmail(email);
        user.setDob(LocalDate.parse(dob));

        return user;
    }
}

UserController.java

@PostMapping
public ResponseEntity<User> addUser(@Valid @RequestBody UserCreateRequest userCreateRequest) {

    User validatedData = userCreateRequest.validated();

    User createdUser = this.userService.addUser(validatedData);

    return ResponseEntity.ok(createdUser);
}

报错信息

(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:1623)\n",
    "message": "Cannot invoke \"com.test.restapi.repository.UserRepository.findUserByEmail(String)\" because \"this.userRepository\" is null",
    "path": "/api/students"
原因分析

UserCreateRequest是通过@RequestBody接收的,Spring会使用Jackson将请求体JSON反序列化为该类的实例,这个实例不是Spring容器管理的Bean——即使你加了@Component注解,也不会从容器中获取实例,而是由Jackson直接创建。因此@Autowired的注入逻辑不会生效,userRepository自然为null。

解决方案

推荐使用自定义Spring验证注解来实现数据库唯一性校验,这符合Spring的验证规范,且能保持请求类的职责单一。

步骤1:创建自定义唯一性校验注解

1.1 邮箱唯一性注解@UniqueEmail

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
    String message() default "Email already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

1.2 用户名唯一性注解@UniqueName

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueNameValidator.class)
public @interface UniqueName {
    String message() default "Name already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

步骤2:实现注解对应的验证器

2.1 邮箱验证器UniqueEmailValidator

@Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {

    private final UserRepository userRepository;

    // 构造方法注入Spring容器管理的UserRepository
    public UniqueEmailValidator(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        // 空值由@NotBlank注解处理,此处无需重复校验
        if (email == null) {
            return true;
        }
        return !userRepository.findUserByEmail(email).isPresent();
    }
}

2.2 用户名验证器UniqueNameValidator

@Component
public class UniqueNameValidator implements ConstraintValidator<UniqueName, String> {

    private final UserRepository userRepository;

    public UniqueNameValidator(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public boolean isValid(String name, ConstraintValidatorContext context) {
        if (name == null) {
            return true;
        }
        return !userRepository.findUserByName(name).isPresent();
    }
}

步骤3:修改UserCreateRequest

移除@Component注解和userRepository相关代码,在对应字段上添加自定义注解:

@Getter
@Setter
public class UserCreateRequest {

    @NotBlank(message = "Name is mandatory")
    @Size(min = 2, max = 20, message = "Name should be between 2 and 20 characters")
    @UniqueName // 添加用户名唯一性校验
    private String name;

    @NotBlank(message = "Email is mandatory")
    @Email(message = "Email should be valid")
    @UniqueEmail // 添加邮箱唯一性校验
    private String email;

    @NotNull(message = "Date of birth is mandatory")
    @DateFormatRule(message = "Date of birth should be in yyyy-MM-dd format")
    private String dob;

    // 仅保留对象转换逻辑,移除校验逻辑
    public User toUser() {
        User user = new User();
        user.setName(name);
        user.setEmail(email);
        user.setDob(LocalDate.parse(dob));
        return user;
    }
}

步骤4:修改控制器

此时@Valid会自动触发所有校验(包括自定义的唯一性校验),无需手动调用validated()方法:

@PostMapping
public ResponseEntity<User> addUser(@Valid @RequestBody UserCreateRequest userCreateRequest) {
    User user = userCreateRequest.toUser();
    User createdUser = this.userService.addUser(user);
    return ResponseEntity.ok(createdUser);
}

其他可选方案(不推荐)

  • 将校验逻辑移至Service层:在userService.addUser()方法中添加数据库唯一性检查,这是分层架构的常规做法,但不符合你“在请求类中校验”的需求。
  • 使用@Configurable:结合AspectJ让Jackson创建的实例也能被Spring注入,但需要额外配置AspectJ,复杂度高,不适合常规项目。

内容的提问来源于stack exchange,提问作者b. tsutskiridze

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 07:56:30