用一个切面来统一返回前端的JSON格式
新项目使用Spring MVC
+ MyBatis
架构来做,这套框架自己应该比较得心应手,这里来写一下这两天做的一些设计。
首先是静态资源的处理,关于这个之前有写文章单独讲过,这里不再重复写,不太清楚的童鞋可以移步查看
接着需要统一JSON
的返回格式,和前端工程师约定,对于字符串类型和日期类型都返回字符串,而对于普通数字类型的话都返回数字,金额类数字都返回格式化好的保留一位小数的字符串(比如”10.0”),另外数字和金额的默认值为0,而字符串的默认值为字符串空(“”),并且对于返回的格式也有规范,所有的JSON
返回必须形如:
{
"status": 1,
"data": data,
"msg": ""
}
其中:
status
代表了该请求的成功与否,若为1表示成功,若为0表示失败,此时msg
中将包含显示给用户的错误信息data
代表了返回给前端的数据,可以是一个复杂的数据结构msg
前面提到了,一般放异常信息。
下面,我们首先需要对默认的Jackson
的序列化规则做出修改,这里自定义一个ObjectMapper
:
/**
* 自定义jackson解析器
* @author Zhu
* @date 2015-5-14
* @version 0.0.1
* @description
*/
@Component
public class CustomerObjectMapper extends ObjectMapper {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
*
*/
public CustomerObjectMapper() {
super();
// this.setSerializationInclusion(Include.NON_NULL);
// 允许单引号
// this.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 字段和值都加引号
// this.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 数字也加引号
// this.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, true);
// this.configure(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS, true);
this.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>(){
@Override
public void serialize(Object value, JsonGenerator generator,
SerializerProvider provider) throws IOException,
JsonProcessingException {
generator.writeString("");
}
});
}
}
然后将我们自定义的ObjectMapper
注入到MappingJackson2HttpMessageConverter
中去并重新配置Spring MVC
中的message converter
<bean id="mappingJackson2HttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="customerObjectMapper"></property>
</bean>
<mvc:annotation-driven>
<mvc:message-converters>
<ref bean="mappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
接下来,我们可以在Controller
方法上添加@ResponseBody
注解来返回JSON
(这里需要说一句,classpath
下需要有jackson
的jar包,这是前提),但是这就要求我们在每个Controller
方法中都进行一些重复的工作,比如:
public ModelMap getUserList(){
ModelMap modelMap = new ModelMap();
try {
modelMap.put("status", 1);
modelMap.put("data", userSerivce.getUserList());
modelMap.put("", "");
} catch (Exception e) {
modelMap.put("status", 0);
modelMap.put("data", "");
modelMap.put("msg", e.getMessage());
logger.error("getUserList occurs error:", e);
}
}
这样的重复代码在项目中不建议出现,我们其实只需要关心其中的data就可以了,所以我打算将其统一写到一个方法中,并且这也有利于以后对于公共结构的更改。下面就是一个切面来统一处理所有的接口:
/**
* 其中ResponseBase类和注解类CustomResponseBody都是自己写的
*/
/**
* @author Zhu
* @date 2015-5-18
* @version 0.0.1
* @description 负责将返回转换成统一消息格式
* 序列化为的格式如下:
* {
"status": 1,
"data": data,
"msg": ""
}
其中被注解的方法只需要关心data的内容即可
*/
@Aspect
@Component
public class ResponseAspect {
@Resource
private MappingJackson2HttpMessageConverter converter;
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 拦截所有@ResponseBody
* @author Zhu
* @date 2015-5-18上午11:32:26
* @description
*/
@Pointcut("execution(* com.xxx.*.web.controller.*.*(..)) && @annotation(com.xxx.common.annotation.CustomResponseBody)")
public void responseBodyPointCut() {
}
/**
* @author Zhu
* @date 2015-5-18上午11:35:42
* @description
* @param pjp
* @throws Throwable
*/
@Around(value = "responseBodyPointCut()")
@ResponseBody
public void formatResult2JSON(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed();
ResponseBase responseBase = new ResponseBase();
responseBase.setData(ret);
HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
converter.write(responseBase, MediaType.APPLICATION_JSON, outputMessage);
shutdownResponse(response);
}
/**
*
* @author Zhu
* @date 2015-5-18下午6:01:46
* @description
* @param jp
* @param error
* @throws Throwable
*/
@AfterThrowing(pointcut = "responseBodyPointCut()", throwing = "error")
public void handleForException(JoinPoint jp, Throwable error) throws Throwable{
ResponseBase responseBase = new ResponseBase();
responseBase.setStatus(0);
responseBase.setMsg(error.getMessage());
HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
logger.error(jp.getSignature().getName() + "-error!", error);
HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
converter.write(responseBase, MediaType.APPLICATION_JSON, outputMessage);
shutdownResponse(response);
}
private void shutdownResponse(HttpServletResponse response) throws IOException{
response.getOutputStream().close();
}
}
当然这里有几个关键点,需要提一下:
- 这也就提到了一个如何在
Spring
中手动使用配置好的Jackson
来序列化输出,这里我注入MappingJackson2HttpMessageConverter
,因为这里想要统一项目的JSON
序列化都交由Jackson
来做 - 由于是对
Controller
的代理,而一般Controller
是不实现接口的,那么就无法使用JDK
自带的动态代理,需要用到cglib
来做 - 关于上下文的问题,这里我在做的时候,由于碰到了两个上下文,即一般所说的rootContext和webContext。。。。
- 在自己返回json的情况下,需要shutdownResponse,即将Response关掉,不然可能会在这里输出json后还会再次输出一些内容
其余的内容后续再写吧,现在手头上项目太多,需要整理的东西也太多了~
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。