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

Spring Boot 3自定义校验器注入空指针问题解决求助

问题描述

使用Spring Data Rest自动生成接口时,遇到错误:Injection from validator is null with message "Cannot invoke because is null",怀疑和Spring Data Rest有关,已尝试配置LocalValidatorFactoryBean但无效。使用Spring Boot 3,配置文件无特殊设置。

相关代码

ProgrammeNomUnique注解

@Constraint(validatedBy = NomUniqueValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ProgrammeNomUnique {

        String message() default "Un programme porte déjà le même nom";

        Class<?>[] groups() default {};

        Class<? extends Payload>[] payload() default {};
}

NomUniqueValidator校验器

@Component
@RequiredArgsConstructor
public class NomUniqueValidator implements ConstraintValidator<ProgrammeNomUnique, String> {

        @Autowired
        private  ProgrammeRepository programmeRepository;

    @Override
    public void initialize(ProgrammeNomUnique constraintAnnotation) {
    }

    @Override
    public boolean isValid(String nouveauNom, ConstraintValidatorContext constraintValidatorContext) {
        return !programmeRepository.existsByNom(nouveauNom);
    }
}

ProgrammeRepository

@CrossOrigin(origins = "http://localhost:4200")
@RepositoryRestResource(path = "programmes")
public interface ProgrammeRepository extends JpaRepository<Programme, Integer> {

    boolean existsByNom(String nom);
}

Programme实体类

@Getter
@Setter
@RequiredArgsConstructor
@Entity
@Table(name = "programme")
public class Programme {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_programme")
    private int id;

    @NotEmpty(message = "Le programme doit avoir un nom")
    @Column
    @ProgrammeNomUnique
    private String nom;

    @NotNull(message = "Le programme doit avoir un prix")
    @Column
    private double prix;

    @NotNull(message = "Le programme doit avoir une durée")
    @Column(name = "duree_semaine")
    private String dureeSemaine;

    @NotEmpty(message = "Le programme doit avoir une description")
    @Column
    private String description;

    @Column(name = "is_a_la_une")
    private int isALaUne;

    @Column(name = "services_associes")
    private String servicesAssocies;

    @Column(name = "id_image")
    private int idImage;

    @OneToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE,
            CascadeType.REFRESH, CascadeType.PERSIST}, fetch = FetchType.LAZY)
    @JoinColumn(name = "id_programme")
    private List<Section> sections;
}

尝试过的无效配置

@Configuration
public class ValidatorConfig {

    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
}

pom.xml依赖

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-rest</artifactId>
   </dependency>

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
   </dependency>

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
   </dependency>

   <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-hibernate5</artifactId>
   </dependency>

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

   <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <scope>runtime</scope>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
   </dependency>

</dependencies>
解决方案

1. 移除自定义注解上的@Component

自定义校验注解不需要被Spring注册为Bean,仅需通过@Constraint(validatedBy = ...)指定对应的校验器即可。修改后的注解代码:

@Constraint(validatedBy = NomUniqueValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
// 移除@Component注解
public @interface ProgrammeNomUnique {

    String message() default "Un programme porte déjà le même nom";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

2. 修复校验器的依赖注入逻辑

同时使用@RequiredArgsConstructor和@Autowired会导致注入冲突,推荐使用构造注入(Lombok的@RequiredArgsConstructor),并确保字段为final:

@Component
@RequiredArgsConstructor
public class NomUniqueValidator implements ConstraintValidator<ProgrammeNomUnique, String> {

    private final ProgrammeRepository programmeRepository; // 改为final,移除@Autowired

    @Override
    public void initialize(ProgrammeNomUnique constraintAnnotation) {
    }

    @Override
    public boolean isValid(String nouveauNom, ConstraintValidatorContext constraintValidatorContext) {
        // 增加空值判断,避免@NotEmpty校验通过前传入空值导致的空指针
        return nouveauNom == null || !programmeRepository.existsByNom(nouveauNom);
    }
}

3. 配置Spring Data Rest使用Spring Validator

Spring Data Rest默认不使用Spring容器的校验机制,需要显式配置让它在实体创建/保存前触发校验:

@Configuration
public class ValidatorConfig implements RepositoryRestConfigurer {

    private final Validator validator;

    // 构造注入Spring容器中的Validator
    public ValidatorConfig(Validator validator) {
        this.validator = validator;
    }

    @Override
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        // 为创建和保存操作添加校验器
        validatingListener.addValidator("beforeCreate", validator);
        validatingListener.addValidator("beforeSave", validator);
    }
}

4. 确认依赖兼容性

你的pom.xml已经包含了spring-boot-starter-validation,这在Spring Boot 3中是正确的(该依赖已整合Jakarta Validation相关实现),无需额外调整。


内容的提问来源于stack exchange,提问作者R Am

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 17:18:22