python切面异常处理_Spring项目中优雅的异常处理
Spring項(xiàng)目中優(yōu)雅的異常處理
前言
如今的Java Web項(xiàng)目多是以 MVC 模式構(gòu)建的,通常我們都是將 Service 層的異常統(tǒng)一的拋出,包括自定義異常和一些意外出現(xiàn)的異常,以便進(jìn)行事務(wù)回滾,而 Service 的調(diào)用者 Controller 則承擔(dān)著異常處理的責(zé)任,因?yàn)樗桥c Web 前端交互的最后一道防線,如果此時(shí)還不進(jìn)行處理則用戶會在網(wǎng)頁上看到一臉懵逼的
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
at cn.keats.TestAdd.main(TestAdd.java:20)
這樣做有以下幾點(diǎn)壞處:
用戶體驗(yàn)很不友好,可能用戶會吐槽一句:這是什么XX網(wǎng)站。然后不再訪問了
如果這個(gè)用戶是同行,他不僅看到了項(xiàng)目代碼的結(jié)構(gòu),而且看到拋出的是這么低級的索引越界異常,會被人家看不起
用戶看到網(wǎng)站有問題,打電話給客服,客服找到產(chǎn)品,產(chǎn)品叫醒正在熟睡/打游戲的你。你不僅睡不好游戲打不了還得挨批評完事改代碼
哎,真慘。因此一般我們采用的方法會是像這樣:
異常處理
一般的Controller處理
Service代碼如下:
@Service
public class DemoService{
public String respException(String param){
if(StringUtils.isEmpty(param)){
throw new MyException(ExceptionEnum.PARAM_EXCEPTION);
}
int i = 1/0;
return "你看不見我!";
}
}
Controller代碼如下:
@RestController
public class DemoController{
@Autowired
private DemoService demoService;
@PostMapping("respException")
public Result respException(){
try {
return Result.OK(demoService.respException(null));
} catch (MyException e){
return Result.Exception(e, null);
}
catch (Exception e) {
return Result.Error();
}
}
}
如果此時(shí)發(fā)送如下的請求:
http://localhost/respException
服務(wù)器捕捉到自定義的異常 MyException,而返回參數(shù)異常的Json串:
{
"code": 1,
"msg": "參數(shù)異常",
"data": null
}
而當(dāng)我們補(bǔ)上參數(shù):
http://localhost/respException?param=zhangsan
則服務(wù)器捕捉到 by zero 異常,會返回未知錯(cuò)誤到前端頁面
{
"code": -1,
"msg": "未知錯(cuò)誤",
"data": null
}
這樣就會在一定程度上規(guī)避一些問題,例如參數(shù)錯(cuò)誤就可以讓用戶去修改其參數(shù),當(dāng)然這一般需要前端同學(xué)配合做頁面的參數(shù)校驗(yàn),必傳參數(shù)都有的時(shí)候再向服務(wù)器發(fā)送請求,一方面減輕服務(wù)器壓力,一方面將問題前置節(jié)省雙方的時(shí)間。但是這樣寫有一個(gè)壞處就是所有的Controller方法中關(guān)于異常的部分都是一樣的,代碼非常冗余。且不利于維護(hù),而且一些不太熟悉異常機(jī)制的同學(xué)可能會像踢皮球一樣將異常抓了拋,拋完又抓回來,鬧著玩呢。。。(筆者就曾經(jīng)接手過一個(gè)跑路同學(xué)的代碼這樣處理異常,那簡直是跟異常捉迷藏呢!可恨)我們在Service有全局事務(wù)處理,在系統(tǒng)中可以有全局的日志處理,這些都是基于Spring 的一大殺器:AOP(面向切面編程) 實(shí)現(xiàn)的,AOP是什么呢?
AOP
AOP是Spring框架面向切面的編程思想,AOP采用一種稱為“橫切”的技術(shù),將涉及多業(yè)務(wù)流程的通用功能抽取并單獨(dú)封裝,形成獨(dú)立的切面,在合適的時(shí)機(jī)將這些切面橫向切入到業(yè)務(wù)流程指定的位置中。如果說我們常用的OOP思想是從上到下執(zhí)行業(yè)務(wù)流程的話,AOP就相當(dāng)于在我們執(zhí)行業(yè)務(wù)的時(shí)候橫切一刀,如下圖所示:
而Advice(通知)是AOP思想中重要的一個(gè)術(shù)語,分為前置通知(Before)、后置通知(AfterReturning)、異常通知(AfterThrowing)、最終通知(After)和環(huán)繞通知(Around)五種。具體通知所表示的意義我這里不多贅述,網(wǎng)上關(guān)于Spring核心原理的講解都會提及。而我們熟知的 Service 事務(wù)處理其實(shí)就是基于AOP AfterThrowing 通知實(shí)現(xiàn)的事務(wù)回滾。我們自定義的日志處理也可以根據(jù)不同的需求定制不同的通知入口。那既然如此,我們?yōu)楹尾蛔远x一個(gè)全局異常處理的切面去簡化我們的代碼呢?別急,且繼續(xù)向下看。
優(yōu)雅的處理異常
Spring 在 3.2 版本已經(jīng)為我們提供了該功能: @ControllerAdvice 注解。此注解會捕捉Controller層拋出的異常,并根據(jù) @ExceptionHandler 注解配置的方法進(jìn)行異常處理。下面是一個(gè)示例工程,主要代碼如下:
Result類:
此 Result 采用泛型的方式,便于在 Swagger 中配置方法的出參。使用靜態(tài)工廠方法是的對象的初始化更加見名只意。對于不存在共享變量問題的 Error 對象,采用雙重校驗(yàn)鎖懶漢單例模式來節(jié)省服務(wù)器資源(當(dāng)然最好還是整個(gè)項(xiàng)目運(yùn)行中一直沒有初始化它讓人更加舒服。)
package cn.keats.util;
import cn.keats.exception.MyException;
import lombok.Data;
/**
* 功能:統(tǒng)一返回結(jié)果,直接調(diào)用對應(yīng)的工廠方法
*
* @author Keats
* @date 2019/11/29 18:20
*/
@Data
public class Result{
private Integer code;
private String msg;
private T data;
/**
* 功能:響應(yīng)成功
*
* @param data 響應(yīng)的數(shù)據(jù)
* @return woke.cloud.property.transformat.Result
* @author Keats
* @date 2019/11/30 8:54
*/
public static Result OK(T data){
return new Result<>(0, "響應(yīng)成功", data);
}
private static Result errorResult;
/**
* 功能:返回錯(cuò)誤,此錯(cuò)誤不可定制,全局唯一。一般是代碼出了問題,需要修改代碼
*
* @param
* @return Result
* @author Keats
* @date 2019/11/30 8:55
*/
public static Result Error(){
if(errorResult == null){
synchronized (Result.class){
if(errorResult == null){
synchronized (Result.class){
errorResult = new Result<>(-1, "未知錯(cuò)誤", null);
}
}
}
}
return errorResult;
}
/**
* 功能:返回異常,直接甩自定義異常類進(jìn)來
*
* @param e 自定義異常類
* @param data 數(shù)據(jù),如果沒有填入 null 即可
* @return woke.cloud.property.transformat.Result
* @author Keats
* @date 2019/11/30 8:55
*/
public static Result Exception(MyException e, T data){
return new Result<>(e.getCode(), e.getMsg(), data);
}
/**
* 功能:為了方便使用,使用靜態(tài)工廠方法創(chuàng)建對象。如需新的構(gòu)造方式,請?zhí)砑訉?yīng)的靜態(tài)工廠方法
*
* @author Keats
* @date 2019/11/30 8:56
*/
private Result(Integer code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
}
}
自定義異常類:
package cn.keats.exception;
import lombok.Getter;
/**
* 功能:系統(tǒng)自定義異常類。繼承自RuntimeException,方便Spring進(jìn)行事務(wù)回滾
*
* @author Keats
* @date 2019/11/29 18:50
*/
@Getter
public class MyException extends RuntimeException{
private Integer code;
private String msg;
public MyException(ExceptionEnum eEnum){
this.code = eEnum.getCode();
this.msg = eEnum.getMsg();
}
}
異常代碼枚舉類:
package cn.keats.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 功能:異常枚舉
*
* @author Keats
* @date 2019/11/29 18:49
*/
@Getter
@AllArgsConstructor
public enum ExceptionEnum {
PARAM_EXCEPTION(1,"參數(shù)異常"),
USER_NOT_LOGIN(2,"用戶未登錄"),
FILE_NOT_FOUND(3,"文件不存在,請重新選擇");
private Integer code;
private String msg;
}
異常切面:
其中 @RestControllerAdvice 是spring 4.3 添加的新注解,是 @ControllerAdvice 和 @ResponseBody 的簡寫方式,類似與 @RestController 與 @Controller 的關(guān)系
package cn.keats.advice;
import cn.keats.exception.MyException;
import cn.keats.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 功能:全局異常處理器,Controller異常直接拋出
*
* @return
* @author Keats
* @date 2019/11/30 10:28
*/
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice{
/**
* 功能:其余非預(yù)先規(guī)避的異常返回錯(cuò)誤
*
* @param e
* @return woke.cloud.property.transformat.Result
* @author Keats
* @date 2019/11/30 10:08
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result ResponseException(Exception e){
log.error("未知錯(cuò)誤,錯(cuò)誤信息:", e);
return Result.Error();
}
/**
* 功能:捕捉到 MyException 返回對應(yīng)的消息
*
* @param e
* @return woke.cloud.property.transformat.Result
* @author Keats
* @date 2019/11/30 10:07
*/
@ExceptionHandler(value = MyException.class)
@ResponseBody
public Result myException(MyException e){
log.info("返回自定義異常:異常代碼:" + e.getCode() + "異常信息:" + e.getMsg());
return Result.Exception(e, null);
}
}
此時(shí)的 Controller 方法可以這樣寫:
package cn.keats.controller;
import cn.keats.service.DemoService;
import cn.keats.util.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController{
@Autowired
private DemoService demoService;
@PostMapping("respException")
public Result respException(String param) throws Exception{
return Result.OK(demoService.respException(param));
}
@PostMapping("respError")
public Result respError() throws Exception{
return Result.OK(demoService.respException(null));
}
}
省略的大部分的異常處理代碼,使得我們只需要關(guān)注業(yè)務(wù),一方面提高了代碼質(zhì)量,可閱讀性,另一方面也提高了我們的開發(fā)速度。美哉!
啟動(dòng)項(xiàng)目,進(jìn)行測試沒有問題。
我是 Keats,一個(gè)熱愛技術(shù)的程序員,鑒于技術(shù)有限,如果本文有什么紕漏或者兄臺還有其他更好的建議/實(shí)現(xiàn)方式,歡迎留言評論,謝謝您!
總結(jié)
以上是生活随笔為你收集整理的python切面异常处理_Spring项目中优雅的异常处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android打印html页面,Andr
- 下一篇: istio springcloud_手牵