[英]Best practice for using date parameters in Spring controllers?
After written several backend APIs, I found that the following code duplicates in almost every method which needs to filter data by dates:写了几个后端API后,发现几乎所有需要按日期过滤数据的方法都重复了以下代码:
@GetMapping(value="/api/test")
@ResponseBody
public Result test(@RequestParam(value = "since", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate since,
@RequestParam(value = "until", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate until) {
// Date validation code I want to eliminate
since = ObjectUtils.defaultIfNull(since, DEFAULT_SINCE_DATE);
until = ObjectUtils.defaultIfNull(until, LocalDate.now().plusDays(1));
if(since.isAfter(until)) {
throw new SinceDateAfterUntilDateException();
}
// Do stuff
}
Obviously this is some kind of code smell.显然,这是某种代码异味。 But, since I do need to validate since
and until
before using them to query the service/DAO, I am not sure where should I extract these code to?但是,因为我确实需要验证since
和until
使用它们查询服务/ DAO,我不知道我应该在哪里提取这些代码来过吗?
Any advice?有什么建议吗?
public class MyCustomDateTypeConverter implements Converter<String, LocalDate> { @Override public LocalDate convert(String param) { //convert string to DateTime return dateTiemObjectCreatedfromParam; } }
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list>
<bean class="com.x.y.z.web.converters.MyCustomDateTypeConverter"/> </list> </property>
</bean>
<mvc:annotation-driven conversion-service="conversionService">
</mvc:annotation-driven>
public Result test(LocalDate since,LocalDate until) {
since = ObjectUtils.defaultIfNull(since, DEFAULT_SINCE_DATE);
until = ObjectUtils.defaultIfNull(until, LocalDate.now().plusDays(1));
if(since.isAfter(until)) {
throw new SinceDateAfterUntilDateException();
}
// Do stuff
}
As ol' gud-procedural approach suggests:正如 ol' gud-procedural 方法所建议的那样:
public Result test(@RequestParam(value = "since", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate since,
@RequestParam(value = "until", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate until) {
checkInputDates();
// Do stuff
}
private checkInputDates(LocalDate since, LocalDate until) {
since = ObjectUtils.defaultIfNull(since, DEFAULT_SINCE_DATE);
until = ObjectUtils.defaultIfNull(until, LocalDate.now().plusDays(1));
if(since.isAfter(until)) {
throw new SinceDateAfterUntilDateException();
}
}
//so on..
If you have request params received from object then you can do it by using Bean level Validation(JSR 303) and custom Date Deserializer by extending Jackson serializers.如果您从对象收到请求参数,那么您可以通过扩展 Jackson 序列化程序使用 Bean 级别验证(JSR 303)和自定义日期反序列化程序来完成。 This way you dont have check the params for null.这样你就不用检查参数是否为空。
public class yourBeanName{
public LocalDate since;
public LocalDate until;
@JsonDeserialize(using = CustomDateDeserializer.class)
public void setSince(LocalDate since) {
this.since = since;
}
// same for until
}
// Custom jackson Desealizer
@Component
public class CustomDateDeserializer extends StdDeserializer<Date> {
@Override
public Date deserialize(JsonParser jsonparser, DeserializationContext
context)
throws IOException, JsonProcessingException {
// Here check the date for null and assign default with your dateTimeFormat
}
}
I would suggest a model type holding the parameters since
and until
with a custom bean validation (using Lombok but you can also write getters and setters).我建议一个模型型保持的参数since
和until
有一个自定义的Bean验证(使用龙目岛,但你也可以写getter和setter)。 The defaults are now field initializers:默认值现在是字段初始值设定项:
@Ordered({"since", "until"})
@Data
public class DateRange {
@NotNull
@PastOrPresent
private LocalDate since = DEFAULT_SINCE_DATE;
@NotNull
private LocalDate until = LocalDate.now().plusDays(1);
}
@GetMapping(value="/api/test")
@ResponseBody
public Result test(@Valid DateRange dateFilter) {
// Do stuff
}
To make the validation work, you need a custom bean validation constraint:要使验证工作,您需要一个自定义 bean 验证约束:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = OrderedValidator.class)
public @interface Ordered {
/** The property names with comparable values in the expected order. **/
String[] value();
String message() default "{com.stackoverflow.validation.Ordered.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And the validator to check the constraint (sorry, litte generics hell to make it work for any kind of Comparable
values instead of LocaleDate
only):和检查约束的验证器(对不起,小泛型地狱使它适用于任何类型的Comparable
值,而不是仅适用于LocaleDate
):
public class OrderedValidator implements ConstraintValidator<Ordered, Object>
{
private String[] properties;
@Override
public void initialize(Ordered constraintAnnotation) {
if (constraintAnnotation.value().length < 2) {
throw new IllegalArgumentException("at least two properties needed to define an order");
}
properties = constraintAnnotation.value();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return isValid(value));
}
private <T extends Comparable<? super T>> boolean isValid(Object value)
{
List<T> values = getProperties(value);
return isSorted(values);
}
private <T extends Comparable<? super T>> List<T> getProperties(Object value)
{
BeanWrapperImpl bean = new BeanWrapperImpl(value);
return Stream.of(properties)
.map(bean::getPropertyDescriptor)
.map(pd -> this.<T>getProperty(pd, value))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
// See https://stackoverflow.com/a/3047160/12890
private <T extends Comparable<? super T>> boolean isSorted(Iterable<T> iterable) {
Iterator<T> iter = iterable.iterator();
if (!iter.hasNext()) {
return true;
}
T t = iter.next();
while (iter.hasNext()) {
T t2 = iter.next();
if (t.compareTo(t2) > 0) {
return false;
}
t = t2;
}
return true;
}
@SuppressWarnings("unchecked")
private <T extends Comparable<? super T>> T getProperty(PropertyDescriptor prop, Object bean) {
try {
return prop.getReadMethod() == null ? null : (T)prop.getReadMethod().invoke(bean);
} catch (ReflectiveOperationException noAccess) {
return null;
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.