SpringBoot全局異常統一處理、反參標准化


對於日常的開發過程中出現的異常,我把它分為兩種,

一種是需要給前端返回的異常,這種異常通常有入參格式、字段缺少、以及相關的業務異常,需要明確的告訴前端出現了什么問題,前端才好處理,

而另一種異常例如空指針、連接超時、io異常,這類型的異常不需要前端知曉,統一返回服務器異常即可。

所以我們需要捕獲異常,對異常進行分類,然后再將封裝成固定的格式返回給前端。

首先第一步個自定義一個ExceptionMap,這里其實啥也沒實現就是改了個名字,這樣在代碼的可讀性上能增加不少(我覺得)。

其實不做這一步直接用HashMap也行。我們都知道異常的拋出是冒泡的形式拋出的,現在要做的就是捕獲,獲取異常的內容,ExceptionMap就是用來異常被捕獲后將異常的信息轉化成一個map,后續再進行格式化和返回

/**
 * 自定義HashMap的ExceptionMap controller拋出的異常被處理成ExceptionMap
 * @author xuwang
 * @date 2019年5月29日 15:04:49
 */
public class XcCuisineExceptionMap extends HashMap {
}

 

第二步需要繼承Exception實現一個新的業務Exception類,

這樣做有兩個用處,一個是可以自定義異常的內容,一個是可以和其他異常區分出來

/**
 * @ClassName: XcCuisineBusinessException
 * @ClassNameExplain:
 * @Description:
 * @author xuwang
 * @date 2019年05月31日 10:34:00
 *
 */
public class XcCuisineBusinessException extends Exception {
    private static final long serialVersionUID = 1;
    private int code;
    private String errorMsg;

    public XcCuisineBusinessException(int code, String errorMsg) {
        super(errorMsg);
        this.code = code;
        this.errorMsg = errorMsg;
    }


    public XcCuisineBusinessException(int code, String errorMsg, Throwable throwable) {
        super(errorMsg, throwable);
        this.code = code;
        this.errorMsg = errorMsg;
    }

    public int getCode() {
        return code;
    }

    public String getErrorMsg() {
        return errorMsg;
    }
}

 

第三步實現一個ControllerAdvice,這一步的目的主要就是捕獲全局異常,所有從Controller拋出的異常都能在這捕獲到,

在ExceptionHandler中,根據getClass().getName()區分出業務異常,和服務器異常,將異常變成一個擁有code和message的XcCuisineExceptionMap

這里還額外判斷了一個MethodArgumentNotValidException ,MethodArgumentNotValidException是使用@Valid對入參里字段進行限制后,字段不符合規則出現的異常,這種也屬於業務異常,但是沒法拋出變成XcCuisineBusinessException,就在這判斷了一下。

/**
 * @ClassName: XcCuisineControllerAdvice
 * @ClassNameExplain: 
 * @Description: 
 * @author xuwang
 * @date 2019年05月31日 10:34:00
 *
 */
@ControllerAdvice
public class XcCuisineControllerAdvice {
    static final Logger logger = LoggerFactory.getLogger(XcCuisineControllerAdvice.class);

    /**
     * 全局異常捕捉處理
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Map errorHandler(Exception ex) {
        Map map = new XcCuisineExceptionMap();
        if(ex.getClass().getName().equals(XcCuisineBusinessException.class.getName())){
            XcCuisineBusinessException bex = (XcCuisineBusinessException) ex;
            map.put("code", bex.getCode());
            map.put("msg", bex.getErrorMsg());
        }else if(ex.getClass().getName().equals(MethodArgumentNotValidException.class.getName())){
            MethodArgumentNotValidException mex = (MethodArgumentNotValidException) ex;
            StringBuffer sb = new StringBuffer();
            List<FieldError> errorList = mex.getBindingResult().getFieldErrors();
            for (FieldError error : errorList) {
                sb.append(error.getObjectName());
                sb.append("對象的");
                sb.append(error.getField());
                sb.append("字段");
                sb.append(translationString(error.getDefaultMessage()));
            }
            map.put("code",ExceptionConstants.PARAM_INVALID_CODE);
            map.put("msg", sb.toString());
        }else {
            map.put("code", ExceptionConstants.SERVER_EXCEPTION_CODE);
            map.put("msg", ExceptionConstants.SERVER_EXCEPTION_MSG);
        }
        return map;
    }

    /**
     * @Title: translationString
     * @TitleExplain:
     * @Description: 對字符串進行轉譯 解決報錯中出現json關鍵字符  導致json序列化失敗的問題
     * @param
     * @return java.lang.String
     * @version
     * @author 
     */
    private String translationString(String string){
        String temp = GsonUtil.toJson(string);
        return temp.substring(1, temp.length()-1);
    }


}

 第四步是在創建轉化器,我的這個轉換器其實對入參和反參都進行了轉換,因為在我的項目里入參也是有標准的,不過這里沒寫太多,

在writeInternal中,反參如果是XcCuisineExceptionMap,直接就轉換成JSON字符串返回,這里還在正常返回的內容中添加了code和message,做到了反參的標准化{code:"",msg:"",data:{}}

/**
 * @author xuwang
 * @ClassName: GsonHttpMessageConverter
 * @ClassNameExplain: Gson轉換器
 * @Description: 
 * @date 2019年05月30日 20:13:04
 */
public class GsonHttpMessageConverter extends AbstractHttpMessageConverter {

    private final static String CODE_KEY = "code";

    private final static String MSG_KEY = "msg";

    private final static String DATA_KEY = "data";

    private final static String CONTENT_TYPE_KEY = "Content-Type";

    public GsonHttpMessageConverter() {
        super(new MediaType("application", "json", Charset.forName("utf-8")));
    }

    @Override
    protected boolean supports(Class aClass) {
        //返回true表示支持所有的類
        return true;
    }

    /**
     * 處理請求內容
     * @param aClass
     * @param httpInputMessage
     * @return
     * @throws IOException
     * @throws HttpMessageNotReadableException
     */
    @Override
    protected Object readInternal(Class aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
            //取出requestBody中內容
            String json = StreamUtils.copyToString(
                    httpInputMessage.getBody(), httpInputMessage.getHeaders().getContentType().getCharset());

            //打印入參
            logger.debug(aClass+" request json : \n" + json);

            if(StringUtils.isNotEmpty(json) && json.trim().startsWith("{") && json.trim().endsWith("}")){
                return GsonUtil.json2Bean(json, aClass);
            }else{
                //TODO  throw new RequestFormatException("請求格式不正確");
                return json;
            }
    }

    /**
     * 處理響應內容
     * @param o
     * @param httpOutputMessage
     * @throws IOException
     * @throws HttpMessageNotWritableException
     */
    @Override
    protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        Class oClass = o.getClass();
        String json = "";
        if(oClass.getName().equals(XcCuisineExceptionMap.class.getName())){
            //判斷如果是拋出來的異常直接轉換為json字符串
            json = GsonUtil.toJson(o);
        }else {
            Map<String, Object> result = new LinkedHashMap<>();
            //設置code
            result.put(CODE_KEY, Constant.CORRECT_CODE);
            //設置msg
            result.put(MSG_KEY, Constant.CORRECT_MSG);
            //設置data
            result.put(DATA_KEY, o == null ? "" : o);
            json = GsonUtil.toJson(result);
        };

        logger.debug("response json : \n" + json);

        httpOutputMessage.getHeaders().add(CONTENT_TYPE_KEY, "application/json");
        httpOutputMessage.getBody().write(json.getBytes("UTF-8"));

    }
}

最后是注冊這個轉換器

@EnableWebMvc
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(
            List<HttpMessageConverter<?>> converters) {
        //清理其他轉換器,添加自定義轉換器
        converters.clear();
        converters.add(createGsonHttpMessageConverter());
    }

    @Bean
    public GsonHttpMessageConverter createGsonHttpMessageConverter() {
        //注入自定義轉換器
        return new GsonHttpMessageConverter();
    }
}

到此就完成了。

 


免責聲明!

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



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