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
相关产品推荐
相关产品推荐

