SpringBoot:如何優(yōu)雅地進行參數(shù)傳遞酝惧、響應數(shù)據(jù)封裝、異常處理伯诬?

在項目開發(fā)中晚唇,接口與接口之間、前后端之間的數(shù)據(jù)傳輸都使用 JSON 格式盗似。

1 fastjson使用

阿里巴巴的 fastjson是目前應用最廣泛的JSON解析框架哩陕。本文也將使用fastjson。

1.1 引入依賴

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.35</version>
</dependency>

2 統(tǒng)一封裝返回數(shù)據(jù)

在web項目中赫舒,接口返回數(shù)據(jù)一般要包含狀態(tài)碼悍及、信息、數(shù)據(jù)等接癌,例如下面的接口示例:

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author guozhengMu
 * @version 1.0
 * @date 2019/8/21 14:55
 * @description
 * @modify
 */
@RestController
@RequestMapping(value = "/test", method = RequestMethod.GET)
public class TestController {
    @RequestMapping("/json")
    public JSONObject test() {
        JSONObject result = new JSONObject();
        try {
            // 業(yè)務邏輯代碼
            result.put("code", 0);
            result.put("msg", "操作成功心赶!");
            result.put("data", "測試數(shù)據(jù)");
        } catch (Exception e) {
            result.put("code", 500);
            result.put("msg", "系統(tǒng)異常,請聯(lián)系管理員缺猛!");
        }
        return result;
    }
}

這樣的話缨叫,每個接口都這樣處理,非常麻煩枯夜,需要一種更優(yōu)雅的實現(xiàn)方式

2.1 定義統(tǒng)一的JSON結(jié)構(gòu)

統(tǒng)一的 JSON 結(jié)構(gòu)中屬性包括數(shù)據(jù)弯汰、狀態(tài)碼、提示信息湖雹,其他項可以自己根據(jù)需要添加咏闪。一般來說,應該有默認的返回結(jié)構(gòu)摔吏,也應該有用戶指定的返回結(jié)構(gòu)鸽嫂。由于返回數(shù)據(jù)類型無法確定纵装,需要使用泛型,代碼如下:

public class ResponseInfo<T> {
    /**
     * 狀態(tài)碼
     */
    protected String code;
    /**
     * 響應信息
     */
    protected String msg;
    /**
     * 返回數(shù)據(jù)
     */
    private T data;

    /**
     * 若沒有數(shù)據(jù)返回据某,默認狀態(tài)碼為 0橡娄,提示信息為“操作成功!”
     */
    public ResponseInfo() {
        this.code = 0;
        this.msg = "操作成功癣籽!";
    }

    /**
     * 若沒有數(shù)據(jù)返回挽唉,可以人為指定狀態(tài)碼和提示信息
     * @param code
     * @param msg
     */
    public ResponseInfo(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * 有數(shù)據(jù)返回時,狀態(tài)碼為 0筷狼,默認提示信息為“操作成功粱腻!”
     * @param data
     */
    public ResponseInfo(T data) {
        this.data = data;
        this.code = 0;
        this.msg = "操作成功荣挨!";
    }

    /**
     * 有數(shù)據(jù)返回,狀態(tài)碼為 0,人為指定提示信息
     * @param data
     * @param msg
     */
    public ResponseInfo(T data, String msg) {
        this.data = data;
        this.code = 0;
        this.msg = msg;
    }
    // 省略 get 和 set 方法
}

2.2 使用統(tǒng)一的JSON結(jié)構(gòu)

我們封裝了統(tǒng)一的返回數(shù)據(jù)結(jié)構(gòu)后嗅虏,在接口中就可以直接使用了议慰。如下:

import com.example.demo.model.ResponseInfo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author guozhengMu
 * @version 1.0
 * @date 2019/8/21 14:55
 * @description
 * @modify
 */
@RestController
@RequestMapping(value = "/test", method = RequestMethod.GET)
public class TestController {
    @RequestMapping("/json")
    public ResponseInfo test() {
        try {
            // 模擬異常業(yè)務代碼
            int num = 1 / 0;
            return new ResponseInfo("測試數(shù)據(jù)");
        } catch (Exception e) {
            return new ResponseInfo(500, "系統(tǒng)異常仇轻,請聯(lián)系管理員水由!");
        }
    }
}

如上,接口的返回數(shù)據(jù)處理便優(yōu)雅了許多竖独。針對上面接口做個測試裤唠,啟動項目,通過瀏覽器訪問:localhost:8096/test/json莹痢,得到響應結(jié)果:

{"code":500,"msg":"系統(tǒng)異常巧骚,請聯(lián)系管理員!","data":null}

3 全局異常處理

3.1 系統(tǒng)定義異常處理

新建一個 ExceptionHandlerAdvice 全局異常處理類格二,然后加上 @RestControllerAdvice 注解即可攔截項目中拋出的異常劈彪,如下代碼中包含了幾個異常處理,如參數(shù)格式異常顶猜、參數(shù)缺失沧奴、系統(tǒng)異常等,見下例:

@RestControllerAdvice
@Slf4j
public class ExceptionHandlerAdvice {

    // 參數(shù)格式異常處理
    @ExceptionHandler({IllegalArgumentException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseInfo badRequestException(IllegalArgumentException exception) {
        log.error("參數(shù)格式不合法:" + e.getMessage());
        return new ResponseInfo(HttpStatus.BAD_REQUEST.value() + "", "參數(shù)格式不符长窄!");
    }

    // 權(quán)限不足異常處理
    @ExceptionHandler({AccessDeniedException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ResponseInfo badRequestException(AccessDeniedException exception) {
        return new ResponseInfo(HttpStatus.FORBIDDEN.value() + "", exception.getMessage());
    }

    // 參數(shù)缺失異常處理
    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseInfo badRequestException(Exception exception) {
        return new ResponseInfo(HttpStatus.BAD_REQUEST.value() + "", "缺少必填參數(shù)滔吠!");
    }

    // 空指針異常
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo handleTypeMismatchException(NullPointerException ex) {
        log.error("空指針異常,{}", ex.getMessage());
        return new JsonResult("500", "空指針異常");
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public JsonResult handleUnexpectedServer(Exception ex) {
        log.error("系統(tǒng)異常:", ex);
        return new JsonResult("500", "系統(tǒng)發(fā)生異常挠日,請聯(lián)系管理員");
    }

    // 系統(tǒng)異常處理
    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo exception(Throwable throwable) {
        log.error("系統(tǒng)異常", throwable);
        return new ResponseInfo(HttpStatus.INTERNAL_SERVER_ERROR.value() + "系統(tǒng)異常疮绷,請聯(lián)系管理員!");
    }
}
  • @RestControllerAdvice 注解包含了 @Component 注解嚣潜,說明在 Spring Boot 啟動時冬骚,也會把該類作為組件交給 Spring 來管理。
  • @RestControllerAdvice 注解包含了 @ResponseBody 注解,為了異常處理完之后給調(diào)用方輸出一個 JSON 格式的封裝數(shù)據(jù)只冻。
  • @RestControllerAdvice 注解還有個 basePackages 屬性庇麦,該屬性用來攔截哪個包中的異常信息,一般我們不指定這個屬性喜德,我們攔截項目工程中的所有異常山橄。
  • 在方法上通過 @ExceptionHandler 注解來指定具體的異常,然后在方法中處理該異常信息舍悯,最后將結(jié)果通過統(tǒng)一的 JSON 結(jié)構(gòu)體返回給調(diào)用者航棱。
  • 但在項目中,我們一般都會比較詳細地去攔截一些常見異常萌衬,攔截 Exception 雖然可以一勞永逸丧诺,但是不利于我們?nèi)ヅ挪榛蛘叨ㄎ粏栴}。實際項目中奄薇,可以把攔截 Exception 異常寫在 GlobalExceptionHandler 最下面,如果都沒有找到抗愁,最后再攔截一下 Exception 異常馁蒂,保證輸出信息友好。

下面我們通過一個接口來進行測試:

@RestController
@RequestMapping(value = "/test", method = RequestMethod.POST)
public class TestController {
    @RequestMapping("/json")
    public ResponseInfo test(@RequestParam String userName, @RequestParam String password) {
        try {
            String data = "登錄用戶:" + userName + "蜘腌,密碼:" + password;
            return new ResponseInfo("0", "操作成功沫屡!", data);
        } catch (Exception e) {
            return new ResponseInfo("500", "系統(tǒng)異常,請聯(lián)系管理員撮珠!");
        }
    }
}

接口調(diào)用沮脖,password這項故意空缺:

image

3.2 自定義異常攔截

在實際項目中,除了攔截一些系統(tǒng)異常外芯急,在某些業(yè)務上勺届,我們需要自定義一些業(yè)務異常,要處理一個服務的調(diào)用時娶耍,那么可能會調(diào)用失敗或者調(diào)用超時等等免姿,此時我們需要自定義一個異常,當調(diào)用失敗時拋出該異常榕酒,讓 ExceptionHandlerAdvice 去捕獲胚膊。

3.2.1 定義異常信息

由于在業(yè)務中,有很多異常想鹰,上面的系統(tǒng)定義異常遠遠不能覆蓋紊婉,為了方便項目異常信息管理,我們一般會定義一個異常信息枚舉類辑舷。比如:

public enum BusinessMsgEnum {
    /**
     * 參數(shù)異常
     */
    PARMETER_EXCEPTION("101", "參數(shù)異常!"),
    /**
     * 等待超時
     */
    SERVICE_TIME_OUT("102", "服務超時喻犁!"),
    /**
     * 參數(shù)過大
     */
    PARMETER_BIG_EXCEPTION("903", "內(nèi)容不能超過200字,請重試!"),
    /**
     * 數(shù)據(jù)庫操作失敗
     */
    DATABASE_EXCEPTION("509", "數(shù)據(jù)庫操作異常,請聯(lián)系管理員株汉!"),
    /**
     * 500 : 一勞永逸的提示也可以在這定義
     */
    UNEXPECTED_EXCEPTION("500", "系統(tǒng)發(fā)生異常筐乳,請聯(lián)系管理員!");
    // 還可以定義更多的業(yè)務異常

    /**
     * 消息碼
     */
    private String code;
    /**
     * 消息內(nèi)容
     */
    private String msg;

    private BusinessMsgEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    // set get方法
}

3.2.2 攔截自定義異常

我們可以定義一個業(yè)務異常乔妈,當出現(xiàn)業(yè)務異常時蝙云,我們就拋出這個自定義的業(yè)務異常即可。比如我們定義一個 BusinessErrorException 異常路召,如下:

public class BusinessErrorException extends RuntimeException {

    private static final long serialVersionUID = -7480022450501760611L;

    /**
     * 異常碼
     */
    private String code;
    /**
     * 異常提示信息
     */
    private String msg;

    public BusinessErrorException(BusinessMsgEnum businessMsgEnum) {
        this.code = businessMsgEnum.code();
        this.msg = businessMsgEnum.msg();
    }
    // get set方法
}

在構(gòu)造方法中勃刨,傳入我們上面自定義的異常枚舉類,在項目中股淡,如果有新的異常信息需要添加身隐,我們直接在枚舉類中添加即可,很方便唯灵,做到統(tǒng)一維護贾铝,在攔截該異常時獲取即可。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 攔截業(yè)務異常埠帕,返回業(yè)務異常信息
     * @param ex
     * @return
     */
    @ExceptionHandler(BusinessErrorException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo handleBusinessError(BusinessErrorException ex) {
        String code = ex.getCode();
        String message = ex.getMessage();
        return new ResponseInfo(code, message);
    }
}

在接口層垢揩,模擬異常場景,如下:

@RestController
@RequestMapping("/test")
public class ExceptionController {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class);

    @GetMapping("/exception")
    public ResponseInfo testException() {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new BusinessErrorException(BusinessMsgEnum.UNEXPECTED_EXCEPTION);
        }
        return new ResponseInfo();
    }
}

啟動項目敛瓷,請求該接口:

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叁巨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子呐籽,更是在濱河造成了極大的恐慌锋勺,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狡蝶,死亡現(xiàn)場離奇詭異庶橱,居然都是意外死亡,警方通過查閱死者的電腦和手機贪惹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門悬包,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人馍乙,你說我怎么就攤上這事布近。” “怎么了丝格?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵撑瞧,是天一觀的道長。 經(jīng)常有香客問我显蝌,道長预伺,這世上最難降的妖魔是什么订咸? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮酬诀,結(jié)果婚禮上脏嚷,老公的妹妹穿的比我還像新娘。我一直安慰自己瞒御,他們只是感情好父叙,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肴裙,像睡著了一般趾唱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜻懦,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天甜癞,我揣著相機與錄音,去河邊找鬼宛乃。 笑死悠咱,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的征炼。 我是一名探鬼主播析既,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柒室!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逗宜,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雄右,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纺讲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體擂仍,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年熬甚,在試婚紗的時候發(fā)現(xiàn)自己被綠了逢渔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡乡括,死狀恐怖肃廓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诲泌,我是刑警寧澤盲赊,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站敷扫,受9級特大地震影響哀蘑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一绘迁、第九天 我趴在偏房一處隱蔽的房頂上張望合溺。 院中可真熱鬧,春花似錦缀台、人聲如沸棠赛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恭朗。三九已至,卻和暖如春依疼,著一層夾襖步出監(jiān)牢的瞬間痰腮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工律罢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留膀值,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓误辑,卻偏偏與公主長得像沧踏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子巾钉,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容