Spring MVC 使用介绍(十二)控制器返回结果统一处理
一、概述
在为前端提供http接口时,通常返回的数据需要统一的json格式,如包含错误码和错误信息等字段。
该功能的实现有四种可能的方式:
-
- AOP 利用环绕通知,对包含@RequestMapping注解的方法统一处理
- 优点:配置简单、可捕获功能方法内部的异常
- 缺点:aop不能修改返回结果的类型,因此功能方法的返回值须统一为Object类型
- filter 在过滤器层统一处理
- 优点:配置简单
- 缺点:无法识别异常结果,须对返回结果进行额外的反序列化
- 拦截器 获取返回值不方便,且无法获取到String类型的返回值,无法实现该功能
- HandlerMethodReturnValueHandler 无上述各方法的缺点,且能复用@ResponseBody等注解,为该功能的完美实现方案
- AOP 利用环绕通知,对包含@RequestMapping注解的方法统一处理
二、基于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使用
springmvc获取上下文ApplicationContext