全局代碼異常處理封裝——讓代碼逼格更高點

我們開發(fā)過程中,不管是通用代碼的開發(fā)還是業(yè)務(wù)代碼的編寫,都涉及到異常的處理,如果不對異常進行封裝處理的話,會導(dǎo)致我們的代碼十分的不雅觀,比較low,所以一個好的全局異常處理的封裝不僅僅能加快我們的開發(fā)效率,并且也能讓我們的代碼逼格更高點,何樂而不為呢?

首先我們來看下一般情況下代碼的處理

    /**
     * 方法定義聲明式異常,明確方法調(diào)用者需要手動處理異常
     *
     * @throws Exception
     */
    public void demonstrateExceptionThrows() throws Exception {
        System.out.println(1 / 0);
    }
    
    /************************************************************/
    
    @Autowired
    BizService bizService;

    public String call() {
        try {
            this.bizService.demonstrateExceptionThrows();
        } catch (Exception e) {
            return "error:" + e.getMessage();
        }
        return "ok";
    }

上面的代碼我們使用try...catch...來捕獲處理異常,如果大量的代碼需要手動去處理異常,那么代碼就先的很臃腫,代碼冗余量也大增,這樣的代碼不僅閱讀不賞心悅目,也顯得我們的技術(shù)不咋滴啊。

SpringMVC提供的解決方案

那有沒有一種解決方案,能很優(yōu)雅的實現(xiàn)異常的捕捉,讓我們更專注于業(yè)務(wù)層面的開發(fā)呢?有的,以前實現(xiàn)起來可能比較繁瑣,萬幸的是我們有了Spring這一利器,Spring本身為我們提供了一個解決方案來全局處理異常的方案,讓我們更好地專注于業(yè)務(wù)層面的開發(fā)。

接下來我們來介紹一下今天的主角@ControllerAdvice@ExceptionHandler;

網(wǎng)上關(guān)于@ControllerAdvice@ExceptionHandler的介紹一大堆,我們主要不是為了介紹這兩個注解的實現(xiàn)原理,我們只是簡單接收這兩個注解在我們封裝的全局異常處理的方案中是如何運用的,能實現(xiàn)什么樣的一個效果

具體的原理和使用方法可以參考如下的博文

Spring MVC之@ControllerAdvice詳解

異常的判斷和拋出

既然Spring為我們解決了全局異常的處理,那么我們這里的任務(wù)就是需要設(shè)計一行代碼來捕獲異常,并且向外拋出自定義的異常

說到一行代碼就拋出異常,各位是不是第一個就想到Assert呢;是的,Assert的內(nèi)部實現(xiàn)很簡單,就是判斷,不符合條件就拋出異常,但是也有相當(dāng)大的局限性,Assert拋出的異常全部都是IllegalArgumentException異常,這個跟我們的需求不太符合,所以我們自己設(shè)計出一個類似Assert的類,但是可以手動返回自定義的異常以及異常編碼和異常信息

我們的異常的判斷和拋出需要滿足以下幾個需求:

  • 同意定義自定義異常的創(chuàng)建
  • 異常的編碼能夠很方便的擴展,并且異常的信息能夠自定義
  • 支持多個自定義異常的創(chuàng)建,并且對擴展開放,定義的異常自動進行全局了攔截

好了,有句話說的話 talk is cheap, show me code,下面我們來貼出主要的代碼

使用的SpringBoot和jdk1.8環(huán)境

  • 首先定義出自定義的Assert來實現(xiàn)我們的一行代碼拋出異常,該接口中定義了創(chuàng)建異常的犯法,并且所以異常對于非空的判斷,后面我們自定義的異常都需要實現(xiàn)該接口,并且提供自定義異常實例化的方法。
    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: BizAssert
     * @Package: com.amos.bizexception.exception
     * @author: zhuqb
     * @Description: 自定義的斷言
     * <p/>
     * 仿照Assert的思路來提供異常的處理,讓代碼更優(yōu)美
     * <p/>
     * 接口定義創(chuàng)建異常的定義方法,并且默認(rèn)提供異常的判斷方法
     * @date: 2019/7/11 0011 上午 8:58
     * @Version: V1.0
     */
    public interface BizAssert {
        /**
         * 定義創(chuàng)建異常的方法
         *
         * @param args 異常的信息
         * @return
         */
        BaseException newException(Object... args);
    
        /**
         * 定義創(chuàng)建好友異常信息的異常的方法
         *
         * @param throwable 異常的信息
         * @param args      異常的msg
         * @return
         */
        BaseException newException(Throwable throwable, Object... args);
    
        /**
         * 判斷對象是否是空
         *
         * @param object 帶判斷是否為空的對象
         */
        default void notNullAssert(Object object) {
            if (StringUtils.isEmpty(object)) {
                throw this.newException(object);
            }
        }
    
        /**
         * 判斷對象是否為空
         *
         * @param object 帶判斷是否為空的對象
         * @param msg    異常返回的信息
         */
        default void notNullAssert(Object object, String msg) {
            if (StringUtils.isEmpty(object)) {
                throw this.newException(msg);
            }
        }
    
        /**
         * 判斷對象是否為空(集合沒有元素)
         *
         * @param object 帶判斷是否為空的對象
         * @param msg    異常返回的信息
         */
        default void notEmptyAssert(Object object, String msg) {
            if (StringUtils.isEmpty(object)) {
                throw this.newException(msg);
            }
            // 如果是集合,則判斷集合里面的元素是否存在
            if (object instanceof Collection) {
                int size = ((Collection) object).size();
                if (size == 0) {
                    throw this.newException(msg);
                }
            }
    
            // 如果是數(shù)組,則判斷數(shù)組元素是否存在
            if (object.getClass().isArray()) {
                int length = ((Object[]) object).length;
                if (length == 0) {
                    throw this.newException(msg);
                }
            }
        }
    
    }

  • BaseException繼承了RuntimeException類,這里是我們所有類的基類,也是全局?jǐn)r截所有自定義異常類的基礎(chǔ),主要是通過多態(tài)的方式來實現(xiàn)攔截所有子類的目的,獲取子類的自定義異常的編碼和信息
    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: BaseException
     * @Package: com.amos.bizexception.exception.bean
     * @author: zhuqb
     * @Description: 自定義業(yè)務(wù)處理的基類異常
     * @date: 2019/7/11 0011 上午 10:11
     * @Version: V1.0
     */
    public class BaseException extends RuntimeException {
        private static final long serialVersionUID = 6433183307699823302L;
        /**
         * 異常的編碼和信息
         */
        private IResult result;
        /**
         * 消息定義的參數(shù)
         */
        private Object[] args;
    
        /**
         * 基類異常的構(gòu)造器
         *
         * @param result
         */
        BaseException(IResult result) {
            super(result.getMsg());
            this.result = result;
        }
    
        /**
         * 通過code和msg來實例化基類異常信息
         *
         * @param code
         * @param msg
         */
        BaseException(int code, String msg) {
            super(msg);
            this.result = new IResult() {
                @Override
                public int getCode() {
                    return code;
                }
    
                @Override
                public String getMsg() {
                    return msg;
                }
            };
        }
    
        /**
         * 基類異常實例化,附帶異常參數(shù)
         *
         * @param result
         * @param args
         * @param msg
         */
        BaseException(IResult result, Object[] args, String msg) {
            super(msg);
            this.result = result;
            this.args = args;
        }
    
        /**
         * 基類異常實例化,附帶異常類信息
         *
         * @param result
         * @param args
         * @param msg
         * @param throwable
         */
        BaseException(IResult result, Object[] args, String msg, Throwable throwable) {
            super(msg, throwable);
            this.result = result;
            this.args = args;
        }
    
    
    }

  • 基類中我們定義了IResult這個接口,這個接口只定義了兩個方法,主要是為了我們自定義異常的編碼和信息服務(wù)的,編碼代表各種不同種類的異常,信息則是異常的具體說明,然后在基類中設(shè)定不同的參數(shù)的實例化方法
    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: IResult
     * @Package: com.amos.bizexception.exception.type
     * @author: zhuqb
     * @Description: 該接口定義了異常信息返回的通用字段
     * <p/>
     * 讓子類去實現(xiàn)該接口,返回異常的編碼和信息
     * 這樣做的好處是用戶可以自定義實現(xiàn)異常的編碼和信息,系統(tǒng)的擴展性更強
     * @date: 2019/7/11 0011 上午 10:13
     * @Version: V1.0
     */
    public interface IResult {
        /**
         * 異常的編碼
         *
         * @return
         */
        int getCode();
    
        /**
         * 異常的信息
         *
         * @return
         */
        String getMsg();
    }

  • 基類的相關(guān)方法和屬性我們定義好了,現(xiàn)在我們開始來實現(xiàn)我們的自定義異常,該異常繼承BaseException異常,并且定義構(gòu)造器方便實例化
    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: BizException
     * @Package: com.amos.bizexception.exception.bean
     * @author: zhuqb
     * @Description: 自定義業(yè)務(wù)異常
     * <p/>
     * 異常繼承基類異常,并且實現(xiàn)對應(yīng)的構(gòu)造器來初始化自定義業(yè)務(wù)異常
     * @date: 2019/7/11 0011 上午 10:41
     * @Version: V1.0
     */
    public class BizException extends BaseException {
        private static final long serialVersionUID = -7118967757594184955L;
    
        /**
         * 自定義業(yè)務(wù)異常的構(gòu)造器
         *
         * @param result
         */
        public BizException(IResult result) {
            super(result);
        }
    
        /**
         * 自定義業(yè)務(wù)異常的構(gòu)造器
         *
         * @param code
         * @param msg
         */
        public BizException(int code, String msg) {
            super(code, msg);
        }
    
        /**
         * 自定義業(yè)務(wù)異常的構(gòu)造器
         *
         * @param result
         * @param args
         * @param msg
         */
        public BizException(IResult result, Object[] args, String msg) {
            super(result, args, msg);
        }
    
        /**
         * 自定義業(yè)務(wù)異常的構(gòu)造器
         *
         * @param result
         * @param args
         * @param msg
         * @param throwable
         */
        public BizException(IResult result, Object[] args, String msg, Throwable throwable) {
            super(result, args, msg, throwable);
        }
    }
  • 上面我們已經(jīng)定義好了所有基類的Assert,其實所有基類的Assert就已經(jīng)能實現(xiàn)全部捕獲異常的要求了,但是不方便擴展,我們的需求是支持各種自定義異常拋出各自的異常,并且能夠自定義異常編碼和消息, 這里我們先來讓我們的程序可以支持創(chuàng)建各種不同的自定義異常
    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: BizExceptionAssert
     * @Package: com.amos.bizexception.exception
     * @author: zhuqb
     * @Description: 自定義業(yè)務(wù)異常斷言
     * <p/>
     * 該接口異常異常信息和自定義業(yè)務(wù)斷言接口, 繼承這兩個接口
     * 1. 獲取異常的編碼和信息接口,方便實現(xiàn)類自定義異常的編碼和信息
     * 2. 獲取定義異常的創(chuàng)建方法,并且重寫創(chuàng)建異常實例的方法,返回對應(yīng)的業(yè)務(wù)異常的實例
     * 3. 注意:此處的異常的編碼和信息我們可以暫時不處理,交給子類來自定義異常編碼和信息
     * @date: 2019/7/11 0011 上午 10:48
     * @Version: V1.0
     */
    public interface BizExceptionAssert extends IResult, BizAssert {
        /**
         * 實現(xiàn)創(chuàng)建異常的方法, 返回自定義異常實例對象
         *
         * @param args 異常的信息
         * @return
         */
        @Override
        default BaseException newException(Object... args) {
            String msg = MessageFormat.format(this.getMsg(), args);
            return new BizException(this, args, msg);
        }
    
        /**
         * 實現(xiàn)創(chuàng)建異常的方法, 返回自定義異常實例對象
         *
         * @param throwable 異常的信息
         * @param args      異常的消息
         * @return
         */
        @Override
        default BaseException newException(Throwable throwable, Object... args) {
            String msg = MessageFormat.format(this.getMsg(), args);
            return new BizException(this, args, msg, throwable);
        }
    }
  • 上面的注釋說的沒有實現(xiàn)異常編碼和信息的定義,這里我們優(yōu)化下設(shè)計,同一種自定義異常也支持異常編碼和信息的自定義,說白了,我們這里就是將自定義異常和異常編碼和信息解耦了,這樣方便我們通過定義不同的對象來組合自定義異常和異常編碼和信息,這個其實體現(xiàn)了設(shè)計模式中開閉原則的開原則,那么閉原則我們怎么處理呢?別急,還記的枚舉么,這個可以初步實現(xiàn)
    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: BizExceptionEnum
     * @Package: com.amos.bizexception.exception.type
     * @author: zhuqb
     * @Description: 自定義異常的編碼和消息
     * <p/>
     * 這里沒有實現(xiàn)setter方法,主要是為了不能修改枚舉中的code值,
     * 但是我們自定義了 definedMsg 方法,主要是為了方便用戶自定義異常的信息
     * 默認(rèn)是選擇枚舉的msg信息
     * @date: 2019/7/11 0011 上午 11:15
     * @Version: V1.0
     */
    @AllArgsConstructor
    public enum BizExceptionEnum implements BizExceptionAssert {
        /**
         * 異常編碼 1000
         * 異常信息 對象不能為空
         */
        NOT_NULL(1000, "對象不能為空");
    
        private int code;
        private String msg;
    
        @Override
        public int getCode() {
            return this.code;
        }
    
        @Override
        public String getMsg() {
            return this.msg;
        }
    
        /**
         * 此處可以動態(tài)修改msg的返回值
         * 這么做是為了實現(xiàn)自定義異常信息
         *
         * @param msg
         * @return
         */
        public BizExceptionEnum definedMsg(String msg) {
            this.msg = msg;
            return this;
        }
    }

好了,至此我們對于自定義異常的捕捉和拋出就已經(jīng)全部完結(jié)了,下面來看看我們業(yè)務(wù)端的調(diào)用吧

    @Service
    public class BizService {
    
        void call(Object object) {
            BizExceptionEnum.NOT_NULL.definedMsg("判斷出對象為NULL,手動拋出異常").notNullAssert(object);
            System.out.println("方法執(zhí)行結(jié)束");
        }
    }

是不是發(fā)現(xiàn)很簡單,一行代碼就實現(xiàn)了手動拋出異常,逼格是不是瞬間高了??。?!

不僅僅如此,這個異常的封裝還很容易進行擴展,如果想再次自定義新的異常,只需要繼承BaseException,提供實例化對象,并且提供自定義的Assert來實現(xiàn)自定異常的初始化方法,最后提供對應(yīng)的異常編碼和信息的枚舉來當(dāng)做自定義異常實現(xiàn)類即可。

類的模型圖如下:


image

全局異常的攔截處理

上面我們已經(jīng)將異常的判斷和拋出這個大頭已經(jīng)全部處理完畢了,接下來,得助于Spring提供的@ControllerAdvice@ExceptionHandler,我們可以和方便的實現(xiàn)異常的全局?jǐn)r截處理

    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: GlobalExceptionHandler
     * @Package: com.amos.bizexception.advice
     * @author: zhuqb
     * @Description: 全局異常處理類
     * <p/>
     * 該Handler主要處理常見的web訪問的信息
     * @date: 2019/7/11 0011 上午 11:46
     * @Version: V1.0
     */
    @Slf4j
    @ControllerAdvice(basePackages = {"com.amos.bizexception"})
    @ResponseBody
    public class GlobalExceptionHandler {
        /**
         * 處理自定義異常
         *
         * @param exception
         * @return
         */
        @ExceptionHandler(BaseException.class)
        public Result handleBizException(BaseException exception) {
            log.error("進入自定義異常攔截中...異常信息為:{}", exception.getMessage());
            return ResultWapper.error(exception.getMessage());
        }
    
    }

這里的攔截了BaseException這個基類,就是為了攔截所有的自定義異常.

至此,我們的全局代碼異常處理封裝就已經(jīng)結(jié)束了,具體的代碼可以查看:

biz-exception

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • PS: 本文講得比較細(xì),所以篇幅較長。 閱讀時間:30m~1h。請認(rèn)真讀完,希望你一小時后能對統(tǒng)一異常處理有一個清...
    sprainkle閱讀 19,998評論 21 157
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,629評論 1 32
  • 寫著寫著發(fā)現(xiàn)簡書提醒我文章接近字?jǐn)?shù)極限,建議我換一篇寫了。 建議52:推薦使用String直接量賦值 一般對象都是...
    我沒有三顆心臟閱讀 1,438評論 2 4
  • ??由于 JavaScript 本身是動態(tài)語言,而且多年來一直沒有固定的開發(fā)工具,因此人們普遍認(rèn)為它是一種最難于調(diào)...
    霜天曉閱讀 823評論 0 1
  • 報到,就是你所想的那樣,有點像大學(xué)入學(xué)報到。交完各種證件,做完體檢,就無所事事了。 軍訓(xùn)第一個小半天,沒有大學(xué)那種...
    明半滅閱讀 318評論 2 0

友情鏈接更多精彩內(nèi)容