简体   繁体   English

在 Spring 控制器中使用日期参数的最佳实践?

[英]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?但是,因为我确实需要验证sinceuntil使用它们查询服务/ DAO,我不知道我应该在哪里提取这些代码来过吗?

Any advice?有什么建议吗?

  1. Implement org.springframework.core.convert.converter.Converter;实现 org.springframework.core.convert.converter.Converter; interface.界面。
  2. Register with spring conversion service.注册弹簧转换服务。
  3. Use in you controller Sharing sample code below :在您的控制器中使用共享示例代码如下:
 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).我建议一个模型型保持的参数sinceuntil有一个自定义的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM