5. spring-boot REST 全局異常處理

講點(diǎn)實(shí)用的小技巧屑彻,學(xué)習(xí)前端之后才發(fā)現(xiàn)以前寫(xiě)的代碼真是給前端兒搞了不少事验庙,在此誠(chéng)懇道歉

單頁(yè)應(yīng)用越來(lái)越多以及移動(dòng)化之后,服務(wù)化已經(jīng)是老生常談了社牲,在前文代碼的基礎(chǔ)上做些簡(jiǎn)單的通用模塊的處理粪薛,后端返回結(jié)果的不一致性真的會(huì)給前端帶來(lái)很大的麻煩,故此為止:

  1. 全局異常捕捉及處理
  2. REST FULL基本常見(jiàn)規(guī)范

直接貼核心代碼搏恤。統(tǒng)一返回結(jié)果RestResult

public class RestResult<T> {

    private boolean result;
    private String message;
    private T data;

    private RestResult() {}

    public static <T> RestResult<T> newInstance() {
        return new RestResult<>();
    }

    // ...setter and getter

    @Override
    public String toString() {
        return "RestResult{" +
                "result=" + result +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

result工具類(lèi)RestResultGenerator

/**
 * Created by kaenry on 2016/9/20.
 * RestResultGenerator
 */
public class RestResultGenerator {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestResultGenerator.class);

    /**
     * normal
     * @param success
     * @param data
     * @param message
     * @param <T>
     * @return
     */
    public static <T> RestResult<T> genResult(boolean success, T data, String message) {
        RestResult<T> result = RestResult.newInstance();
        result.setResult(success);
        result.setData(data);
        result.setMessage(message);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("generate rest result:{}", result);
        }
        return result;
    }

    /**
     * success
     * @param data
     * @param <T>
     * @return
     */
    public static <T> RestResult<T> genSuccessResult(T data) {

        return genResult(true, data, null);
    }

    /**
     * error message
     * @param message error message
     * @param <T>
     * @return
     */
    public static <T> RestResult<T> genErrorResult(String message) {

        return genResult(false, null, message);
    }

    /**
     * error
     * @param error error enum
     * @param <T>
     * @return
     */
    public static <T> RestResult<T> genErrorResult(ErrorCode error) {

        return genErrorResult(error.getMessage());
    }

    /**
     * success no message
     * @return
     */
    public static RestResult genSuccessResult() {
        return genSuccessResult(null);
    }
}

統(tǒng)一異常攔截處理:RestExceptionHandler

/**
 * Created by kaenry on 2016/9/20.
 * RestExceptionHandler
 */
@ControllerAdvice(annotations = RestController.class)
public class RestExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionHandler.class);

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private <T> RestResult<T> runtimeExceptionHandler(Exception e) {
        LOGGER.error("---------> huge error!", e);
        return RestResultGenerator.genErrorResult(ErrorCode.SERVER_ERROR);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    private <T> RestResult<T> illegalParamsExceptionHandler(MethodArgumentNotValidException e) {
        LOGGER.error("---------> invalid request!", e);
        return RestResultGenerator.genErrorResult(ErrorCode.ILLEGAL_PARAMS);
    }

}

無(wú)論請(qǐng)求成功或失敗統(tǒng)一返回RestResult违寿,可自由定義,比如加上錯(cuò)誤code或異常的多次處理以及日志啊什么的熟空,代碼都很簡(jiǎn)單藤巢,這里就不詳細(xì)介紹了,返回的結(jié)果類(lèi)似{"result":true,"message":null,"data":{"id":3,"username":"kaenry","password":"jianshu"}}息罗,spring-boot默認(rèn)使用Jackson解析拼裝json掂咒,如需要忽略null,加個(gè)注解即可:@JsonInclude(JsonInclude.Include.NON_NULL),fastjson默認(rèn)開(kāi)啟迈喉。@Valid注解會(huì)驗(yàn)證屬性绍刮,不通過(guò)會(huì)先交給BindingResult,如果沒(méi)有這個(gè)參數(shù)則會(huì)拋出異常MethodArgumentNotValidException弊添,@ExceptionHandler捕捉到異常則會(huì)進(jìn)入illegalParamsExceptionHandler方法返回結(jié)果:

{
  "result": false,
  "message": "request params invalid"
}

測(cè)試RestController修改已有代碼UserRestController:

@RestController
@RequestMapping("/api/users")
public class UserRestController {

    @Autowired
    IUserService userService;

    /**
     * get all user, GET
     * @return
     */
    @RequestMapping(value = "", method = RequestMethod.GET)
    public RestResult<List<User>> all() {
        List<User> all = userService.findAll();
        return RestResultGenerator.genSuccessResult(all);
    }

    /**
     * add single user
     * @param user username, password
     * @return RestResult
     * @throws Exception valid check
     */
    @RequestMapping(value = "", method = RequestMethod.POST)
    public RestResult<User> save(@Valid @RequestBody User user) throws Exception {
        User save = userService.save(user);
        return RestResultGenerator.genSuccessResult(save);
    }

    /**
     * get single user by id, GET /id
     * @param id user id
     * @return RestResult<User>
     * @throws Exception
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public RestResult<User> get(@PathVariable Long id) throws Exception {
        User user = userService.findById(id);
        return RestResultGenerator.genSuccessResult(user);
    }

    /**
     * delete user by id
     * @param id user id
     * @return success
     * @throws Exception
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public RestResult delete(@PathVariable Long id) throws Exception {
        userService.delete(id);
        return RestResultGenerator.genSuccessResult();
    }

    /**
     * update user for all props
     * @param id update user id
     * @param newUser new props
     * @return updated User
     * @throws Exception
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public RestResult<User> updateAll(@PathVariable Long id, @Valid @RequestBody User newUser) throws Exception {
        User user = userService.findById(id);
        // copy all new user props to user except id
        BeanUtils.copyProperties(newUser, user, "id");
        user = userService.save(user);
        return RestResultGenerator.genSuccessResult(user);
    }

    /**
     * update user for some props
     * @param id update user id
     * @param newUser some props
     * @return updated user
     * @throws Exception
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
    public RestResult<User> update(@PathVariable Long id, @Valid @RequestBody User newUser) throws Exception {
        User user = userService.findById(id);
        // copy all new user props to user except null props
        BeanUtils.copyProperties(newUser, user, Utils.getNullPropertyNames(newUser));
        user = userService.save(user);
        return RestResultGenerator.genSuccessResult(user);
    }
}

盡量按照RESTFULL標(biāo)準(zhǔn)書(shū)寫(xiě)的:

  1. GET /api/users录淡,獲取全部
  2. POST /api/users捌木,新增一個(gè)
  3. GET /api/users/:id油坝,獲取單個(gè)
  4. DELETE /api/users/:id,刪除單個(gè)
  5. PUT /api/users/:id刨裆,全量更新
  6. PATCH /api/users/:id澈圈,部分更新

代碼都很簡(jiǎn)單,注意參數(shù)盡量使用Bean帆啃,非特殊情況千萬(wàn)不要使用諸如Map作為接收參數(shù)瞬女,圖一時(shí)痛快,飲恨一生芭恕诽偷;在這里使用@RequestBody的原因是因?yàn)楝F(xiàn)在的前端(因?yàn)橛辛?code>nodejs)大多都會(huì)采用JSON直傳而不是傳統(tǒng)意義上的form了,對(duì)應(yīng)其實(shí)就是http協(xié)議里的請(qǐng)求頭從application/x-www-form-urlencoded換成了application/json疯坤;這里在更新的時(shí)候有個(gè)小技巧报慕,使用BeanUtils復(fù)制需要的屬性,getNullPropertyNames方法是返回對(duì)象里面的為null的屬性压怠,因?yàn)椴恍枰旅吒裕唧w請(qǐng)看代碼。地址還是那個(gè)地址:https://github.com/kaenry/spring-boot-magneto/releases/tag/v1.8.2菌瘫。
畢竟不是真實(shí)項(xiàng)目蜗顽,沒(méi)有寫(xiě)測(cè)試布卡,測(cè)試工具推薦使用PostMan插件,記得先獲取token雇盖,隨便上個(gè)圖

新增一個(gè)用戶(hù)忿等,kaenry/jianshu
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市崔挖,隨后出現(xiàn)的幾起案子这弧,更是在濱河造成了極大的恐慌,老刑警劉巖虚汛,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匾浪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡卷哩,警方通過(guò)查閱死者的電腦和手機(jī)蛋辈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)将谊,“玉大人冷溶,你說(shuō)我怎么就攤上這事∽鹋ǎ” “怎么了逞频?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)栋齿。 經(jīng)常有香客問(wèn)我苗胀,道長(zhǎng),這世上最難降的妖魔是什么瓦堵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任基协,我火速辦了婚禮,結(jié)果婚禮上菇用,老公的妹妹穿的比我還像新娘澜驮。我一直安慰自己,他們只是感情好惋鸥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布杂穷。 她就那樣靜靜地躺著,像睡著了一般卦绣。 火紅的嫁衣襯著肌膚如雪耐量。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天迎卤,我揣著相機(jī)與錄音拴鸵,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛劲藐,可吹牛的內(nèi)容都是我干的八堡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼聘芜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼兄渺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起汰现,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挂谍,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后瞎饲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體口叙,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年嗅战,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妄田。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驮捍,死狀恐怖疟呐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情东且,我是刑警寧澤启具,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站珊泳,受9級(jí)特大地震影響鲁冯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旨椒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一晓褪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧综慎,春花似錦、人聲如沸勤庐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)愉镰。三九已至米罚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丈探,已是汗流浹背录择。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隘竭。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓塘秦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親动看。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尊剔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)菱皆,斷路器须误,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評(píng)論 25 707
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法仇轻,內(nèi)部類(lèi)的語(yǔ)法京痢,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法篷店,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,622評(píng)論 18 399
  • 深情既是死罪历造,又怎怕挫骨揚(yáng)灰。 或許感動(dòng)你了嗎船庇? 就感覺(jué)觸及了心最柔軟的地方吭产,或許我感動(dòng)了
    青草王閱讀 249評(píng)論 0 0
  • —title: “也談《大話(huà)西游3》臣淤,經(jīng)典永不毀”— 說(shuō)明 首先,我得承認(rèn)窃爷,我還沒(méi)有觀(guān)看《大話(huà)西游3》這部影片邑蒋,而...
    何建博桑閱讀 720評(píng)論 1 4