我正在使用Spring Boot,Spring Data REST,Spring HATEOAS,Hibernate,Spring Validation创建一个应用程序。
我创建了自己的验证,以便在this guide之后支持SpEL。
所以我有我的验证员:
componentWillMount() {
this.setState({
userPrize: 1
})
}
nextPrize() {
this.setState(userPrize: this.state.userPrize + 1);
}
previousPrize() {
this.setState(userPrize: this.state.userPrize - 1);
}
render() {
switch(this.state.userPrize) {
case 1:
return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
case 2:
return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
case 3:
return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
case 4:
return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
case 5:
return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
default:
throw new Error("Unknown prize state");
}
}
和我的注释:
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();
}
..并为REST存储库定义了验证器:
@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;
}
这是我的豆子:
@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);
}
}
使用Swagger我正在对这个json进行POST:
@Transactional
@RepositoryRestResource(excerptProjection = LicensePlateProjection.class)
@PreAuthorize("isAuthenticated()")
public interface LicensePlateRepository
extends PagingAndSortingRepository<LicensePlate, Long>, RevisionRepository<LicensePlate, Long, Integer> {
public LicensePlate findByLicensePlate(String licencePlate);
因为我有检查小巴的座位少于17个的验证规则,我应该看到验证错误,我看到了这个:
{"licensePlate":"asdfg","engineEuroLevel":"4","particulateFilter":true,"seats":18,"miniBus":true,"country":"http://localhost:8080/api/v1/countries/1"}
出现HTTP 400错误(此返回代码正确)。
我要指出我创建了Junit测试用例,并且看到了正确的消息:
{
"errors": []
}
所以我猜问题出现在REST / MVC部分。我调试了请求,并检查了班级@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());
}
;在构造函数中,我看到我的错误是正确的,我可以看到错误消息和正确的结构:
org.springframework.data.rest.core.RepositoryConstraintViolationException
我无法看到我犯错误的地方。使用其他(也)自定义验证器,我看到了正确的消息。我还有人让我朝着正确的方向解决问题吗?
答案 0 :(得分:0)
我认为Spring MVC不知道在哪里显示错误消息,因为类级别约束的约束违反并不表示任何特定属性。
HV的@ScriptAssert
提供了reportOn()属性,用于指定报告错误的属性。
对于自定义约束,您可以使用通过ConstraintValidatorContext
公开的API创建自定义约束违规和属性路径来执行相同操作。