比较合适的前后端交互方式
这篇博客是对过去博客的整理与总结,现在,以下的博客都可以认为过时了。
存在日期类型的JSON数据,进行SpringMVC参数绑定时存在的问题和解决方案
前端发送请求
var jsonObj = { 'name' : '啊aaaa', // 乱码问题 'date' : '1905-01-02 14:23:59', // 时间转化问题 'money' : '10.333', // 小数 'no' : '9', // 数字 'serial' : '2147483999' // Long }; $.ajax({ type : "post", url : "/interaction", dataType : "json", contentType : 'application/json', data : JSON.stringify(jsonObj), success : function(data) { alter(data); }, error : function(data) { // TODO } });
没什么问题。
后端绑定参数
1、首先需要指定SpringMVC参数绑定之前的JSON转化策略,选择Jackson。
2、其次需要自定义一个ObjectMapper类,目的是在Jackson的ObjectMapper上追加定制对于日期转化的策略。
3、最后,把这些策略反映在SpringMVC的配置文件中。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
public class JacksonObjectMapper extends ObjectMapper { private static final long serialVersionUID = -8909209092708797621L; public JacksonObjectMapper() { super(); configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); } }
<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="io.spldeolin.bestpractice.util.JacksonObjectMapper"/> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
通过写个请求方法和实体类来测试一下
@RequestMapping(value = "interaction", method = RequestMethod.POST) @ResponseBody public String interaction(@RequestBody InteractionInput input) { LOG.info(input); return "success"; }
package io.spldeolin.bestpractice.input; import java.math.BigDecimal; import java.util.Date; public class InteractionInput { private String name; private Date date; private BigDecimal money; private Integer no; private Long serial; // getters and setters }
结果
绑定失败时的处理
示例中,如果前端把jsonObj.date改成"1905-------01-------02 14爱上打是的:23ssssss:59"这样的非法格式,绑定就会失败,如图所示
这样的提示实际上不够友好(虽然发生的原因基本上是前端BUG),但最好还是捕获一下。
@ExceptionHandler(HttpMessageNotReadableException.class) @ResponseStatus(HttpStatus.OK) @ResponseBody public String processHttpMessageNotReadableException(HttpMessageNotReadableException e) { return "请求不可读(可能原因:1.没有Request Body 2.Request Body格式有误)"; }
结果
参数校验
参数绑定成功了,但最好再加一层校验,比如年龄,用Integer类型绑定,虽然上万的数字也可以成功绑定,但显然是不合理的。所以应该引入JSR303的实现
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.4.1.Final</version> </dependency>
package io.spldeolin.bestpractice.input; import java.math.BigDecimal; import java.util.Date; import javax.validation.constraints.Max; public class InteractionInput { private String name; private Date date; @Max(value = 10L, message = "钱太多") private BigDecimal money; private Integer no; private Long serial; // getters and setters }
@RequestMapping(value = "interaction", method = RequestMethod.POST) @ResponseBody public String interaction(@RequestBody @Valid InteractionInput input, BindingResult checker) { // 这里只是为了演示,实际上这段解析BindingResult对象的代码最好抽到共通类中 if (checker.hasFieldErrors()) { for (FieldError error : checker.getFieldErrors()) { String errmsg = error.getDefaultMessage(); LOG.error(errmsg); return errmsg; } } LOG.info(input); return "success"; }
返回值
最好返回一个实体类对象,而不是一个具体的String什么的,比如
@RequestMapping(value = "interaction", method = RequestMethod.POST) @ResponseBody public RequestResult interaction(@RequestBody @Valid InteractionInput input, BindingResult checker) { // 这里只是为了演示,实际上这段解析BindingResult对象的代码最好抽到共通类中 if (checker.hasFieldErrors()) { for (FieldError error : checker.getFieldErrors()) { String errmsg = error.getDefaultMessage(); LOG.error(errmsg); return RequestResult.failure().errmsg(errmsg); } } LOG.info(input); return RequestResult.success().data("交互成功。(实际开发中data参数可以放各种想要传给前端的对象)"); }
function interaction() { var jsonObj = { 'name' : '啊aaaa', // 乱码问题 'date' : '1905-01-02 14:23:59', // 时间转化问题 'money' : '10.333', // 小数 'no' : '9', // 数字 'serial' : '2147483999' // Long }; $.ajax({ type : "post", url : "/interaction", dataType : "json", contentType : 'application/json', data : JSON.stringify(jsonObj), success : function(resp) { if (resp.result) { alert(resp.data); // 或者解析这个resp.data } else { alert(resp.errmsg); } }, error : function(data) { // TODO } }); }
public class RequestResult { private boolean result; private Object data; private String errmsg; public boolean isResult() { return result; } private RequestResult() {} public static RequestResult success() { RequestResult instance = new RequestResult(); instance.setResult(true); return instance; } public static RequestResult failure() { RequestResult instance = new RequestResult(); instance.setResult(false); return instance; } public RequestResult data(Object data) { this.data = data; return this; } public RequestResult errmsg(String errmsg) { this.errmsg = errmsg; return this; } }