我有点不知所措,我有以下自定义 bean 验证器代码:
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy=UniqueCarBrandNameValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueCarBrandName {
String message() default "Email address is already registered";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
import icodeit.carfleetmanager.service.CarBrandService;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class UniqueCarBrandNameValidator implements ConstraintValidator<UniqueCarBrandName, String> {
@Autowired
private CarBrandService carBrandService;
@Override
public boolean isValid(String name, ConstraintValidatorContext constraintValidatorContext) {
return carBrandService.fetchOneByName(name).isEmpty();
}
}
简而言之,我需要一项服务来查看我的 CarBrandName 是否唯一,我尝试在自定义验证器注释中实现此功能。问题是自动连接在自定义验证器中不起作用,服务为空,给了我一个 NPE,但该服务在我的控制器中自动连接得很好。按照文档,我包含了以下配置:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
// Used for auto wiring beans in validation classes
@Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
这样 Spring 上下文也可以在自定义验证器中使用。但无济于事。我还在这个stackoverflow线程中尝试了建议的解决方案,但没有成功。
我正在使用以下 Maven 配置:
?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>icodeit</groupId>
<artifactId>service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service</name>
<description>backend service</description>
<properties>
<java.version>17</java.version>
<jackson-hibernate.version>2.15.0-rc2</jackson-hibernate.version>
<org.mapstruct.version>1.5.4.Final</org.mapstruct.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
<lombok.version>1.18.24</lombok.version>
</properties>
<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-jdbc</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.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
相关错误信息:
java.lang.NullPointerException: Cannot invoke "icodeit.carfleetmanager.service.CarBrandService.fetchOneByName(String)" because "this.carBrandService" is null
at icodeit.carfleetmanager.validation.carbrand.UniqueCarBrandNameValidator.isValid(UniqueCarBrandNameValidator.java:17) ~[classes/:na]
at icodeit.carfleetmanager.validation.carbrand.UniqueCarBrandNameValidator.isValid(UniqueCarBrandNameValidator.java:9) ~[classes/:na]
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:180) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:66) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:75) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:130) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:123) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:555) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:518) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:488) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:450) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:400) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:172) ~[hibernate-validator-8.0.0.Final.jar:8.0.0.Final]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:125) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:80) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.action.internal.EntityIdentityInsertAction.preInsert(EntityIdentityInsertAction.java:184) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:74) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:653) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:283) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:264) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:322) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:340) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:286) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:192) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:122) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:184) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:129) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:53) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:737) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:721) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
编辑: 我尝试过按照 ananta 的回答中的建议通过构造函数进行注入,但遗憾的是我收到以下错误:
java.lang.NoSuchMethodException: icodeit.carfleetmanager.validation.carbrand.UniqueCarBrandNameValidator.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3585) ~[na:na]
at java.base/java.lang.Class.getConstructor(Class.java:2271) ~[na:na]
您可以尝试使用@AllArgsConstructor来注入CarBrandService而不是@Autowire。使用这种方式,bean 容器必须在创建 UniqueCarBrandNameValidator 之前确保您的 CarBrandService 存在,否则,它将在启动时中断。
@Component
@AllArgsConstructor // Add this
public class UniqueCarBrandNameValidator implements ConstraintValidator<UniqueCarBrandName, String> {
// @Autowired => Remove this
private CarBrandService carBrandService;
...
}
我想我在尝试的过程中找到了一个可能的解决方案。如果您使用替代方法,通过使用 Jakarta 注入的验证器来验证服务中的实体,然后看到您的实体在将其提交到 JPA 存储库时未进行验证,那么它会起作用:
@AllArgsConstructor
@Component
public class CarBrandService {
private final CarBrandRepository carBrandRepository;
private final Validator validator;
...
private void validate(CarBrand carBrand) {
// This works!!
Set<ConstraintViolation<CarBrand>> constraintViolations = validator.validate(carBrand);
if (!customConstraintVoilations.isEmpty() || !constraintViolations.isEmpty()) {
throw new CustomConstraintViolationException(customConstraintVoilations, constraintViolations);
}
}
...
现在唯一的问题是,再次保存时将得到验证,在这种情况下它将再次抛出 NPE 错误。您可以通过对实体以外的另一个对象进行验证来解决此问题。
我也遇到了同样的问题并找到了解决方案这里。
您可以通过将以下属性添加到 application.properties 文件来解决该问题:
spring.jpa.properties.javax.persistence.validation.mode = none
它对我有用,我希望它也对你有用。