mat*_*th5 4 java validation spring-boot
我正在为请求日期约束创建共享组件,开始日期在结束日期之前。我想接受当前的验证请求,并使其通用,因此我输入(任何类的 Begin 和 EndDate 类成员),它将起作用。如何才能做到这一点?我在请求类上方、下面的 ProductRequest 中使用注释。
注意:如何在注释中设置开始和结束日期参数;它们可能并不总是“开始/结束”字段成员,有时它们可能是另一个类中的“开始/结束”。
@DatesRequestConstraint
public class ProductRequest {
private Long productId;
private DateTime startDate;
private DateTime EndDate;
private List<String> productStatus;
}
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = ProductValidator.class)
@Documented
public @interface DatesRequestConstraint {
String message() default "Invalid dates request.";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
}
public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, ProductRequest> {
@Override
public void initialize(DatesRequestConstraint constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(ProductRequest productRequest, ConstraintValidatorContext constraintValidatorContext) {
if (productRequest.getStartDate() != null &&
productRequest.getEndDate() != null &&
productRequest.getStartDate().isAfter(productRequest.getEndDate())) {
return false;
}
else return true;
}
Run Code Online (Sandbox Code Playgroud)
您可以使用自定义注释来注释 startDate 和 endDate ,例如:
@StartDateField
private DateTime startDate;
@EndDateField
private DateTime endDate;
Run Code Online (Sandbox Code Playgroud)
然后在您的 中isValid(),您可以通过迭代所有类字段(在您的情况下为所有 ProductRequest 字段)并检查以下内容,通过注释访问 startDate 和 endDate 字段:
field.isAnnotationPresent(StartDateField.class)
field.isAnnotationPresent(EndDateField.class)
Run Code Online (Sandbox Code Playgroud)
完整的代码可能如下:
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.List;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ ANNOTATION_TYPE.TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = ProductValidator.class)
@Documented
@interface DatesRequestConstraint {
String message() default "Invalid dates request.";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
}
@DatesRequestConstraint
class ProductRequest {
private Long productId;
@StartDateField
private DateTime startDate;
@EndDateField
private DateTime EndDate;
private List<String> productStatus;
}
@Target({ ElementType.FIELD })
@Retention(RUNTIME)
@Documented
@interface StartDateField {
}
@Target({ ElementType.FIELD })
@Retention(RUNTIME)
@Documented
@interface EndDateField {
}
public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, Object> {
@Override
public void initialize(DatesRequestConstraint constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(Object requestObject, ConstraintValidatorContext constraintValidatorContext) {
DateTime startDate = getDateFieldByAnnotation(requestObject, StartDateField.class);
DateTime endDate = getDateFieldByAnnotation(requestObject, EndDateField.class);
if (startDate != null &&
endDate != null &&
startDate.isAfter(endDate)) {
return false;
} else return true;
}
private DateTime getDateFieldByAnnotation(Object requestObject, Class<? extends Annotation> annotationClass) {
return Arrays.stream(requestObject.getClass().getDeclaredFields()).filter(field -> field.isAnnotationPresent(annotationClass)).map(field -> {
try {
return field.get(requestObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}).map(DateTime.class::cast).findAny().orElse(null);
}
}
Run Code Online (Sandbox Code Playgroud)
你可以:
ConstraintValidator<DatesMatch, Object>以便您可以将@DatesMatch注释应用于任何类型;String字段添加到@DatesMatch注释中,您可以在其中指定要验证的字段的名称;这里有一个针对多个自定义字段的类级验证的类似示例:Baeldung:Spring MVC 自定义验证(向下滚动到“9.自定义类级验证”)。
根据您的示例进行定制,类似这样的内容应该有效:
@Constraint(validatedBy = DatesMatchValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DatesMatch {
String message() default "The dates don't match.";
String startField();
String endField();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface List {
DatesMatch[] value();
}
}
// Accept a list of items so that you can validate more than one pair of dates on the same object if needed
@DatesMatch.List({
@DatesMatch(
startField = "startDate",
endField = "endDate",
message = "The end date must be after the start date."
)
})
public class ProductRequest {
private Long productId;
private Instant startDate;
private Instant endDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
public class DatesMatchValidator implements ConstraintValidator<DatesMatch, Object> {
private String startField;
private String endField;
public void initialize(DatesMatch constraintAnnotation) {
this.startField = constraintAnnotation.startField();
this.endField = constraintAnnotation.endField();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
Instant startFieldValue = (Instant) new BeanWrapperImpl(value)
.getPropertyValue(startField);
Instant endFieldValue = (Instant) new BeanWrapperImpl(value)
.getPropertyValue(endField);
if (startFieldValue == null || endFieldValue == null) {
return true;
}
return endFieldValue.isAfter(startFieldValue);
}
}
Run Code Online (Sandbox Code Playgroud)
更新:(回应评论):
这个答案很好,允许多对日期,但是类型字符串不安全,人们可以在产品字段中输入任何字段
实施ConstraintValidator<DatesMatch, Object>是一种简单的包罗万象的解决方案,您可以将其应用于任何类别。
ConstraintValidator但是您绝对可以通过为要验证的每种类型(即ConstraintValidator<DatesMatch, ProductRequest>,, ...)实现一个单独的类型,以更类型安全的方式来完成此操作ConstraintValidator<DatesMatch, AnotherRequest>,然后在属性中指定所有类型@Constraint(validatedBy={...}):
@Constraint(validatedBy = {ProductRequestDatesMatchValidator.class, AnotherRequestDatesMatchValidator.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DatesMatch {
String message() default "Invalid dates request.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@DatesMatch(message = "Start and end dates do not match!")
public class ProductRequest {
private Long productId;
private Instant startDate;
private Instant endDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
@DatesMatch(message = "Begin and finish dates do not match!")
public class AnotherRequest {
private Long productId;
private Instant beginDate;
private Instant finishDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
public class ProductRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, ProductRequest> {
@Override
public boolean isValid(ProductRequest value, ConstraintValidatorContext context) {
// No need to cast here
Instant startDate = value.getStartDate();
Instant endDate = value.getEndDate();
// You could reuse this logic between each implementation by putting it in a parent class or a utility method
if (startDate == null || endDate == null) {
return true;
}
return startDate.isBefore(endDate);
}
}
public class AnotherRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, AnotherRequest> {
@Override
public boolean isValid(AnotherRequest value, ConstraintValidatorContext context) {
Instant beginDate = value.getBeginDate();
Instant finishDate = value.getFinishDate();
if (beginDate == null || finishDate == null) {
return true;
}
return beginDate.isBefore(finishDate);
}
}
Run Code Online (Sandbox Code Playgroud)
但请注意,这仍然不是编译时类型安全的,因为您可以将注释放在@DatesMatch尚未编写实现的类上,并且验证只会在运行时失败。
(您可以使用注释处理来实现编译时类型安全,但这又是另一个话题了。)
| 归档时间: |
|
| 查看次数: |
2252 次 |
| 最近记录: |