1. 程式人生 > >springmvc 通過異常增強返回給客戶端統一格式 springmvc請求引數異常處理

springmvc 通過異常增強返回給客戶端統一格式 springmvc請求引數異常處理

本文轉載於:http://www.cnblogs.com/nosqlcoco/p/5562107.html 

在springmvc開發中,我們經常遇到這樣的問題;邏輯正常執行時返回客戶端指定格式的資料,比如json,但是遇NullPointerException空指標異常,NoSuchMethodException呼叫的方法不存在異常,返回給客戶端的是服務端異常堆疊資訊,導致客戶端不能正常解析資料;這明顯不是我們想要的。

  幸好從spring3.2提供的新註解@ControllerAdvice,從名字上可以看出大體意思是控制器增強。原理是使用AOP對Controller控制器進行增強(前置增強、後置增強、環繞增強,AOP原理請自行查閱);那麼我沒可以自行對控制器的方法進行呼叫前(前置增強)和呼叫後(後置增強)的處理。

  spring提供了@ExceptionHandler異常增強註解。程式如果在執行控制器方法前或執行時丟擲異常,會被@ExceptionHandler註解了的方法處理。

  配置applicationContext-mvc.xml:

<!-- 使用Annotation自動註冊Bean,掃描@Controller和@ControllerAdvice-->
<context:component-scan base-package="com.drskj.apiservice" use-default-filters="false">
    <!-- base-package 如果多個,用“,”分隔 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    <!--控制器增強,使一個Contoller成為全域性的異常處理類,類中用@ExceptionHandler方法註解的方法可以處理所有Controller發生的異常-->
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>

全域性異常處理類:
package com.drskj.apiservice.handler;

import java.io.IOException;

import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.drskj.apiservice.common.utils.ReturnFormat;
/**
 * 異常增強,以JSON的形式返回給客服端
 * 異常增強型別:NullPointerException,RunTimeException,ClassCastException,
         NoSuchMethodException,IOException,IndexOutOfBoundsException
         以及springmvc自定義異常等,如下:
SpringMVC自定義異常對應的status code  
           Exception                       HTTP Status Code  
ConversionNotSupportedException         500 (Internal Server Error)
HttpMessageNotWritableException         500 (Internal Server Error)
HttpMediaTypeNotSupportedException      415 (Unsupported Media Type)
HttpMediaTypeNotAcceptableException     406 (Not Acceptable)
HttpRequestMethodNotSupportedException  405 (Method Not Allowed)
NoSuchRequestHandlingMethodException    404 (Not Found) 
TypeMismatchException                   400 (Bad Request)
HttpMessageNotReadableException         400 (Bad Request)
MissingServletRequestParameterException 400 (Bad Request)
 *
 */
@ControllerAdvice
public class RestExceptionHandler{
    //執行時異常
    @ExceptionHandler(RuntimeException.class)  
    @ResponseBody  
    public String runtimeExceptionHandler(RuntimeException runtimeException) {  

        return ReturnFormat.retParam(1000, null);
    }  

    //空指標異常
    @ExceptionHandler(NullPointerException.class)  
    @ResponseBody  
    public String nullPointerExceptionHandler(NullPointerException ex) {  
        ex.printStackTrace();
        return ReturnFormat.retParam(1001, null);
    }   
    //型別轉換異常
    @ExceptionHandler(ClassCastException.class)  
    @ResponseBody  
    public String classCastExceptionHandler(ClassCastException ex) {  
        ex.printStackTrace();
        return ReturnFormat.retParam(1002, null);  
    }  

    //IO異常
    @ExceptionHandler(IOException.class)  
    @ResponseBody  
    public String iOExceptionHandler(IOException ex) {  
        ex.printStackTrace();
        return ReturnFormat.retParam(1003, null); 
    }  
    //未知方法異常
    @ExceptionHandler(NoSuchMethodException.class)  
    @ResponseBody  
    public String noSuchMethodExceptionHandler(NoSuchMethodException ex) {  
        ex.printStackTrace();
        return ReturnFormat.retParam(1004, null);
    }  

    //陣列越界異常
    @ExceptionHandler(IndexOutOfBoundsException.class)  
    @ResponseBody  
    public String indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {  
        ex.printStackTrace();
        return ReturnFormat.retParam(1005, null);
    }
    //400錯誤
    @ExceptionHandler({HttpMessageNotReadableException.class})
    @ResponseBody
    public String requestNotReadable(HttpMessageNotReadableException ex){
        System.out.println("400..requestNotReadable");
        ex.printStackTrace();
        return ReturnFormat.retParam(400, null);
    }
    //400錯誤
    @ExceptionHandler({TypeMismatchException.class})
    @ResponseBody
    public String requestTypeMismatch(TypeMismatchException ex){
        System.out.println("400..TypeMismatchException");
        ex.printStackTrace();
        return ReturnFormat.retParam(400, null);
    }
    //400錯誤
    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseBody
    public String requestMissingServletRequest(MissingServletRequestParameterException ex){
        System.out.println("400..MissingServletRequest");
        ex.printStackTrace();
        return ReturnFormat.retParam(400, null);
    }
    //405錯誤
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    @ResponseBody
    public String request405(){
        System.out.println("405...");
        return ReturnFormat.retParam(405, null);
    }
    //406錯誤
    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    @ResponseBody
    public String request406(){
        System.out.println("404...");
        return ReturnFormat.retParam(406, null);
    }
    //500錯誤
    @ExceptionHandler({ConversionNotSupportedException.class,HttpMessageNotWritableException.class})
    @ResponseBody
    public String server500(RuntimeException runtimeException){
        System.out.println("500...");
        return ReturnFormat.retParam(406, null);
    }
}

  以上包括了常見的服務端異常型別,@ResponseBody表示以json格式返回客戶端資料。我們也可以自定義異常類(這裡我把它叫做MyException)並且繼承RunTimeException,並且在全域性異常處理類新增一個方法來處理異常,使用@ExceptionHandler(MyException.class)註解在方法上實現自定義異常增強。

  格式化response資料類ReturnFormat:

package com.drskj.apiservice.common.utils;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.JSON;import com.google.common.collect.Maps;
//格式化返回客戶端資料格式(json)
public class ReturnFormat {
    private static Map<String,String>messageMap = Maps.newHashMap();
    //初始化狀態碼與文字說明
    static {
        messageMap.put("0", "");

        messageMap.put("400", "Bad Request!");
        messageMap.put("401", "NotAuthorization");
        messageMap.put("405", "Method Not Allowed");
        messageMap.put("406", "Not Acceptable");
        messageMap.put("500", "Internal Server Error");

        messageMap.put("1000", "[伺服器]執行時異常");
        messageMap.put("1001", "[伺服器]空值異常");
        messageMap.put("1002", "[伺服器]資料型別轉換異常");
        messageMap.put("1003", "[伺服器]IO異常");
        messageMap.put("1004", "[伺服器]未知方法異常");
        messageMap.put("1005", "[伺服器]陣列越界異常");
        messageMap.put("1006", "[伺服器]網路異常");

        messageMap.put("1010", "使用者未註冊");
        messageMap.put("1011", "使用者已註冊");
        messageMap.put("1012", "使用者名稱或密碼錯誤");
        messageMap.put("1013", "使用者帳號凍結");
        messageMap.put("1014", "使用者資訊編輯失敗");
        messageMap.put("1015", "使用者資訊失效,請重新獲取");

        messageMap.put("1020", "驗證碼傳送失敗");
        messageMap.put("1021", "驗證碼失效");
        messageMap.put("1022", "驗證碼錯誤");
        messageMap.put("1023", "驗證碼不可用");
        messageMap.put("1029", "簡訊平臺異常");

        messageMap.put("1030", "周邊無店鋪");
        messageMap.put("1031", "店鋪新增失敗");
        messageMap.put("1032", "編輯店鋪資訊失敗");
        messageMap.put("1033", "每個使用者只能新增一個商鋪");
        messageMap.put("1034", "店鋪不存在");

        messageMap.put("1040", "無瀏覽商品");
        messageMap.put("1041", "新增失敗,商品種類超出上限");
        messageMap.put("1042", "商品不存在");
        messageMap.put("1043", "商品刪除失敗");

        messageMap.put("2010", "缺少引數或值為空");
        
        messageMap.put("2029", "引數不合法");
        messageMap.put("2020", "無效的Token");
        messageMap.put("2021", "無操作許可權");
        messageMap.put("2022", "RSA解密失敗,密文資料已損壞");
        messageMap.put("2023", "請重新登入");
    }
    public static String retParam(int status,Object data) {
        OutputJson json = new OutputJson(status, messageMap.get(String.valueOf(status)), data);
        return json.toString();
    }
}

返回格式實體類OutPutJson;這裡用到了知名的fastjson將物件轉json:

package com.drskj.apiservice.common.utils;


import java.io.Serializable;


import com.alibaba.fastjson.JSON;


public class OutputJson implements Serializable{


    /**
     * 返回客戶端統一格式,包括狀態碼,提示資訊,以及業務資料
     */
    private static final long serialVersionUID = 1L;
    //狀態碼
    private int status;
    //必要的提示資訊
    private String message;
    //業務資料
    private Object data;


    public OutputJson(int status,String message,Object data){
        this.status = status;
        this.message = message;
        this.data = data;
    }
    public int getStatus() {
        return status;
    }
    public void setStatus(int status) {
        this.status = status;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public String toString(){
        if(null == this.data){
            this.setData(new Object());
        }
        return JSON.toJSONString(this);
    }
}

例項:CodeController繼承自BaseController,有一個sendMessage方法呼叫Service層傳送簡訊驗證碼;

  1.如果客戶端請求方式為非POST,否則丟擲HttpMediaTypeNotSupportedException異常;

  2.如果username、forType或userType沒傳,則丟擲MissingServletRequestParameterException異常;

  3.如果springmvc接收無法進行型別轉換的欄位,會報TypeMismatchException異常;

  .....

  大部分的請求異常,springmvc已經為我們定義好了,為我們開發restful應用提高了測試效率,方便排查問題出在哪一環節。

@RestController
@RequestMapping("/api/v1/code")
public class CodeController extends BaseController {
    @Autowired
    private CodeService codeService;
    
    /**
     * 傳送簡訊
     * @param username 使用者名稱
     * @param type register/backpwd
     * @return
     * status: 0 2010 2029 1011 1010 1006 1020 
     */
    @RequestMapping(value="/sendMessage",method=RequestMethod.POST,produces="application/json")
    public String sendMessage(@RequestParam(value="username",required=true)String username,
            @RequestParam(value="forType",required=true)String forType,
            @RequestParam(value="userType",required=true)String userType){
        if(null == username || "".equals(username)){
            return retContent(2010, null);
        }
        if(!"user".equals(userType) && !"merchant".equals(userType)){
            return retContent(2029, null);
        }
        if(!"register".equals(forType) && !"backpwd".equals(forType)){
            return retContent(2029, null);
        }
        return codeService.sendMessage(username, forType, userType);
    }
}


public abstract class BaseController {
    protected String retContent(int status,Object data) {
        return ReturnFormat.retParam(status, data);
    }
}


通過spring ControllerAdvice對各種異常進行攔截處理,統一格式返回給客戶端。

接下來我們更精細的講,通過@ExceptionHandler攔截異常,提示引數客戶端哪些引數沒有傳或引數資料型別不一致,方便客戶端服務端聯調測試。

簡述一下上一篇攔截異常主要流程:

  1.自定義一個類RestExceptionHandler,並使用@ControllerAdvice註解,表示這個類是控制器增強;

  2.在RestExceptionHandler新建一個方法,並使用@ExceptionHandler({Exception.clss})註解在方法上,表示這個方法處理異常資訊。

  3.在springMvc.xml裡配置


<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />

 @ExceptionHandler註解允許我們指定異常型別進行攔截處理,也可以對自定義異常攔截。

  那麼我們來看看機springmvc對於http請求的異常型別。


Exception Type

HTTP Status Code 

ConversionNotSupportedException

500 (Internal Server Error) 

HttpMediaTypeNotAcceptableException

 

406 (Not Acceptable)  

HttpMediaTypeNotSupportedException

 

415 (Unsupported Media Type) 

HttpMessageNotReadableException

 

400 (Bad Request) 

HttpMessageNotWritableException

 

 500 (Internal Server Error) 

HttpRequestMethodNotSupportedException

 

405 (Method Not Allowed) 

MissingServletRequestParameterException

400 (Bad Request)  

 

NoSuchRequestHandlingMethodException

 

404 (Not Found)  

 

TypeMismatchException

 

400 (Bad Request)

  springmvc內部已經為我們定義好了http請求常見的異常型別,我們只需要使用@ExceptionHandler({MissingServletRequestParameterException.class})註解在方法上,方法引數型別就是我們指定的異常型別,就能獲取到缺少引數異常時的異常物件。

//引數型別不匹配
//getPropertyName()獲取資料型別不匹配引數名稱
//getRequiredType()實際要求客戶端傳遞的資料型別
@ExceptionHandler({TypeMismatchException.class})
@ResponseBody
public String requestTypeMismatch(TypeMismatchException ex){
    ex.printStackTrace();
    return outputJson(-400, "引數型別不匹配,引數" + ex.getPropertyName() + "型別應該為" + ex.getRequiredType());
}
//缺少引數異常
//getParameterName() 缺少的引數名稱
@ExceptionHandler({MissingServletRequestParameterException.class})
@ResponseBody
public String requestMissingServletRequest(MissingServletRequestParameterException ex){
    ex.printStackTrace();
    return outputJson(-400, "缺少必要引數,引數名稱為" + ex.getParameterName());
}


 這樣不管是引數異常,還是資料型別異常,還是請求方法異常,都能做到精細的處理,精確到某個方法的引數和資料型別,給客戶端提示更有意義的資訊。

本文轉載於:http://www.cnblogs.com/nosqlcoco/p/5562107.html