一、概述

在为前端提供http接口时,通常返回的数据需要统一的json格式,如包含错误码和错误信息等字段。

该功能的实现有四种可能的方式:

    • AOP 利用环绕通知,对包含@RequestMapping注解的方法统一处理

      • 优点:配置简单、可捕获功能方法内部的异常
      • 缺点:aop不能修改返回结果的类型,因此功能方法的返回值须统一为Object类型
    • filter 在过滤器层统一处理
      • 优点:配置简单
      • 缺点:无法识别异常结果,须对返回结果进行额外的反序列化
    • 拦截器  获取返回值不方便,且无法获取到String类型的返回值,无法实现该功能
    • HandlerMethodReturnValueHandler 无上述各方法的缺点,且能复用@ResponseBody等注解,为该功能的完美实现方案

二、基于HandlerMethodReturnValueHandler的实现方案

HandlerMethodReturnValueHandler是spring mvc为统一处理控制器功能方法返回结果的接口类,为策略模式实现,视图名称的解析和@ResponseBody输出json等功能均为基于该接口的实现。spring mvc处理流程的源码分析可参考自定义统一api返回json格式

具体实现方案如下:

定义统一的返回实体

public class ResponseInfo {
public static final int ERROR_CODE_SUCCESS = 0;
public static final int ERROR_CODE_MAPPING_FAILED = 100;
public static final int ERROR_CODE_BUSINESS_FAILED = 130; /**
* 错误码
*/
private int errorCode; /**
* 错误信息
*/
private String errorMsg; /**
* 数据
*/
private Object data;
...
}

自定义HandlerMethodReturnValueHandler实现类

/**
* 对controller返回的数据统一封装为ResponseInfo,注意:
* 1、controller异常由spring mvc异常机制处理,会跳过该处理器
* 2、该处理器仅处理包含@RestController、@ResponseBody注解的控制器*/
public class MyHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler { @Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> controllerClass = returnType.getContainingClass();
returnType.getMethodAnnotation(ResponseBody.class); return controllerClass.isAnnotationPresent(RestController.class)
|| controllerClass.isAnnotationPresent(ResponseBody.class)
|| returnType.getMethodAnnotation(ResponseBody.class) != null;
} @Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest) throws Exception { ResponseInfo responseInfo = new ResponseInfo();
if (returnValue instanceof ResponseInfo) {
responseInfo = (ResponseInfo) returnValue;
}
else {
responseInfo.setData(returnValue);
} // 标识请求是否已经在该方法内完成处理
mavContainer.setRequestHandled(true); HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.setContentType("application/json;charset=UTF-8");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.getWriter().write(JSON.toJSONString(responseInfo));
}
}

实例化和注册该处理器

@Configuration
public class WebConfig implements ApplicationContextAware { /**
* 实例化为bean
*/
@Bean
public MyHandlerMethodReturnValueHandler myHandlerMethodReturnValueHandler() {
return new MyHandlerMethodReturnValueHandler();
} /*
* 注册到容器,采用这种注册方式的目的:
* 自定义的HandlerMethodReturnValueHandler放在默认实现的前面,从而优先采用自定义处理策略
* 否则,无法覆盖@ResponseBody处理机制,且String类型的返回值将默认由ViewNameMethodReturnValueHandler处理而映射为视图名
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { RequestMappingHandlerAdapter handlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
handlers.add(this.myHandlerMethodReturnValueHandler());
handlers.addAll(handlerAdapter.getReturnValueHandlers());
handlerAdapter.setReturnValueHandlers(handlers);
}
}

spring-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <context:property-placeholder location="classpath:application.properties"/> <context:component-scan base-package="cn.matt" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
<context:exclude-filter type="assignable" expression="cn.matt.common.web.WebConfig" />
</context:component-scan> </beans>

spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <mvc:annotation-driven /> <context:component-scan base-package="cn.matt" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController" />
<context:include-filter type="assignable" expression="cn.matt.common.web.WebConfig" />
</context:component-scan> </beans>

控制器基类

public class BaseController {

    @ExceptionHandler
public void exp(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException {
response.setContentType("text/plain;charset=UTF-8");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0); ResponseInfo responseInfo = new ResponseInfo();
responseInfo.setErrorCode(ResponseInfo.ERROR_CODE_MAPPING_FAILED);
responseInfo.setErrorMsg(ex.getMessage());
response.getWriter().write(JSON.toJSONString(responseInfo));
}
}

测试控制器

@RestController
@RequestMapping("/test")
public class TestController extends BaseController { @RequestMapping(value = "/hello")
public String hello() {
return "hello";
} @RequestMapping(value = "/user")
public UserInfo getUser() {
UserInfo userInfo = new UserInfo();
userInfo.setUserName("matt");
userInfo.setProvince("安徽");
userInfo.setCity("阜阳");
return userInfo;
}
}

启动后,输入http://localhost:8080/wfc-web/test/user,http输出:

{"data":{"city":"阜阳","province":"安徽","userName":"matt"},"errorCode":0}

* 上述基于继承的异常处理方式,有一定侵入性,基于@RestControllerAdvice 或 @ControllerAdvice注解的方案无侵入性,详细可参考 Spring Boot 系列(八)@ControllerAdvice 拦截异常并统一处理

参考:

SpringMVC HandlerMethodReturnValueHandler解读

spring mvc 处理Controller返回结果和HandlerMethodReturnValueHandler使用

自定义统一api返回json格式(app后台框架搭建三)

springmvc获取上下文ApplicationContext

Spring MVC 使用介绍(十二)控制器返回结果统一处理的更多相关文章

  1. Spring MVC 框架结构介绍(二)

    Spring MVC框架结构 Spring MVC是围绕DispatcherServlet设计的,DispatcherServlet向处理程序分发各种请求.处理程序默认基于@Controller和@R ...

  2. Spring MVC 使用介绍(二)—— DispatcherServlet

    一.Hello World示例 1.引入依赖 <dependency> <groupId>javax.servlet</groupId> <artifactI ...

  3. Spring MVC 使用介绍(十五)数据验证 (二)依赖注入与方法级别验证

    一.概述 JSR-349 (Bean Validation 1.1)对数据验证进一步进行的规范,主要内容如下: 1.依赖注入验证 2.方法级别验证 二.依赖注入验证 spring提供BeanValid ...

  4. Spring MVC 使用介绍(十四)文件上传下载

    一.概述 文件上传时,http请求头Content-Type须为multipart/form-data,有两种实现方式: 1.基于FormData对象,该方式简单灵活 2.基于<form> ...

  5. Spring MVC体系结构和处理请求控制器

    Spring MVC体系结构和处理请求控制器 一:MVC设计模式: (1.)数据访问接口:DAO层 (2.)处理业务逻辑层:Service层 (3.)数据实体:POJO (4.)负责前段请求接受并处理 ...

  6. 【Spring MVC系列】--(4)返回JSON

    [Spring MVC系列]--(4)返回JSON 摘要:本文主要介绍如何在控制器中将数据生成JSON格式并返回 1.导入包 (1)spring mvc 3.0不需要任何其他配置,添加一个jackso ...

  7. Spring MVC 使用介绍(十三)数据验证 (一)基本介绍

    一.消息处理功能 Spring提供MessageSource接口用于提供消息处理功能: public interface MessageSource { String getMessage(Strin ...

  8. Spring Boot 2.X(十二):定时任务

    简介 定时任务是后端开发中常见的需求,主要应用场景有定期数据报表.定时消息通知.异步的后台业务逻辑处理.日志分析处理.垃圾数据清理.定时更新缓存等等. Spring Boot 集成了一整套的定时任务工 ...

  9. Spring MVC 4.1.4 RESTFUL风格返回JSON数据406错误处理

    Spring MVC 4.1.4 RESTFUL风格返回JSON数据406错误处理 今天在使用spring4.1.4,使用ResponseBody注解返回JSON格式的数据的时候遇到406错误. 解决 ...

  10. thinkPHP 模板中的语法知识 详细介绍(十二)

    原文:thinkPHP 模板中的语法知识 详细介绍(十二) 本章节:介绍模板中的语法,详细的语法介绍 一.导入CSS和JS文件    ==>记住常量的是大写 1.css link .js  sc ...

随机推荐

  1. Reactive Extensions 相见恨晚的Rx.Net

    何为Reactive Extensions(Rx) Rx是一个遵循函数式编程的类库,它引用观察者以及迭代器设计模式对可观察对象产生的数据进行异步消费.使用Rx, 开发人员将使用LINQ运算符操作异步数 ...

  2. 我爱Java系列之《JavaEE面试宝典》---【IO流面试总结】

    1.什么是比特(Bit),什么是字节(Byte),什么是字符(Char),它们长度是多少,各有什么区别 答案 Bit最小的二进制单位 ,是计算机的操作部分 取值0或者1 Byte是计算机操作数据的最小 ...

  3. springboot源码分析-SpringApplication

    SpringApplication SpringApplication类提供了一种方便的方法来引导从main()方法启动的Spring应用程序 SpringBoot 包扫描注解源码分析 @Spring ...

  4. Gerrit系统框架介绍

    Gerrit目录介绍 转自:https://blog.csdn.net/tanshizhen119/article/details/79889242 先上图 bin/ : 主要是放gerrit.sh启 ...

  5. px妙转rem

    px:像素,相对长度单位,相对于显示器屏幕的分辨率而言(其实我个人认为可以理解为固定单位): rem:这是个web前端中的新成员,是CSS3中新增的一个相对单位.相对的只是html根元素: 1.设定两 ...

  6. 深圳市共创力咨询CEO杨学明的最新演讲:互联网模式下的企业创新管理

    2018年11月14日, 深圳市共创力咨询董事长.深圳市汇成研发管理咨询公司董事长杨学明先生受邀参加由深圳图书馆主办,深圳手讯视频承办的“倾听行业之声”2018第二届世界CED智慧大会,此次分享的主题 ...

  7. emacs 文本替换

    文本替换方法: M-% (query-replace) 输入 响应 SPC 或者 y 替换当前匹配并前进到下一个匹配处 DEL 或者 n 忽略此次匹配并前进到下一个匹配处 . 替换当前匹配并退出 , ...

  8. Linux/Ubuntu 16.04 使用校园网客户端Dr.com DrClient 有线连网,同时开启WiFi热点

    前面写过Ubuntu 16.04 使用校园网客户端 DrClient 无线上网,在这篇文章中将要介绍下,在Ubuntu 16.04上如何使用校园网客户端实现有线登录,这个问题也让博主困惑了很久,但是问 ...

  9. RocketMQ4.3.x对顺序消息的理解

    1.RocketMQ消息队列简单介绍 这里简单介绍一下RocketMQ的消息队列的模型 一个topic对应多个队列如下图: 生产者和消费者分别向队列中发送和消费消息,生产者和消费者都可以是多个,通过组 ...

  10. UVA - 11090 - Going in Cycle!!(二分+差分约束系统)

    Problem  UVA - 11090 - Going in Cycle!! Time Limit: 3000 mSec Problem Description You are given a we ...