系统定制开发SpringMVC Date类型参数解析

系统定制开发前端向后端传递Date数据,系统定制开发后端有三种实现方式,@RequestBody注解、@RequestParam系统定制开发注解和不加注解。系统定制开发日期数据的形式也有两种,系统定制开发时间戳和日期字符串。

情况一:后端用@RequestBody注解标记。系统定制开发在这种情况下,Date系统定制开发又可以分两种数据存在方式:系统定制开发一种是单独作为接口的系统定制开发参数来传递,系统定制开发另外一种是作为参数对系统定制开发象属性来传递。

1.Date系统定制开发类型数据单独作为接口系统定制开发的参数传递。系统定制开发在默认情况(不实现任何日期转换接口或方法)下,前端向后端传递日期数据时,前端日期数据需要转换成时间戳,代码如下所示。如果前端向后端传的数据是日期字符串,会抛出相应的异常(Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('-' (code 45)): Expected space separating root-level values,我这里的日期格式是:“yyyy-MM-dd HH:mm:ss”)。如果在不实现任何日期转换接口或方法情况下,想要实现前端向后端传递日期字符串的功能,后端只能将日期类型参数封装到参数对象中,作为Date作为参数对象的属性来传递。

   示例代码1 (Date单独作为接口的参数)

  1. @Test
  2.     public void testRequestBodyDateTest() {
  3.         String params = "1608537480434";
  4.         String jsonStr = StringUtils.replaceSingleQuoteWithDouble(params);
  5.                     this.postBody("/testRequestBodyDate/", jsonStr);
  6. }
  7.     @PutMapping("/testRequestBodyDate/")
  8.     public CommonResult  testRequestBodyDate(@RequestBody Date test){
  9.         logger.info("testRequestBodyDate");
  10.         return CommonResult.success(test);
  11. }

 

2.Date类型数据作为参数对象属性传递。在默认情况(不实现任何日期转换接口或方法)下,前端的日期数据只能是日期字符串,代码如下所示。如果是时间戳,org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.Date` from String "1608537480434": not a valid representation (error: Failed to parse Date value '1608537480434': Unparseable date: "1608537480434");。

示例代码2 (Date作为参数对象属性)

  1.    @Test
  2.     public void testBodyTest() {
  3.         String params = "{intTest':1,'longTest':0,'dateTest':'2020-12-21 15:58:00'}";
  4.         String jsonStr = StringUtils.replaceSingleQuoteWithDouble(params);
  5.                     this.postBody("/testRequestBody/", jsonStr);
  6.     }
  7.     @PostMapping("/testRequestBody/")
  8.     public CommonResult  testBody(@RequestBody TestObject testObject){
  9.         logger.info("testBody");
  10.         return CommonResult.success(testObject);
  11.     }

情况二:后端用@RequestParam注解或者没有注解的方式接收前端日期字符串。在没有实现日期转换相关接口情况下,代码如下示例代码3所示,会出现的异常(org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '2020-12-21 17:14:44'; nested exception is java.lang.IllegalArgumentException)。此时Date数据不管是单独作为接口的参数,还是作为参数对象属性,后端都必须实现日期转换接口或方法,在转换接口或方法中实现日期转换功能,此时,前端传到后端的日期数据可以是时间戳,也可以是日期字符串,具体逻辑由开发者自己决定,这里有两种实现方法。方法一是为controller编写一个基类,在基类中实现一个方法,用@InitBinder注解,并绑定DataBinder(WebDataBinder ),如示例代码4;方法二是实现Converter接口,如下示例代码5。

示例代码3

  1.   @Test
  2.     public void testDateTest() {
  3.         String params = "{'test':'2020-12-21 15:58:00'}";
  4.         String jsonStr = StringUtils.replaceSingleQuoteWithDouble(params);
  5.                     this.post("/testDate/", jsonStr);
  6.     }
  7.     @PutMapping("/testDate/")
  8.     public CommonResult  testDate(@RequestParam Date test){
  9.         logger.info("testDate");
  10.         return CommonResult.success(test);
  11. }

示例代码4(方法一)

  1. /**
  2.      * 将前台传递过来的日期格式的字符串,自动转化为Date类型
  3.      */
  4.     @InitBinder
  5.     public void initBinder(WebDataBinder binder)
  6.     {
  7.         // Date 类型转换
  8.         binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
  9.         {
  10.             @Override
  11.             public void setAsText(String text)
  12.             {
  13.                 setValue(DateUtils.parseDate(text, "yyyy-MM-dd HH:mm:ss"));
  14.             }
  15.         });
  16.     }

示例代码5(方法二)

  1. @Configuration
  2. public class AnerTesterConfig implements Converter<String, Date> {
  3.     private static Logger logger = LoggerFactory.getLogger(AnerTesterConfig.class);
  4.     @Override
  5.     public Date convert(String source) {
  6.         String value = source.trim();
  7.         if (StringUtils.isEmpty(value)) {
  8.             return null;
  9.         }
  10.         if (StringUtils.isNotEmpty(dateFormat)) {
  11.             return parseDate(value, dateFormat);
  12.         } else {
  13.             return parseDate(value, ""yyyy-MM-dd HH:mm:ss"");
  14.         }
  15.     }
  16.     /**
  17.      * format date
  18.      *
  19.      * @param dateStr
  20.      * @param format S
  21.      * @return Date
  22.      */
  23.     public Date parseDate(String dateStr, String format) {
  24.         Date date=null;
  25.         try {
  26. // 这里可以根据dateStr 是时间戳还是日期格式字符串来做转换           
  27. DateFormat dateFormat = new SimpleDateFormat(format);
  28.             date = dateFormat.parse(dateStr);
  29.         } catch (Exception e) {
  30.             logger.error("formatting date string {} is error", dateStr);
  31.          }
  32.         return date;
  33.     }
  34. }

接下来,我们来看看使用SpringMVC进行开发的时候,通过不同方式传递Date类型的请求参数具体原理是什么。SpringMVC中处理控制器参数的接口是HandlerMethodArgumentResolver,此接口有很多子类,分别处理不同注解的参数,上述提到了@RequestBody和@RequestParam两种注解,依次对应着两种解析处理器

  • RequestParamMethodArgumentResolver:解析处理使用了@RequestParam注解的参数、MultipartFile类型参数和java基本数据类型(如long、int、byte等)参数。
  • RequestResponseBodyMethodProcessor:解析处理@RequestBody注解的参数。

实际上,一般在解析一个控制器的请求参数,用到的是解析器组合对象:HandlerMethodArgumentResolverComposite,其封装了所有继承HandlerMethodArgumentResolver解析器的子类。而HandlerMethodArgumentResolver子类在解析参数的时候会用HttpMessageConverter转换器的子类进行数据匹配转换。常见的有MappingJackson2HttpMessageConverter,用来处理application/json媒体类型RequestResponseBodyMethodProcessor用来转换json字符串);FormHttpMessageConverter,用来处理form表单数据和application/x-www-form-urlencoded(RequestParamMethodArgumentResolver用来转换@RequestParam注解的参数和无注解的java基本数据类型参数其实HandlerMethodArgumentResolver子类使用哪个HttpMessageConverter子类实际上是由请求头中的ContentType决定的

首先,分析一下使用了@RequestParam注解和不带注解的情况。RequestParamMethodArgumentResolver接收数据之前,TypeConverterDelegate会通过DataBinder来对数据进行处理,这里可以在Controller中注册InitBinder对Date类型数据转换,如上述方法一。DataBinder在对数据进行转换后,会把数据交给RequestParamMethodArgumentResolver处理,此时,FormHttpMessageConverter会把数据交给TypeConverterDelegate处理,TypeConverterDelegate会通过会通过sourceType和targetType两种类型来查找Converter,如果此时定义了String数据类型转Date数据类型的Converter时,就会调用自定义的Converter,否则的系统会调用默认的转换器ObjectToObjectConverter。所以,可以自定义String数据类型转Date数据类型的Converter,实现String到Date的转换,如上述方法二。

其次,分析带有@RequestBody注解,Date单独作为接口参数的情况。在AbstractJackson2HttpMessageConverter中,会有一个ObjectMapper对象,该对象主要是负责json字符串的转换。AbstractJackson2HttpMessageConverter类中调用readJavaType方法,该方法调用objectMapper对象的readValue(InputStream src, JavaType valueType)方法来读取数据,其中,reader载有源数据,javaType表示需要转换成的数据类型。 接下来方法调用关系为: readValue->_readMapAndClose->_findRootDeserializer->DateDeserializer的deserialize方法->_parseDate->父类_parseDate(代码见示例6所示,转向case 7分支),通过JsonParser的getLongValue方法获取时间戳,然后创建Date对象返回。在这里,如果日期数据为时间戳,会被正常解析,如果日期数据为日期字符串(yyyy-MM-dd HH:mm:ss格式数据),方法调用关系为:readValue->_readMapAndClose->_initForReading->UTF8StreamJsonParser的nextToken方法->_nextTokenNotInObject->_parsePosNumber->_verifyRootSpace,在_verifyRootSpace方法中,如果出现json解析的非法字符,会调用_reportUnexpectedChar,并抛出异常,该方法代码如示例代码7所示。所以,当这里的javaType为Date类型时,源数据格式必须为时间戳,否则为抛出异常。

示例代码6

  1. protected Date _parseDate(JsonParser p, DeserializationContext ctxt) throws IOException {
  2. switch(p.getCurrentTokenId()) {
  3. case 3:
  4. return this._parseDateFromArray(p, ctxt);
  5. case 4:
  6. case 5:
  7. case 8:
  8. case 9:
  9. case 10:
  10. default:
  11. return (Date)ctxt.handleUnexpectedToken(this._valueClass, p);
  12. case 6:
  13. return this._parseDate(p.getText().trim(), ctxt);
  14. case 7:
  15. long ts;
  16. try {
  17. ts = p.getLongValue();
  18. } catch (InputCoercionException | JsonParseException var7) {
  19. Number v = (Number)ctxt.handleWeirdNumberValue(this._valueClass, p.getNumberValue(), "not a valid 64-bit long for creating `java.util.Date`", new Object[0]);
  20. ts = v.longValue();
  21. }
  22. return new Date(ts);
  23. case 11:
  24. return (Date)this.getNullValue(ctxt);
  25. }
  26. }

 示例代码7

  1. protected void _reportUnexpectedChar(int ch, String comment) throws JsonParseException {
  2.         if (ch < 0) {
  3.             this._reportInvalidEOF();
  4.         }
  5.         String msg = String.format("Unexpected character (%s)", _getCharDesc(ch));
  6.         if (comment != null) {
  7.             msg = msg + ": " + comment;
  8.         }
  9.         this._reportError(msg);
  10.     }

 

最后,分析带有@RequestBody注解,Date作为参数对象属性情况。AbstractJackson2HttpMessageConverter类中调用readJavaType方法,该方法调用objectMapper对象的readValue(reader, javaType)方法来读取数据,接着方法调用关系为:readValue->_readMapAndClose->JsonDeserializer对象的deserialize->deserializeFromObject方法。在deserializeFromObject方法中,SettableBeanProperty根据目标对象中属性的类型依次调用deserializeAndSet方法来对json数据中每一项数据进行转换。在方法deserializeAndSet中会调用数据类型对应的Deserializer(为StdDeserializer子类)对象中的deserialize方法。因此,在对Date类型转换时,调用了DateDeserializer对象的deserialize方法,接着方法调用的关系为:deserialize->_parseDate->父类_parseDate(代码如示例代码6,转向代码中的case 6分支)->StdDeserializer的 _parseDate(JsonParser p, DeserializationContext ctxt)->_parseDate(String value, DeserializationContext ctxt)->DeserializationContext的parseDate(String dateStr),在该方法中调用getDateFormat方法获取日期格式,这里会去配置文件中读取日期格式,就是在application.yml配置文件中配置spring:jackson:date-format的值。parseDate(String dateStr)方法的源码如示例代码8所示。

示例代码8

  1. public Date parseDate(String dateStr) throws IllegalArgumentException {
  2.         try {
  3.             DateFormat df = this.getDateFormat();
  4.             return df.parse(dateStr);
  5.         } catch (ParseException var3) {
  6.             throw new IllegalArgumentException(String.format("Failed to parse Date value '%s': %s", dateStr, ClassUtil.exceptionMessage(var3)));
  7.         }
  8.     }

 

综上所述,就是Spring MVC中后端接收型数据的来龙去脉。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发