我正在使用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).]
我不能看到我做的錯誤。與其他(也)自定義驗證器,我看到正確的消息。我也有人把我放在正確的方向來解決問題?
嗨! )你[註冊](https://docs.spring。io/spring-data/rest/docs/current/reference/html /#validation)你的驗證器?你不想看到我的SDR驗證[示例](https://github.com/Cepr0/sdr-validation-demo)嗎? – Cepr0
@ Cepr0是的。我更新了我的問題。我檢查了你的例子,似乎我正在做同樣的事情。 – drenda
正如我所見,你只註冊了標準的驗證器('@Autowired private Validator validator;'),但不是自定義的:'validatingListener.addValidator(「beforeSave」,新的SpELClassValidator());'。或者我錯了? – Cepr0