應用程序在運行過程中,會有大量需要處理的異常。在頁面解析的一個工程中,會存在多個service類同時出現頁面解析異常和解析結果入庫異常,而這就表示在程序中需要一個機制,去統一處理這些異常,提供統一的異常處理。因爲我設計這個結構的主要目的是爲了簡化代碼。
在探尋spring的異常處理機制的時候,主要有三種方式來統一處理異常。三種方式都是使用的@ExceptionHandler註解。
@ExceptionHandler註解解釋:
當一個Controller中有方法加了@ExceptionHandler之後,這個Controller其他方法中沒有捕獲的異常就會以參數的形式傳入加了@ExceptionHandler註解的那個方法中。
三種方式都需要首先爲自己的系統設計一個自定義的異常類,通過它來傳遞狀態碼,以及一些其他參數信息。
public class ProcessException extends RuntimeException {
private static final long serialVersionUID = 1L;
// 任務明細ID
protected String rwmxid;
public ProcessException(String rwmxid) {
this.rwmxid = rwmxid;
}
public String getRwmxid() {
return rwmxid;
}
public void setRwmxid(String rwmxid) {
this.rwmxid = rwmxid;
}
}
第一種思路,設計一個基類。類中使用@ExceptionHandler註解 表明要做異常處理的方法
/**
* Created by liuruijie.
* 處理異常的類,需要處理異常的Controller直接繼承這個類
*/
public class BaseController {
/**
* 處理Controller拋出的異常
* @param e 異常實例
* @return Controller層的返回值
*/
@ExceptionHandler
@ResponseBody
public Object expHandler(Exception e){
if(e instanceof SystemException){
SystemException ex= (SystemException) e;
return WebResult.buildResult().status(ex.getCode())
.msg(ex.getMessage());
}else{
e.printStackTrace();
return WebResult.buildResult().status(Config.FAIL)
.msg("系統錯誤");
}
}
}
這種方式的缺點爲:之後所有需要異常處理的Controller都繼承這個類,從而獲取到異常處理的方法。
雖然這種方式可以解決問題,但是極其不靈活,因爲動用了繼承機制就只爲獲取一個默認的方法,這顯然是不好的。
第二種方式,將這個基類變爲接口,提供此方法的默認實現(也就是接口中的default方法,java8開始支持接口方法的默認實現)
/**
* Created by liuruijie.
* 接口形式的異常處理
*/
public interface DataExceptionSolver {
@ExceptionHandler
@ResponseBody
default Object exceptionHandler(Exception e){
try {
throw e;
} catch (SystemException systemException) {
systemException.printStackTrace();
return WebResult.buildResult().status(systemException.getCode())
.msg(systemException.getMessage());
} catch (Exception e1){
e1.printStackTrace();
return WebResult.buildResult().status(Config.FAIL)
.msg("系統錯誤");
}
}
}
這種方式雖然沒有佔用繼承,但是也不是很優雅,因爲幾乎所有的Controller都需要進行異常處理,於是我每個Controller都需要去寫implement DataExceptionSolver,這顯然不是我真正想要的。況且這種方式依賴java8纔有的語法,這是一個很大的侷限。
第三種方式,使用加強Controller做全局異常處理。
所謂加強Controller就是@ControllerAdvice註解,有這個註解的類中的方法的某些註解會應用到所有的Controller裏,其中就包括@ExceptionHandler註解。
於是可以寫一個全局的異常處理類:
/**
* @ClassName ExceptionHandle
* @Description 統一處理控制層的異常
* @date 2020年3月14日 上午9:55:41
* @version 1.0
*/
@ControllerAdvice
public class ExceptionHandle {
private final Logger log = LoggerFactory.getLogger(getClass());
@ExceptionHandler(ParseException.class)
public void parseException(ParseException ex) {
ex.printStackTrace();
log.error("parseException ",ExceptionUtil.getMessage(ex));
Map<String,Object> paraMap = new HashMap<String,Object>();
paraMap.put("rwmxid", ex.getRwmxid());
CommonUtil.updateDataLog(paraMap);
}
@ExceptionHandler(ProcessException.class)
public void processException(ProcessException ex) {
ex.printStackTrace();
log.error("ProcessException ",ExceptionUtil.getMessage(ex));
Map<String,Object> paraMap = new HashMap<String,Object>();
paraMap.put("rwmxid", ex.getRwmxid());
CommonUtil.updateDataLog(paraMap);
}
@ExceptionHandler(Exception.class)
@ResponseBody
public TransEntity<?> error(Exception ex) {
ex.printStackTrace();
log.error("Exception ",ExceptionUtil.getMessage(ex));
return TransEntity.error();
}
}
如此,我們現在的Controller中的方法就可以很簡潔了:
/**
* Created by liuruijie on 2016/12/28.
* 賬號
*/
@RestController
@RequestMapping("passport")
public class PassportController {
PassportService passportService;
@RequestMapping("login")
public Object doLogin(HttpSession session, String username, String password){
User user = passportService.doLogin(username, password);
session.setAttribute("user", user);
return WebResult.buildResult().redirectUrl("/student/index");
}
}
在passprotService的doLogin方法中,可能會拋出用戶名或密碼錯誤等異常,然後就會交由ExceptionHandle 去處理,直接返回異常信息給前端,然後前端也不需要關心是否返回了異常,因爲這些都已經定義好了。
如果我們自定義了異常,也可以在指定拋出我們自定義的異常,然後在全局異常處理類中進行處理, @ExceptionHandler(ParseException.class) 表明,會處理拋出的 ParseException 異常。