1.前言
任何系統(tǒng),我們不會傻傻的在每一個地方進(jìn)行異常捕獲和處理,整個系統(tǒng)一般我們會在一個的地方統(tǒng)一進(jìn)行異常處理,spring boot全局異常處理很簡單;
前后端分離,后端API,一般對于異常處理,要做得無非兩件事,
1.是記錄日志及相應(yīng)通知處理,這是對內(nèi)的
2.是給出返回結(jié)果給API調(diào)用者,這是對外的
對API調(diào)用者來說,他只需要一個返回結(jié)果(包含錯誤代碼、提示信息),其他的他不關(guān)心
對后端來說,他只需要記錄日志,通知或者給發(fā)布相應(yīng)消息給其他隊(duì)列處理相關(guān)事項(xiàng);
所以:看到過不少人封裝了很多個自定義異常類,其實(shí),完全沒有必要,只需要一個異常處理來處理所有異常即可,然后封裝一個錯誤識別碼和提示消息的枚舉,用于返回給API調(diào)用者;然后后端的處理,直接在一個異常處理方法中全部處理就行了,完全沒必要封裝N多個自定義異常,那沒有任何意義;
關(guān)于異常的思想認(rèn)識
我們應(yīng)該認(rèn)識到,一切異常,對系統(tǒng)來說,都是不正常的表現(xiàn),都是屬于缺陷,都屬于BUG,盡管有些異常是我們主動拋出的;
我們要做的,是應(yīng)該盡量提高系統(tǒng)可用性,最大限度避免任何異常的出現(xiàn),而不是去指望完善異常處理來完善系統(tǒng);
異常處理,是異常無法避免的出現(xiàn)了而采取的一種應(yīng)急措施,主要目的是對外增加友好性,對內(nèi)提供補(bǔ)救線索;
不要認(rèn)為完善的異常處理是系統(tǒng)核心,他不是,不要指望異常處理盡善盡美,不要指望異常處理來給系統(tǒng)缺陷擦屁股;
如果系統(tǒng)異常過多,那么你要做的不是去完善異常處理機(jī)制,而是要好好去反思:系統(tǒng)架構(gòu)設(shè)計(jì)是否合理,系統(tǒng)邏輯設(shè)計(jì)是否合理;
2.全局異常并處理的方法一(@ControllerAdvice 和 @ExceptionHandler)
=================================================
在開發(fā)中,我們會有如下的場景:某個接口中,存在一些業(yè)務(wù)異常。例如用戶輸入的參數(shù)校驗(yàn)失敗、用戶名密碼不存在等。當(dāng)觸發(fā)這些業(yè)務(wù)異常時,我們需要拋出這些自定義的業(yè)務(wù)異常,并對其進(jìn)行處理。一般我們要把這些異常信息的狀態(tài)碼和異常描述,友好地返回給調(diào)用者,調(diào)用者則利用狀態(tài)碼等信息判斷異常的具體情況。
過去,我們可能需要在 controller 層通過 try/catch 處理。首先 catch 自定義異常,然后 catch 其它異常。對于不同的異常,我們需要在 catch 的同時封裝將要返回的對象。然而,這么做的弊端就是代碼會變得冗長。每個接口都需要做 try/catch 處理,而且一旦需要調(diào)整,所有的接口都需要修改一遍,非常不利于代碼的維護(hù),如下段代碼所示
@RequestMapping (value = "/test") public ResponseEntity test() { ResponseEntity re = new ResponseEntity(); // 業(yè)務(wù)處理 // ... try { // 業(yè)務(wù) } catch (BusinessException e) { logger.info("業(yè)務(wù)發(fā)生異常,code:" + e.getCode() + "msg:" + e.getMsg()); re.setCode(e.getCode()); re.setMsg(e.getMsg()); return re; } catch (Exception e) { logger.error("服務(wù)錯誤:", e); re.setCode("xxxxx"); re.setMsg("服務(wù)錯誤"); return re; } return re; }
那么,有沒有什么方法可以簡便地處理這些異常信息呢?答案是肯定的。Spring 3.2 中,新增了 @ControllerAdvice 注解,可以用于定義 @ExceptionHandler 、 @InitBinder 、@ModelAttribute ,并應(yīng)用到所有 @RequestMapping 中。簡單來說就是,可以通過@ControllerAdvice 注解配置一個全局異常處理類,來統(tǒng)一處理 controller 層中的異常,于此同時 controller 中可以不用再寫 try/catch,這使得代碼既整潔又便于維護(hù)。
使用方法
定義自定義異常
有關(guān)自定義異常相關(guān)知識點(diǎn)這里就不詳細(xì)說明了,如果不了解的話自行搜索一下。這里貼上一個簡單的自定義業(yè)務(wù)異常類。
/** * 自定義業(yè)務(wù)異常類 * * @author Yuzhe Ma * @date 2018/11/28 */ @Data public class BusinessException extends RuntimeException { private String code; private String msg; public BusinessException(String code, String msg) { this.code = code; this.msg = msg; } }
注: @Data 為 Lombok 插件。自動生成 set/get 方法。具體使用方法這里就不展開介紹了。
@ControllerAdvice @ExceptionHand` 配置全局異常處理類
/** * 全局異常處理器 * * @author Yuzhe Ma * @date 2018/11/12 */ @ControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 處理 Exception 異常 * * @param httpServletRequest httpServletRequest * @param e 異常 * @return */ @ResponseBody @ExceptionHandler(value = Exception.class) public ResponseEntity exceptionHandler(HttpServletRequest httpServletRequest, Exception e) { logger.error("服務(wù)錯誤:", e); return new ResponseEntity("xxx", "服務(wù)出錯"); } /** * 處理 BusinessException 異常 * * @param httpServletRequest httpServletRequest * @param e 異常 * @return */ @ResponseBody @ExceptionHandler(value = BusinessException.class) public ResponseEntity businessExceptionHandler(HttpServletRequest httpServletRequest, BusinessException e) { logger.info("業(yè)務(wù)異常。code:" + e.getCode() + "msg:" + e.getMsg()); return new ResponseEntity(e.getCode(), e.getMsg()); } }
@ControllerAdvice
定義該類為全局異常處理類。
@ExceptionHandler
定義該方法為異常處理方法。value 的值為需要處理的異常類的 class 文件。在例子中,方法傳入兩個參數(shù)。一個是對應(yīng)的 Exception 異常類,一個是 HttpServletRequest 類。當(dāng)然,除了這兩種參數(shù),還支持傳入一些其他參數(shù)。
這樣,就可以對不同的異常進(jìn)行統(tǒng)一處理了。通常,為了使 controller 中不再使用任何 try/catch,也可以在 GlobalExceptionHandler 中對 Exception 做統(tǒng)一處理。這樣其他沒有用 @ExceptionHandler 配置的異常就都會統(tǒng)一被處理。
遇到異常時拋出異常即可
在業(yè)務(wù)中,遇到業(yè)務(wù)異常的地方,直接使用 throw 拋出對應(yīng)的業(yè)務(wù)異常即可。例如
throw new BusinessException("3000", "賬戶密碼錯誤");
在 Controller 中的寫法
Controller 中,不需要再寫 try/catch,除非特殊用途。
@RequestMapping(value = "/test") public ResponseEntity test() { ResponseEntity re = new ResponseEntity(); // 業(yè)務(wù)處理 // ... return re; }
結(jié)果展示
異常拋出后,返回如下結(jié)果。
{ "code": "3000", "msg": "賬戶密碼錯誤", "data": null }
注意 不一定必須在 controller 層本身拋出異常才能被 GlobalExceptionHandler 處理,只要異常最后是從 contoller 層拋出去的就可以被全局異常處理器處理。異步方法中的異常不會被全局異常處理。拋出的異常如果被代碼內(nèi)的 try/catch 捕獲了,就不會被 GlobalExceptionHandler 處理了。總結(jié)
本文介紹了在 SpringBoot 中,通過配置全局異常處理器統(tǒng)一處理 Controller 層引發(fā)的異常。
優(yōu)點(diǎn)
減少代碼冗余,代碼便于維護(hù)
缺點(diǎn)
只能處理 controller 層拋出的異常,對例如 Interceptor(攔截器)層的異常、定時任務(wù)中的異常、異步方法中的異常,不會進(jìn)行處理。
3.全局異常并處理的方法二 (AOP)
雖然@ControllerAdvice注解通常和@ExceptionHandler注解用于全局異常的處理。
但是這種方式有個缺點(diǎn)就是,只是對控制層進(jìn)行了異常攔截,比如像工具類中或者其他類中的異常,并不會攔截。
由于業(yè)務(wù)執(zhí)行時不能保證程序不出錯,所以寫代碼必須添加try-catch,但是如果頻繁的添加try-catch則必然導(dǎo)致代碼結(jié)構(gòu)混亂.所以需要進(jìn)行優(yōu)化.
原則:如果出現(xiàn)了問題一般將檢查異常,轉(zhuǎn)化為運(yùn)行時異常.
核心原理: 代理動態(tài)思想------->AOP操作
采用自定義AOP的方式可以實(shí)現(xiàn)攔截。
有幾個關(guān)鍵點(diǎn)
定義切入點(diǎn)為最大項(xiàng)目包
采用AOP的@AfterThrowing注解獲取到全局異常捕獲一個例子package com.example.promethuesdemo.exception; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @author chenzhen * Created by chenzhen on 2020/7/20.
*/ @Aspect @Slf4j @Component public class GlobalExceptionAspect { @Pointcut("execution(* com.example..*.*(..))") public void pointcut(){ } @AfterThrowing(pointcut = "pointcut()",throwing = "e") public void afterThrowing(JoinPoint joinPoint,Throwable e){ log.error("全局捕獲到異常了.............."); //紀(jì)錄錯誤信息 log.error("系統(tǒng)錯誤:{}", e.getMessage()); // todo 想要執(zhí)行的操作 } }
aop中相關(guān)概念
Aspect(切面): Aspect 聲明類似于 Java 中的類聲明,在 Aspect 中會包含著一些 Pointcut 以及相應(yīng)的 Advice。* Joint point(連接點(diǎn)):表示在程序中明確定義的點(diǎn),典型的包括方法調(diào)用,對類成員的訪問以及異常處理程序塊的執(zhí)行等等,它自身還可以嵌套其它
joint point。* Pointcut(切點(diǎn)):表示一組 joint point,這些 joint point 或是通過邏輯關(guān)系組合起來,或是通過通配、正則表達(dá)式等方式集中起來,它定義了相應(yīng)的 Advice 將要發(fā)生的地方。* Advice(增強(qiáng)):Advice 定義了在 Pointcut 里面定義的程序點(diǎn)具體要做的操作,它通過 before、after 和 around 來區(qū)別是在每個 joint point 之前、之后還是代替執(zhí)行的代碼。* Target(目標(biāo)對象):織入 Advice 的目標(biāo)對象.。 Weaving(織入):將 Aspect 和其他對象連接起來, 并創(chuàng)建 Adviced object 的過程
Advice(增強(qiáng))的類型
before advice, 在 join point 前被執(zhí)行的 advice. 雖然 before advice 是在 join point 前被執(zhí)行, 但是它并不能夠阻止 join point 的執(zhí)行, 除非發(fā)生了異常(即我們在 before advice 代碼中,不能人為地決定是否繼續(xù)執(zhí)行 join point 中的代碼)* after return advice, 在一個 join point 正常返回后執(zhí)行的 advice* after throwing advice, 當(dāng)一個 join point 拋出異常后執(zhí)行的 advice* after(final) advice, 無論一個 join point 是正常退出還是發(fā)生了異常, 都會被執(zhí)行的 advice.* around advice, 在 join point 前和 joint point 退出后都執(zhí)行的 advice. 這個是最常用的 advice.* introduction,introduction可以為原有的對象增加新的屬性和方法。
注意
spring AOP中的AfterThrowing增強(qiáng)處理可以對目標(biāo)方法的異常進(jìn)行處理,但這種處理與直接使用catch捕捉處理異常的方式不同,catch捕捉意味著能完全處理異常,即只要catch塊本身不拋出新的異常,則被處理的異常不會往上級調(diào)用者進(jìn)一步傳播下去;但是如果使用了AfterThrowing增強(qiáng)處理用于對異常進(jìn)行處理,處理后異常仍然會往上一級調(diào)用者傳播,如果是在main中調(diào)用的目標(biāo)方法,那么異常會直接傳到JVM,如下截圖所示:
SpringBoot 之配置全局異常處理器捕獲異常
另外需要注意, 如果目標(biāo)方法中出現(xiàn)異常,并由catch捕捉處理且catch又沒有拋出新的異常,那么針對該目標(biāo)方法的AfterThrowing增強(qiáng)處理將不會被執(zhí)行。
以上是SpringBoot怎么配置全局異常處理器捕獲異常的詳細(xì)內(nèi)容。更多信息請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣服圖片

Undresser.AI Undress
人工智能驅(qū)動的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover
用于從照片中去除衣服的在線人工智能工具。

Clothoff.io
AI脫衣機(jī)

Video Face Swap
使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的代碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
功能強(qiáng)大的PHP集成開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級代碼編輯軟件(SublimeText3)

Jasypt介紹Jasypt是一個java庫,它允許開發(fā)員以最少的努力為他/她的項(xiàng)目添加基本的加密功能,并且不需要對加密工作原理有深入的了解用于單向和雙向加密的高安全性、基于標(biāo)準(zhǔn)的加密技術(shù)。加密密碼,文本,數(shù)字,二進(jìn)制文件...適合集成到基于Spring的應(yīng)用程序中,開放API,用于任何JCE提供程序...添加如下依賴:com.github.ulisesbocchiojasypt-spring-boot-starter2.1.1Jasypt好處保護(hù)我們的系統(tǒng)安全,即使代碼泄露,也可以保證數(shù)據(jù)源的

一、Redis實(shí)現(xiàn)分布式鎖原理為什么需要分布式鎖在聊分布式鎖之前,有必要先解釋一下,為什么需要分布式鎖。與分布式鎖相對就的是單機(jī)鎖,我們在寫多線程程序時,避免同時操作一個共享變量產(chǎn)生數(shù)據(jù)問題,通常會使用一把鎖來互斥以保證共享變量的正確性,其使用范圍是在同一個進(jìn)程中。如果換做是多個進(jìn)程,需要同時操作一個共享資源,如何互斥呢?現(xiàn)在的業(yè)務(wù)應(yīng)用通常是微服務(wù)架構(gòu),這也意味著一個應(yīng)用會部署多個進(jìn)程,多個進(jìn)程如果需要修改MySQL中的同一行記錄,為了避免操作亂序?qū)е屡K數(shù)據(jù),此時就需要引入分布式鎖了。想要實(shí)現(xiàn)分

1、自定義RedisTemplate1.1、RedisAPI默認(rèn)序列化機(jī)制基于API的Redis緩存實(shí)現(xiàn)是使用RedisTemplate模板進(jìn)行數(shù)據(jù)緩存操作的,這里打開RedisTemplate類,查看該類的源碼信息publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations,BeanClassLoaderAware{//聲明了key、value的各種序列化方式,初始值為空@NullableprivateRedisSe

springboot讀取文件,打成jar包后訪問不到最新開發(fā)出現(xiàn)一種情況,springboot打成jar包后讀取不到文件,原因是打包之后,文件的虛擬路徑是無效的,只能通過流去讀取。文件在resources下publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

使用場景1、下單成功,30分鐘未支付。支付超時,自動取消訂單2、訂單簽收,簽收后7天未進(jìn)行評價。訂單超時未評價,系統(tǒng)默認(rèn)好評3、下單成功,商家5分鐘未接單,訂單取消4、配送超時,推送短信提醒……對于延時比較長的場景、實(shí)時性不高的場景,我們可以采用任務(wù)調(diào)度的方式定時輪詢處理。如:xxl-job今天我們采

在Springboot+Mybatis-plus不使用SQL語句進(jìn)行多表添加操作我所遇到的問題準(zhǔn)備工作在測試環(huán)境下模擬思維分解一下:創(chuàng)建出一個帶有參數(shù)的BrandDTO對象模擬對后臺傳遞參數(shù)我所遇到的問題我們都知道,在我們使用Mybatis-plus中進(jìn)行多表操作是極其困難的,如果你不使用Mybatis-plus-join這一類的工具,你只能去配置對應(yīng)的Mapper.xml文件,配置又臭又長的ResultMap,然后再去寫對應(yīng)的sql語句,這種方法雖然看上去很麻煩,但具有很高的靈活性,可以讓我們

SpringBoot和SpringMVC都是Java開發(fā)中常用的框架,但它們之間有一些明顯的差異。本文將探究這兩個框架的特點(diǎn)和用途,并對它們的差異進(jìn)行比較。首先,我們來了解一下SpringBoot。SpringBoot是由Pivotal團(tuán)隊(duì)開發(fā)的,它旨在簡化基于Spring框架的應(yīng)用程序的創(chuàng)建和部署。它提供了一種快速、輕量級的方式來構(gòu)建獨(dú)立的、可執(zhí)行

一、@Import引入普通類@Import引入普通的類可以幫助我們把普通的類定義為Bean。@Import可以添加在@SpringBootApplication(啟動類)、@Configuration(配置類)、@Component(組件類)對應(yīng)的類上。注意:@RestController、@Service、@Repository都屬于@Component@SpringBootApplication@Import(ImportBean.class)//通過@Import注解把ImportBean
