對於日常的開發過程中出現的異常,我把它分為兩種,
一種是需要給前端返回的異常,這種異常通常有入參格式、字段缺少、以及相關的業務異常,需要明確的告訴前端出現了什么問題,前端才好處理,
而另一種異常例如空指針、連接超時、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(); } }
到此就完成了。