Spring異常統一處理


在J2EE項目的開發中,不管是對底層的數據庫操作過程,還是業務層的處理過程,還是控制層的處理過程,都不可避免會遇到各種可預知的、不可預知的異常需要處理。每個過程都單獨處理異常,系統的代碼耦合度高,工作量大且不好統一,維護的工作量也很大。 
那么,能不能將所有類型的異常處理從各處理過程解耦出來,這樣既保證了相關處理過程的功能較單一,也實現了異常信息的統一處理和維護?答案是肯定的。

Spring對異常統一處理的方式有兩種:

  • 使用 HandlerExceptionResolver 接口,並且 Spring 已經提供默認的實現類 SimpleMappingExceptionResolver。
  • 使用@ExceptionHandler注解實現異常處理; 

一、基於 HandlerExceptionResolver 接口的方式

使用這種方式只需要實現 resolveException 方法,該方法返回一個 ModelAndView 對象,在方法內部對異常的類型進行判斷,然后返回合適的 ModelAndView 對象,如果該方法返回了 null,則 Spring 會繼續尋找其他的實現了 HandlerExceptionResolver 接口的 Bean。換句話說,Spring 會搜索所有注冊在其環境中的實現了 HandlerExceptionResolver 接口的 Bean,逐個執行,直到返回了一個 ModelAndView 對象。

@Component
public class CustomExceptionHandler implements HandlerExceptionResolver {
  
    @Override  
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object object, Exception exception) {
        if(exception instanceof IOException){
            return new ModelAndView("ioexp");  
        }else if(exception instanceof SQLException){
            return new ModelAndView("sqlexp");  
        }  
        return null;  
    }  
}

注意:這個類必須聲明到 Spring 配置文件中,或者使用 @Component 標簽,讓 Spring 管理它。同時 Spring 也提供默認的實現類 SimpleMappingExceptionResolver,需要使用時只需要使用注入到 Spring 配置文件進行聲明即可。自定義實現類與默認的實現類,可同時使用。

二、使用@ExceptionHandler

提示:當一個Controller中有方法加了@ExceptionHandler之后,這個Controller其他方法中沒有捕獲的異常就會以參數的形式傳入加了@ExceptionHandler注解的那個方法中。

統一返回數據結構
定義返回的數據結構

先定義接口返回數據結構,code為0表示操作成功,非0表示異常。其中data只有在處理成功才會返回,其他情況不會返回,或者那些不需要返回數據的接口(更新、刪除…)

{
     "code": 0,
     "message": "SUCCESS",
     "data": {}
}
數據接口字段模型定義
/**
 * 結果統一返回類, 常用於json格式, 處理json對象的類,數據必須要有相關的get和set方法。
 */
public final class ResponseResultDto<T> implements Serializable {

    private static final long serialVersionUID = 8908073950154134675L;

    /**
     * 狀態碼
     **/
    private Integer code;

    /**
     * 狀態描述信息
     **/
    private String message;

    /**
     * 返回數據
     **/
    private T data;

    private ResponseResultDto(Builder<T> builder) {
        this.code = builder.code;
        this.message = builder.message;
        this.data = builder.data;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setData(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public static class Builder<T> {
        private Integer code;
        private String message;
        private T data;

        public Builder setCode(Integer code) {
            this.code = code;
            return this;
        }

        public Builder setMessage(String message) {
            this.message = message;
            return this;
        }

        public Builder setData(T data) {
            this.data = data;
            return this;
        }

        public ResponseResultDto builder() {
            return new ResponseResultDto(this);
        }
    }

}

這樣創建對象:

new ResponseResultDto.Builder<String>().setCode(0).setData("xxxx").setMessage("aaaa").builder();
狀態碼枚舉

項目用到的狀態碼、描述信息要有個文件統一去做枚舉定義,一方面可以實現復用,另一方面如果狀態碼、描述有改動只需要在定義枚舉的地方改動即可。

public enum StatusEnum {
    SUCCESS(0, "成功"),
    FAILURE(-99, "失敗");

    private final Integer code;
    private final String message;

    StatusEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}
自定義異常類
/**
 * 系統業務異常
 */
public class BusinessException extends RuntimeException {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 2332608236621015980L;

    private Integer code;

    public BusinessException() {
        super();
    }

    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public BusinessException(Throwable cause) {
        super(cause);
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public BusinessException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

}    
異常統一處理方式
設計基類

所有需要異常處理的Controller都繼承這個類,從而獲取到異常處理的方法。雖然這種方式可以解決問題,但是極其不靈活,因為動用了繼承機制就只為獲取一個默認的方法,這顯然是不好的。

public class BaseController {
    /**
     * 處理Controller拋出的異常
     * @param e 異常實例
     * @return Controller層的返回值
     */
    @ExceptionHandler
    @ResponseBody
    public Object expHandler(Exception e){
    
if(e instanceof BusinessException){ BusinessException ex= (BusinessException) e; return new ResponseResultDto.Builder<String>().setCode(ex.getCode()).setMessage(ex.getMessage()).builder(); }else{ e.printStackTrace(); return new ResponseResultDto.Builder<String>().setCode(StatusEnum.FAILURE.getCode()).setMessage(e.getMessage()).builder();
} } }
聲明一個default接口(java8開始支持接口方法的默認實現)
public interface DataExceptionSolver {

    @ExceptionHandler
    @ResponseBody
    default Object exceptionHandler(Exception e) {
     if (e instanceof BusinessException) { BusinessException ex = (BusinessException) e; return new ResponseResultDto.Builder<String>().setCode(ex.getCode()).setMessage(ex.getMessage()).builder(); } else { e.printStackTrace(); return new ResponseResultDto.Builder<String>().setCode(StatusEnum.FAILURE.getCode()).setMessage(e.getMessage()).builder(); } } }

這種方式雖然沒有占用繼承,但是也不是很優雅,因為幾乎所有的Controller都需要進行異常處理,於是我每個Controller都需要去寫implement DataExceptionSolver,這顯然不是我真正想要的。況且這種方式依賴java8才有的語法,這是一個很大的局限。

使用加強Controller做全局異常處理。

所謂加強Controller就是@ControllerAdvice注解,有這個注解的類中的方法的某些注解會應用到所有的Controller里,其中就包括@ExceptionHandler注解。

於是可以寫一個全局的異常處理類:

@ControllerAdvice
public class GlobalExceptionHandler {

    //處理自定義的異常
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public Object customHandler(BusinessException e) {
        e.printStackTrace();
     return new ResponseResultDto.Builder<String>().setCode(e.getCode()).setMessage(e.getMessage()).builder(); } //其他未處理的異常 @ExceptionHandler(Exception.class) @ResponseBody public Object exceptionHandler(Exception e) { e.printStackTrace();
     return new ResponseResultDto.Builder<String>().setCode(StatusEnum.FAILURE.getCode()).setMessage(e.getMessage()).builder(); } }

這個類中只處理了兩個異常,但是已經滿足了大部分需要,如果還有需要特殊處理的地方,可以再加上處理的方法就行了,推薦使用這種處理方式。

 


免責聲明!

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



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