Spring MVC 使用介紹(十二)控制器返回結果統一處理


一、概述

在為前端提供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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM