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

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

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

    /**
     * 方法定義聲明式異常,明確方法調用者需要手動處理異常
     *
     * @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...來捕獲處理異常依啰,如果大量的代碼需要手動去處理異常乎串,那么代碼就先的很臃腫,代碼冗余量也大增速警,這樣的代碼不僅閱讀不賞心悅目叹誉,也顯得我們的技術不咋滴啊。

SpringMVC提供的解決方案

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

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

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

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

Spring MVC之@ControllerAdvice詳解

異常的判斷和拋出

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

說到一行代碼就拋出異常淮阐,各位是不是第一個就想到Assert呢;是的叮阅,Assert的內部實現很簡單,就是判斷枝嘶,不符合條件就拋出異常帘饶,但是也有相當大的局限性哑诊,Assert拋出的異常全部都是IllegalArgumentException異常群扶,這個跟我們的需求不太符合,所以我們自己設計出一個類似Assert的類镀裤,但是可以手動返回自定義的異常以及異常編碼和異常信息

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

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

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

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

  • 首先定義出自定義的Assert來實現我們的一行代碼拋出異常,該接口中定義了創(chuàng)建異常的犯法担猛,并且所以異常對于非空的判斷幕垦,后面我們自定義的異常都需要實現該接口,并且提供自定義異常實例化的方法傅联。
    /**
     * 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)建異常的定義方法饵较,并且默認提供異常的判斷方法
     * @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);
                }
            }
    
            // 如果是數組铅匹,則判斷數組元素是否存在
            if (object.getClass().isArray()) {
                int length = ((Object[]) object).length;
                if (length == 0) {
                    throw this.newException(msg);
                }
            }
        }
    
    }

  • BaseException繼承了RuntimeException類锹雏,這里是我們所有類的基類脆侮,也是全局攔截所有自定義異常類的基礎哮肚,主要是通過多態(tài)的方式來實現攔截所有子類的目的雅倒,獲取子類的自定義異常的編碼和信息
    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: BaseException
     * @Package: com.amos.bizexception.exception.bean
     * @author: zhuqb
     * @Description: 自定義業(yè)務處理的基類異常
     * @date: 2019/7/11 0011 上午 10:11
     * @Version: V1.0
     */
    public class BaseException extends RuntimeException {
        private static final long serialVersionUID = 6433183307699823302L;
        /**
         * 異常的編碼和信息
         */
        private IResult result;
        /**
         * 消息定義的參數
         */
        private Object[] args;
    
        /**
         * 基類異常的構造器
         *
         * @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;
                }
            };
        }
    
        /**
         * 基類異常實例化绅络,附帶異常參數
         *
         * @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這個接口狈茉,這個接口只定義了兩個方法,主要是為了我們自定義異常的編碼和信息服務的掸掸,編碼代表各種不同種類的異常氯庆,信息則是異常的具體說明,然后在基類中設定不同的參數的實例化方法
    /**
     * Copyright ? 2018 五月工作室. All rights reserved.
     *
     * @Project: biz-exception
     * @ClassName: IResult
     * @Package: com.amos.bizexception.exception.type
     * @author: zhuqb
     * @Description: 該接口定義了異常信息返回的通用字段
     * <p/>
     * 讓子類去實現該接口猾漫,返回異常的編碼和信息
     * 這樣做的好處是用戶可以自定義實現異常的編碼和信息点晴,系統的擴展性更強
     * @date: 2019/7/11 0011 上午 10:13
     * @Version: V1.0
     */
    public interface IResult {
        /**
         * 異常的編碼
         *
         * @return
         */
        int getCode();
    
        /**
         * 異常的信息
         *
         * @return
         */
        String getMsg();
    }

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

好了,至此我們對于自定義異常的捕捉和拋出就已經全部完結了蛙粘,下面來看看我們業(yè)務端的調用吧

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

是不是發(fā)現很簡單,一行代碼就實現了手動拋出異常出牧,逼格是不是瞬間高了穴肘?!L蚝邸评抚!

不僅僅如此,這個異常的封裝還很容易進行擴展伯复,如果想再次自定義新的異常慨代,只需要繼承BaseException,提供實例化對象,并且提供自定義的Assert來實現自定異常的初始化方法啸如,最后提供對應的異常編碼和信息的枚舉來當做自定義異常實現類即可侍匙。

類的模型圖如下:


image

全局異常的攔截處理

上面我們已經將異常的判斷和拋出這個大頭已經全部處理完畢了,接下來叮雳,得助于Spring提供的@ControllerAdvice@ExceptionHandler想暗,我們可以和方便的實現異常的全局攔截處理

    /**
     * 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這個基類妇汗,就是為了攔截所有的自定義異常.

至此,我們的全局代碼異常處理封裝就已經結束了说莫,具體的代碼可以查看:

biz-exception

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末杨箭,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子储狭,更是在濱河造成了極大的恐慌互婿,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晶密,死亡現場離奇詭異擒悬,居然都是意外死亡模她,警方通過查閱死者的電腦和手機稻艰,發(fā)現死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侈净,“玉大人尊勿,你說我怎么就攤上這事⌒笳欤” “怎么了元扔?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旋膳。 經常有香客問我澎语,道長,這世上最難降的妖魔是什么验懊? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任擅羞,我火速辦了婚禮,結果婚禮上义图,老公的妹妹穿的比我還像新娘减俏。我一直安慰自己,他們只是感情好碱工,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布娃承。 她就那樣靜靜地躺著,像睡著了一般怕篷。 火紅的嫁衣襯著肌膚如雪历筝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天廊谓,我揣著相機與錄音梳猪,去河邊找鬼。 笑死蹂析,一個胖子當著我的面吹牛舔示,可吹牛的內容都是我干的碟婆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼惕稻,長吁一口氣:“原來是場噩夢啊……” “哼竖共!你這毒婦竟也來了?” 一聲冷哼從身側響起俺祠,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤公给,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蜘渣,有當地人在樹林里發(fā)現了一具尸體淌铐,經...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年蔫缸,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拾碌。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖校翔,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情防症,我是刑警寧澤孟辑,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站饲嗽,受9級特大地震影響,放射性物質發(fā)生泄漏喝噪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一指么、第九天 我趴在偏房一處隱蔽的房頂上張望酝惧。 院中可真熱鬧,春花似錦伯诬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悍及。三九已至,卻和暖如春心赶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缨叫。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留销钝,地道東北人琐簇。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像鸽嫂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子据某,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內容

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