1

我正在使用Spring Boot,Spring Data REST,Spring HATEOAS,Hibernate,Spring Validation创建应用程序。在Spring数据REST中带有验证错误的空邮件

我创建了我自己的验证,以支持以下this guide SpEL。

所以我我的验证:

public class SpELClassValidator implements ConstraintValidator<ValidateClassExpression, Object> { 
    private Logger log = LogManager.getLogger(); 

    private ValidateClassExpression annotation; 
    private ExpressionParser parser = new SpelExpressionParser(); 

    public void initialize(ValidateClassExpression constraintAnnotation) { 
     annotation = constraintAnnotation; 
     parser.parseExpression(constraintAnnotation.value()); 
    } 

    public boolean isValid(Object value, ConstraintValidatorContext context) { 
     try {   
      StandardEvaluationContext spelContext = new StandardEvaluationContext(value); 
      return (Boolean) parser.parseExpression(annotation.value()).getValue(spelContext); 
     } catch (Exception e) { 
      log.error("", e); 
      return false; 
     } 

    } 
} 

和我的注解:

@Target({ java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE }) 
@Retention(RetentionPolicy.RUNTIME) 
@Constraint(validatedBy = { SpELClassValidator.class }) 
@Documented 
@Repeatable(ValidateClassExpressions.class) 
public @interface ValidateClassExpression { 

    String message() default "{expression.validation.message}"; 

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

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

    String value(); 

} 

验证的配置:

@Bean 
public MessageSource messageSource() { 
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); 
    messageSource.setBasenames("classpath:/i18n/messages"); 
    // messageSource.setDefaultEncoding("UTF-8"); 
    // set to true only for debugging 
    messageSource.setUseCodeAsDefaultMessage(false); 
    messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1)); 
    messageSource.setFallbackToSystemLocale(false); 
    return messageSource; 
} 

/** 
* Enable Spring bean validation 
* https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation 
* 
* @return 
*/ 
@Bean 
public LocalValidatorFactoryBean validator() { 
    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); 
    factoryBean.setValidationMessageSource(messageSource()); 
    return factoryBean; 
} 

@Bean 
public MethodValidationPostProcessor methodValidationPostProcessor() { 
    MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor(); 
    methodValidationPostProcessor.setValidator(validator()); 
    return methodValidationPostProcessor; 
} 

..和对REST库定义的验证:

@Configuration 
public class RestConfig extends RepositoryRestConfigurerAdapter { 
    @Autowired 
    private Validator validator; 

    public static final DateTimeFormatter ISO_FIXED_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") 
      .withZone(ZoneId.of("Z")); 

    @Bean 
    public RootResourceProcessor rootResourceProcessor() { 
     return new RootResourceProcessor(); 
    } 

    @Override 
    public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) { 

    } 

    @Override 
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) { 
     validatingListener.addValidator("beforeCreate", validator); 
     validatingListener.addValidator("beforeSave", validator); 
     super.configureValidatingRepositoryEventListener(validatingListener); 
    } 
} 

这是我的豆:

@Entity 
// Validate the number of seats if the bus is a minibus 
@ValidateClassExpression(value = "#this.isMiniBus() == true ? #this.getSeats()<=17 : true", message = "{Expression.licenseplate.validminibus}") 
public class LicensePlate extends AbstractEntity { 
    private static final long serialVersionUID = -6871697166535810224L; 

    @NotEmpty 
    @ColumnTransformer(read = "UPPER(licensePlate)", write = "UPPER(?)") 
    @Column(nullable = false, unique = true) 
    private String licensePlate; 

    // The engine euro level (3,4,5,6) 
    @Range(min = 0, max = 6) 
    @NotNull 
    @Column(nullable = false, columnDefinition = "INTEGER default 0") 
    private int engineEuroLevel = 0; 

    @NotNull(message = "{NotNull.licenseplate.enginetype}") 
    @Enumerated(EnumType.STRING) 
    @Column(nullable = false) 
    private EngineType engineType = EngineType.DIESEL; 

    // If the bus has the particulate filter 
    @NotNull(message = "{NotNull.licenseplate.particulatefilter}") 
    @Column(nullable = false, columnDefinition = "BOOLEAN default false") 
    private boolean particulateFilter = false; 

    // Number of seats 
    @NotNull 
    @Range(min = 1, max = 99) 
    @Column(nullable = false, columnDefinition = "INTEGER default 50") 
    private int seats = 50; 

    // If the vehicle is a minibus 
    @NotNull 
    @Column(nullable = false, columnDefinition = "BOOLEAN default false") 
    private boolean miniBus = false; 

    @NotNull(message = "{NotNull.licenseplate.country}") 
    // The country of the vehicle 
    @ManyToOne(fetch = FetchType.LAZY, optional = false) 
    private Country country; 

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) 
    private List<Note> notes = new ArrayList<>(); 

    public LicensePlate() { 
    } 

    public String getLicensePlate() { 
     return licensePlate; 
    } 

    public void setLicensePlate(String licensePlate) { 
     this.licensePlate = licensePlate; 
    } 

    public int getEngineEuroLevel() { 
     return engineEuroLevel; 
    } 

    public void setEngineEuroLevel(int engineEuroLevel) { 
     this.engineEuroLevel = engineEuroLevel; 
    } 

    public int getSeats() { 
     return seats; 
    } 

    public void setSeats(int seats) { 
     this.seats = seats; 
    } 

    public boolean isMiniBus() { 
     return miniBus; 
    } 

    public void setMiniBus(boolean miniBus) { 
     this.miniBus = miniBus; 
    } 

    public EngineType getEngineType() { 
     return engineType; 
    } 

    public void setEngineType(EngineType engineType) { 
     this.engineType = engineType; 
    } 

    public boolean isParticulateFilter() { 
     return particulateFilter; 
    } 

    public void setParticulateFilter(boolean particulateFilter) { 
     this.particulateFilter = particulateFilter; 
    } 

    public Country getCountry() { 
     return country; 
    } 

    public void setCountry(Country country) { 
     this.country = country; 
    } 

    @Override 
    public String toString() { 
     return "LicensePlate [licensePlate=" + licensePlate + ", engineEuroLevel=" + engineEuroLevel + ", engineType=" 
       + engineType + ", particulateFilter=" + particulateFilter + ", seats=" + seats + ", miniBus=" + miniBus 
       + "]"; 
    } 

    public List<Note> getNotes() { 
     return notes; 
    } 

    public void setNotes(List<Note> notes) { 
     this.notes = notes; 
    } 

} 

配置上我也一直这个类:

@RestControllerAdvice 
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler { 

    @Override 
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, 
      HttpHeaders headers, HttpStatus status, WebRequest request) { 
     throw new RuntimeException(ex); 
    } 

    @Override 
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, 
      HttpHeaders headers, HttpStatus status, WebRequest request) { 
     throw new RuntimeException(ex); 
    } 

    @Override 
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, 
      HttpHeaders headers, HttpStatus status, WebRequest request) { 
     throw new RuntimeException(ex); 
    } 

} 

使用我的仓库:

@Transactional 
@RepositoryRestResource(excerptProjection = LicensePlateProjection.class) 
@PreAuthorize("isAuthenticated()") 
public interface LicensePlateRepository 
     extends PagingAndSortingRepository<LicensePlate, Long>, RevisionRepository<LicensePlate, Long, Integer> { 

    public LicensePlate findByLicensePlate(String licencePlate); 

使用扬鞭我做了此json的POST:

{"licensePlate":"asdfg","engineEuroLevel":"4","particulateFilter":true,"seats":18,"miniBus":true,"country":"http://localhost:8080/api/v1/countries/1"} 

因为我是检查小巴少于17席的验证规则,我应该看到验证错误,instad我看到:

{ 
    "errors": [] 
} 

与HTTP 400错误(该返回码是正确的)。

我已经指出,我创建JUnit测试案例,我看到正确的讯息:

@Test 
@WithMockUser(roles = "ADMIN") 
public void validateMinibusWithMoreThan17SeatsFails() { 
    assertEquals(1, countryRepository.count()); 

    LicensePlate plate = new LicensePlate(); 
    plate.setLicensePlate("AA123BB"); 
    plate.setEngineEuroLevel(3); 
    plate.setMiniBus(true); 
    plate.setSeats(18); 
    plate.setCountry(countryRepository.findFirstByOrderByIdAsc()); 

    Set<ConstraintViolation<LicensePlate>> constraintViolations = validator.validate(plate); 
    assertEquals(1, constraintViolations.size()); 
    ConstraintViolation<LicensePlate> constraintViolation = constraintViolations.iterator().next(); 
    assertEquals("I veicoli di tipo minibus possono avere al massimo 17 posti (16 passeggeri più il conducente).", 
      constraintViolation.getMessage()); 
} 

所以我想这个问题是关于REST/MVC的一部分。我调试了这个请求,并且检查了类org.springframework.data.rest.core.RepositoryConstraintViolationException;在构造函数中,我看到我的错误是正确的,我可以看到错误消息和正确的结构:

org.springframework.data.rest.core.ValidationErrors: 1 errors 
Error in object 'LicensePlate': codes [ValidateClassExpression.LicensePlate,ValidateClassExpression]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [LicensePlate.,]; arguments []; default message [],org.springframework.valid[email protected]520b6a25]; default message [I veicoli di tipo minibus possono avere al massimo 17 posti (16 passeggeri più il conducente).] 

我不能看到我做的错误。与其他(也)自定义验证器,我看到正确的消息。我也有人把我放在正确的方向来解决问题?

+0

嗨! )你[注册](https://docs.spring。io/spring-data/rest/docs/current/reference/html /#validation)你的验证器?你不想看到我的SDR验证[示例](https://github.com/Cepr0/sdr-validation-demo)吗? – Cepr0

+0

@ Cepr0是的。我更新了我的问题。我检查了你的例子,似乎我正在做同样的事情。 – drenda

+0

正如我所见,你只注册了标准的验证器('@Autowired private Validator validator;'),但不是自定义的:'validatingListener.addValidator(“beforeSave”,新的SpELClassValidator());'。或者我错了? – Cepr0

回答

0

我认为Spring MVC不知道在哪里显示错误消息,因为违反类级别约束的约束条件不会指示任何特定的属性。

HV的@ScriptAssert提供reportOn()属性用于指定属性以报告错误。

对于您的自定义约束,您可以使用通过ConstraintValidatorContext公开的API创建自定义约束违规和属性路径。

+0

你是对的。我猜也没有一个特定的属性春天mvc能够显示CoinstraintValidation错误。顺便说一下,我不得不将Hibernate Validator更新到版本> = 5.4,因为reportOn()仅适用于该版本。 – drenda