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

时间:2017-06-07 09:52:53

标签: java spring spring-mvc parameters

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

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

有什么建议吗?

4 个答案:

答案 0 :(得分:0)

  1. 实施org.springframework.core.convert.converter.Converter;接口
  2. 注册弹簧转换服务。
  3. 在控制器中使用 分享以下示例代码:
  4. 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
    }
    

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

{{1}}

答案 3 :(得分:0)

我建议使用自定义 bean validation 保存参数 sinceuntil 的模型类型(使用 Lombok,但您也可以编写 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;
        }
    }
}