繁体   English   中英

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

[英]Best practice for using date parameters in Spring controllers?

写了几个后端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
}

显然,这是某种代码异味。 但是,因为我确实需要验证sinceuntil使用它们查询服务/ DAO,我不知道我应该在哪里提取这些代码来过吗?

有什么建议吗?

  1. 实现 org.springframework.core.convert.converter.Converter; 界面。
  2. 注册弹簧转换服务。
  3. 在您的控制器中使用共享示例代码如下:
 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
}

正如 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..

如果您从对象收到请求参数,那么您可以通过扩展 Jackson 序列化程序使用 Bean 级别验证(JSR 303)和自定义日期反序列化程序来完成。 这样你就不用检查参数是否为空。

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
    }

 }

我建议一个模型型保持的参数sinceuntil有一个自定义的Bean验证(使用龙目岛,但你也可以写getter和setter)。 默认值现在是字段初始值设定项:

@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
}

要使验证工作,您需要一个自定义 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 {};
}

和检查约束的验证器(对不起,小泛型地狱使它适用于任何类型的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