SpringBoot接收时间类型参数完整解决方案

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> SpringBoot接收时间类型参数完整解决方案

在JavaWeb程序的开发过程中,接口是前后端对接的主要窗口,而接口参数的接收有时候是一个令人头疼的事情,这其中最困扰程序猿的,应该是时间参数的接收。

比如:设置一个用户的过期时间,前端到底以什么格式传递参数呢?时间戳?还是2019-12-01 22:13:00这种格式?还是其他格式?

今天我就来总结一下SpringBoot Web应用接口接收时间类型参数的问题解决方案。

注:目前我对Spring源码的掌握还不是很好,所以这一篇仅仅总结一下解决方法,后面感悟多了会重写一下!:sunglasses:

示例代码请前往:https://github.com/laolunsi/spring-boot-examples

经过简单的测试,我们知道:

  • 不使用@RequestBody注解的情况下,所有时间类型参数都会引起报错;
  • 使用@RequestBody,前端传递时间戳或`2019-11-22`形式正常,传递`2019-11-22 11:22:22`报错,其他格式同样报错。
  • 使用@RequestBody,前端传递时间戳或 2019-11-22形式正常,传递 2019-11-22 11:22:22报错,其他格式同样报错。

    之前有接触过类似的解决办法,在类的属性上加上@DateFormat注解,解决单个时间参数问题。

    但是局限较多。

    理想的解决方案是:一次配置,全局通用,多种格式,自动转换(朗朗上口嗷)

    一、源码简要分析

    首先我们来简单分析一下源码:

    SpringBoot接收时间类型参数完整解决方案

    深入的就不解释了(我现在也不懂🤦‍♂️)

    简单来说,接口接收的参数,首先被HandlerMethodArgumentResolver的实现类处理了一遍,将其转换为我们需要的格式。

    这里主要分为两种情况:

  • 使用了@RequestBody的参数,一般是对象接收,前端传递的通常是JSON形式
  • 其他接收参数的方式,比如@RequestAttribute,@RequestParam,或者默认形式,前端传递的通常是表单参数、请求URL后缀参数等
  • 其他接收参数的方式,比如@RequestAttribute,@RequestParam,或者默认形式,前端传递的通常是表单参数、请求URL后缀参数等

    二、解决方法

  • 默认形式,或使用@RequestAttribute,或使用@RequestParam,这样的参数,通过配置converter来解决问题
  • 使用@RequestBody解析的参数,通过在ObjectMapper中配置序列化和反序列化规则来处理
  • 使用@RequestBody解析的参数,通过在ObjectMapper中配置序列化和反序列化规则来处理

    2.1 自定义converter

    针对第一种情况,我们需要配置converter,这里介绍两种方法:

  • @ControllerAdvice + @InitBinder
  • 直接使用@Bean定义converter类
  • 直接使用@Bean定义converter类

    首先我们这里需要一个DateConverter类,这个类实现了Converter接口,重写了其中的convert方法,将String转成Date类型:

    我们这里定义了三种处理格式:

    
    /**
     * 日期转换类
     * 将标准日期、标准日期时间、时间戳转换成Date类型
     */
    /*@Deprecated*/
    public class DateConverter implements ConverterString, Date {
    
        private Logger logger = LoggerFactory.getLogger(DateConverter.class);
    
        private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
        private static final String shortDateFormat = "yyyy-MM-dd";
        private static final String timeStampFormat = "^\d+$";
    
        @Override
        public Date convert(String value) {
            logger.info("转换日期:" + value);
    
            if(value == null || value.trim().equals("") || value.equalsIgnoreCase("null")) {
                return null;
            }
    
            value = value.trim();
    
            try {
                if (value.contains("-")) {
                    SimpleDateFormat formatter;
                    if (value.contains(":")) {
                        formatter = new SimpleDateFormat(dateFormat);
                    } else {
                        formatter = new SimpleDateFormat(shortDateFormat);
                    }
                    return formatter.parse(value);
                } else if (value.matches(timeStampFormat)) {
                    Long lDate = new Long(value);
                    return new Date(lDate);
                }
            } catch (Exception e) {
                throw new RuntimeException(String.format("parser %s to Date fail", value));
            }
            throw new RuntimeException(String.format("parser %s to Date fail", value));
        }
    }
    

    注:这个DateConverter类在下面都会用到。

    
    import com.aegis.yqmanagecenter.config.date.DateConverter;
    import com.aegis.yqmanagecenter.model.bean.common.JsonResult;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.propertyeditors.CustomDateEditor;
    import org.springframework.core.convert.support.GenericConversionService;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.*;
    
    import java.beans.PropertyEditorSupport;
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    @ControllerAdvice
    public class ControllerHandler {
    
        private Logger logger = LoggerFactory.getLogger(ControllerHandler.class);
    
        @InitBinder
        public void initBinder(WebDataBinder binder) {
            // 方法1,注册converter
            GenericConversionService genericConversionService = (GenericConversionService) binder.getConversionService();
            if (genericConversionService != null) {
                genericConversionService.addConverter(new DateConverter());
            }
    
            // 方法2,定义单格式的日期转换,可以通过替换格式,定义多个dateEditor,代码不够简洁
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            CustomDateEditor dateEditor = new CustomDateEditor(df, true);
            binder.registerCustomEditor(Date.class, dateEditor);
    
    
            // 方法3,同样注册converter
            binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
                @Override
                public void setAsText(String text) throws IllegalArgumentException {
                    setValue(new DateConverter().convert(text));
                }
            });
    
        }
    }
    

    注:上面的三个方法都是利用@ControllerAdvice+@InitBinder来设置时间参数处理的,其中1和3都可以设置DateConverter,而方法2只能一个一个手动设置格式。

    这里需要注意,上述配置方法都无法解决Json格式数据中的时间参数接收问题。下面我们直接看完整的解决方案——将DateConverter注册为组件,并使用ObjectMapper来配置时间参数的序列化(接口返回值)和反序列化形式(接口接收参数)。

    2.2 配置ObjectMapper以及完整解决方案

    完整的解决方案:

    
    /**
     * 日期转换配置
     * 解决@RequestAttribute、@RequestParam和@RequestBody三种类型的时间类型参数接收与转换问题
     */
    @Configuration
    public class DateConfig {
    
        /**
         * 默认日期时间格式
         */
        public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    
        /**
         * Date转换器,用于转换RequestParam和PathVariable参数
         */
        @Bean
        public ConverterString, Date dateConverter() {
            return new DateConverter();
        }
    
        /**
         * Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json
         * 使用@RequestBody注解的对象中的Date类型将从这里被转换
         */
        @Bean
        public ObjectMapper objectMapper(){
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
            objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
    
            JavaTimeModule javaTimeModule = new JavaTimeModule();
    
            //Date序列化和反序列化
            javaTimeModule.addSerializer(Date.class, new JsonSerializerDate() {
                @Override
                public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                    SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
                    String formattedDate = formatter.format(date);
                    jsonGenerator.writeString(formattedDate);
                }
            });
            javaTimeModule.addDeserializer(Date.class, new JsonDeserializerDate() {
                @Override
                public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
                    return new DateConverter().convert(jsonParser.getText());
                }
            });
    
            objectMapper.registerModule(javaTimeModule);
            return objectMapper;
        }
    
    }
    

    原文始发于微信公众号(猿生物语):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> SpringBoot接收时间类型参数完整解决方案


     上一篇
    实战SpringBoot缓存开发 实战SpringBoot缓存开发
    点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:Yrion cnblogs.com/wyq178/p/9840985.html 前言:缓存在开发中是一个必不可少的优化点,近期在公司的项目重构中,关
    下一篇 
    SpringBoot全局异常处理配置 SpringBoot全局异常处理配置
    示例代码在https://github.com/laolunsi/spring-boot-examples 一、全局统一异常处理配置先来看这样一个接口: @RestController @RequestMapping(value = ""