欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解SpringBoot如何優(yōu)雅的進(jìn)行全局異常處理

 更新時(shí)間:2023年07月02日 08:37:09   作者:碼老思  
在SpringBoot的開(kāi)發(fā)中,為了提高程序運(yùn)行的魯棒性,我們經(jīng)常需要對(duì)各種程序異常進(jìn)行處理,但是如果在每個(gè)出異常的地方進(jìn)行單獨(dú)處理的話,這會(huì)引入大量業(yè)務(wù)不相關(guān)的異常處理代碼,這篇文章帶大家了解一下如何優(yōu)雅的進(jìn)行全局異常處理

為了實(shí)現(xiàn)全局?jǐn)r截,這里使用到了Spring中提供的兩個(gè)注解,@RestControllerAdvice@ExceptionHandler,結(jié)合使用可以攔截程序中產(chǎn)生的異常,并且根據(jù)不同的異常類型分別處理。下面我會(huì)先介紹如何利用這兩個(gè)注解,優(yōu)雅的完成全局異常的處理,接著解釋這背后的原理。

1. 如何實(shí)現(xiàn)全局?jǐn)r截?

1.1 自定義異常處理類

在下面的例子中,我們繼承了ResponseEntityExceptionHandler并使用@RestControllerAdvice注解了這個(gè)類,接著結(jié)合@ExceptionHandler針對(duì)不同的異常類型,來(lái)定義不同的異常處理方法。這里可以看到我處理的異常是自定義異常,后續(xù)我會(huì)展開(kāi)介紹。

ResponseEntityExceptionHandler中包裝了各種SpringMVC在處理請(qǐng)求時(shí)可能拋出的異常的處理,處理結(jié)果都是封裝成一個(gè)ResponseEntity對(duì)象。ResponseEntityExceptionHandler是一個(gè)抽象類,通常我們需要定義一個(gè)用來(lái)處理異常的使用@RestControllerAdvice注解標(biāo)注的異常處理類來(lái)繼承自ResponseEntityExceptionHandler。ResponseEntityExceptionHandler中為每個(gè)異常的處理都單獨(dú)定義了一個(gè)方法,如果默認(rèn)的處理不能滿足你的需求,則可以重寫對(duì)某個(gè)異常的處理。

@Log4j2  
@RestControllerAdvice  
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {  
    /**  
     * 定義要捕獲的異常 可以多個(gè) @ExceptionHandler({})     *  
     * @param request  request  
     * @param e        exception  
     * @param response response  
     * @return 響應(yīng)結(jié)果  
     */  
    @ExceptionHandler(AuroraRuntimeException.class)  
    public GenericResponse customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {  
        AuroraRuntimeException exception = (AuroraRuntimeException) e;  
       if (exception.getCode() == ResponseCode.USER_INPUT_ERROR) {  
           response.setStatus(HttpStatus.BAD_REQUEST.value());  
       } else if (exception.getCode() == ResponseCode.FORBIDDEN) {  
           response.setStatus(HttpStatus.FORBIDDEN.value());  
       } else {  
           response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());  
       }  
        return new GenericResponse(exception.getCode(), null, exception.getMessage());  
    }  
    @ExceptionHandler(NotLoginException.class)  
    public GenericResponse tokenExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {  
        log.error("token exception", e);  
        response.setStatus(HttpStatus.FORBIDDEN.value());  
        return new GenericResponse(ResponseCode.AUTHENTICATION_NEEDED);  
    }  
}

1.2 定義異常碼

這里定義了常見(jiàn)的幾種異常碼,主要用在拋出自定義異常時(shí),對(duì)不同的情形進(jìn)行區(qū)分。

@Getter  
public enum ResponseCode {  
    SUCCESS(0, "Success"),  
    INTERNAL_ERROR(1, "服務(wù)器內(nèi)部錯(cuò)誤"),  
    USER_INPUT_ERROR(2, "用戶輸入錯(cuò)誤"),  
    AUTHENTICATION_NEEDED(3, "Token過(guò)期或無(wú)效"),  
    FORBIDDEN(4, "禁止訪問(wèn)"),  
    TOO_FREQUENT_VISIT(5, "訪問(wèn)太頻繁,請(qǐng)休息一會(huì)兒");  
    private final int code;  
    private final String message;  
    private final Response.Status status;  
    ResponseCode(int code, String message, Response.Status status) {  
        this.code = code;  
        this.message = message;  
        this.status = status;  
    }  
    ResponseCode(int code, String message) {  
        this(code, message, Response.Status.INTERNAL_SERVER_ERROR);  
    }  
}

1.3 自定義異常類

這里我定義了一個(gè)AuroraRuntimeException的異常,就是在上面的異常處理函數(shù)中,用到的異常。每個(gè)異常實(shí)例會(huì)有一個(gè)對(duì)應(yīng)的異常碼,也就是前面剛定義好的。

@Getter  
public class AuroraRuntimeException extends RuntimeException {  
    private final ResponseCode code;  
    public AuroraRuntimeException() {  
        super(String.format("%s", ResponseCode.INTERNAL_ERROR.getMessage()));  
        this.code = ResponseCode.INTERNAL_ERROR;  
    }  
    public AuroraRuntimeException(Throwable e) {  
        super(e);  
        this.code = ResponseCode.INTERNAL_ERROR;  
    }  
    public AuroraRuntimeException(String msg) {  
        this(ResponseCode.INTERNAL_ERROR, msg);  
    }  
    public AuroraRuntimeException(ResponseCode code) {  
        super(String.format("%s", code.getMessage()));  
        this.code = code;  
    }  
    public AuroraRuntimeException(ResponseCode code, String msg) {  
        super(msg);  
        this.code = code;  
    }  
}

1.4 自定義返回類型

為了保證各個(gè)接口的返回統(tǒng)一,這里專門定義了一個(gè)返回類型。

@Getter  
@Setter  
public class GenericResponse<T> {  
    private int code;  
    private T data;  
    private String message;  
    public GenericResponse() {};  
    public GenericResponse(int code, T data) {  
        this.code = code;  
        this.data = data;  
    }  
    public GenericResponse(int code, T data, String message) {  
        this(code, data);  
        this.message = message;  
    }  
    public GenericResponse(ResponseCode responseCode) {  
        this.code = responseCode.getCode();  
        this.data = null;  
        this.message = responseCode.getMessage();  
    }  
    public GenericResponse(ResponseCode responseCode, T data) {  
        this(responseCode);  
        this.data = data;  
    }  
    public GenericResponse(ResponseCode responseCode, T data, String message) {  
        this(responseCode, data);  
        this.message = message;  
    }  
}

實(shí)際測(cè)試異常

下面的例子中,我們想獲取到用戶的信息,如果用戶的信息不存在,可以直接拋出一個(gè)異常,這個(gè)異常會(huì)被我們上面定義的全局異常處理方法所捕獲,然后根據(jù)不同的異常編碼,完成不同的處理和返回。

public User getUserInfo(Long userId) {  
	// some logic
    User user = daoFactory.getExtendedUserMapper().selectByPrimaryKey(userId);  
    if (user == null) {  
        throw new AuroraRuntimeException(ResponseCode.USER_INPUT_ERROR, "用戶id不存在");  
    }
    // some logic
	....
}

以上就完成了整個(gè)全局異常的處理過(guò)程,接下來(lái)重點(diǎn)說(shuō)說(shuō)為什么@RestControllerAdvice@ExceptionHandler結(jié)合使用可以攔截程序中產(chǎn)生的異常?

全局?jǐn)r截的背后原理?

下面會(huì)提到@ControllerAdvice注解,簡(jiǎn)單地說(shuō),@RestControllerAdvice與@ControllerAdvice的區(qū)別就和@RestController與@Controller的區(qū)別類似,@RestControllerAdvice注解包含了@ControllerAdvice注解和@ResponseBody注解。

接下來(lái)我們深入Spring源碼,看看是怎么實(shí)現(xiàn)的,首先DispatcherServlet對(duì)象在創(chuàng)建時(shí)會(huì)初始化一系列的對(duì)象,這里重點(diǎn)關(guān)注函數(shù)initHandlerExceptionResolvers(context);.

public class DispatcherServlet extends FrameworkServlet {
    // ......
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		// 重點(diǎn)關(guān)注
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
    // ......
}

在initHandlerExceptionResolvers(context)方法中,會(huì)取得所有實(shí)現(xiàn)了HandlerExceptionResolver接口的bean并保存起來(lái),其中就有一個(gè)類型為ExceptionHandlerExceptionResolver的bean,這個(gè)bean在應(yīng)用啟動(dòng)過(guò)程中會(huì)獲取所有被@ControllerAdvice注解標(biāo)注的bean對(duì)象做進(jìn)一步處理,關(guān)鍵代碼在這里:

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
		implements ApplicationContextAware, InitializingBean {
    // ......
	private void initExceptionHandlerAdviceCache() {
		// ......
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		AnnotationAwareOrderComparator.sort(adviceBeans);
		for (ControllerAdviceBean adviceBean : adviceBeans) {
			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
			if (resolver.hasExceptionMappings()) {
			    // 找到所有ExceptionHandler標(biāo)注的方法并保存成一個(gè)ExceptionHandlerMethodResolver類型的對(duì)象緩存起來(lái)
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
				if (logger.isInfoEnabled()) {
					logger.info("Detected @ExceptionHandler methods in " + adviceBean);
				}
			}
			// ......
		}
	}
    // ......
}

當(dāng)Controller拋出異常時(shí),DispatcherServlet通過(guò)ExceptionHandlerExceptionResolver來(lái)解析異常,而ExceptionHandlerExceptionResolver又通過(guò)ExceptionHandlerMethodResolver 來(lái)解析異常, ExceptionHandlerMethodResolver 最終解析異常找到適用的@ExceptionHandler標(biāo)注的方法是這里:

public class ExceptionHandlerMethodResolver {
	// ......
	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
		List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
		// 找到所有適用于Controller拋出異常的處理方法,例如Controller拋出的異常
		// 是AuroraRuntimeException(繼承自RuntimeException),那么@ExceptionHandler(AuroraRuntimeException.class)和
		// @ExceptionHandler(Exception.class)標(biāo)注的方法都適用此異常
		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
			if (mappedException.isAssignableFrom(exceptionType)) {
				matches.add(mappedException);
			}
		}
		if (!matches.isEmpty()) {
		/* 這里通過(guò)排序找到最適用的方法,排序的規(guī)則依據(jù)拋出異常相對(duì)于聲明異常的深度,例如
	Controller拋出的異常是是AuroraRuntimeException(繼承自RuntimeException),那么AuroraRuntimeException
	相對(duì)于@ExceptionHandler(AuroraRuntimeException.class)聲明的AuroraRuntimeException.class其深度是0,
	相對(duì)于@ExceptionHandler(Exception.class)聲明的Exception.class其深度是2,所以
	@ExceptionHandler(BizException.class)標(biāo)注的方法會(huì)排在前面 */
			Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
			return this.mappedMethods.get(matches.get(0));
		}
		else {
			return null;
		}
	}
    // ......
}

整個(gè)@RestControllerAdvice處理的流程就是這樣,結(jié)合@ExceptionHandler就完成了對(duì)不同異常的靈活處理。

以上就是詳解SpringBoot如何優(yōu)雅的進(jìn)行全局異常處理的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot 全局異常處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • RocketMQ集群消費(fèi)與廣播消費(fèi)模式

    RocketMQ集群消費(fèi)與廣播消費(fèi)模式

    這篇文章主要介紹了RocketMQ集群消費(fèi)與廣播消費(fèi)模式,消息隊(duì)列RocketMQ版支持集群消費(fèi)和廣播消費(fèi),本文介紹集群消費(fèi)和廣播消費(fèi)的基本概念、適用場(chǎng)景、功能差異、注意事項(xiàng)以及設(shè)置方式
    2023-02-02
  • httpclient提交json參數(shù)的示例詳解

    httpclient提交json參數(shù)的示例詳解

    httpclient使用post提交json參數(shù),和使用表單提交區(qū)分,本文結(jié)合示例代碼講解的非常詳細(xì),補(bǔ)充介紹了HttpClient請(qǐng)求傳json參數(shù)的案例代碼,感興趣的朋友一起看看吧
    2024-02-02
  • 詳解java JDK 動(dòng)態(tài)代理類分析(java.lang.reflect.Proxy)

    詳解java JDK 動(dòng)態(tài)代理類分析(java.lang.reflect.Proxy)

    這篇文章主要介紹了詳解java JDK 動(dòng)態(tài)代理類分析(java.lang.reflect.Proxy)的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • SpringBoot+MybatisPlus+代碼生成器整合示例

    SpringBoot+MybatisPlus+代碼生成器整合示例

    這篇文章主要介紹了SpringBoot+MybatisPlus+代碼生成器整合示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • mybatis整合springboot報(bào)BindingException:Invalid?bound?statement?(not?found)異常解決

    mybatis整合springboot報(bào)BindingException:Invalid?bound?stateme

    這篇文章主要給大家介紹了關(guān)于mybatis整合springboot報(bào)BindingException:Invalid?bound?statement?(not?found)異常的解決辦法,這個(gè)錯(cuò)誤通常是由于Mapper文件中的statement?id與Java代碼中的方法名不一致導(dǎo)致的,需要的朋友可以參考下
    2024-01-01
  • 詳解Java中CAS機(jī)制的原理與優(yōu)缺點(diǎn)

    詳解Java中CAS機(jī)制的原理與優(yōu)缺點(diǎn)

    CAS?英文就是?compare?and?swap?,也就是比較并交換,這篇文章主要來(lái)和大家介紹一下Java中CAS機(jī)制的原理與優(yōu)缺點(diǎn),感興趣的小伙伴可以了解一下
    2023-06-06
  • 走進(jìn)JDK之不可變類String

    走進(jìn)JDK之不可變類String

    這篇文章主要給大家介紹了JDK之不可變類String的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用JDK具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • springboot?max-http-header-size最大長(zhǎng)度的那些事及JVM調(diào)優(yōu)方式

    springboot?max-http-header-size最大長(zhǎng)度的那些事及JVM調(diào)優(yōu)方式

    這篇文章主要介紹了springboot?max-http-header-size最大長(zhǎng)度的那些事及JVM調(diào)優(yōu)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • java從list中取出對(duì)象并獲得其屬性值的方法

    java從list中取出對(duì)象并獲得其屬性值的方法

    這篇文章主要介紹了java從list中取出對(duì)象并獲得其屬性值的方法,大家參考使用
    2013-12-12
  • Java多線程之synchronized同步代碼塊詳解

    Java多線程之synchronized同步代碼塊詳解

    這篇文章主要為大家詳細(xì)介紹了Java多線程之synchronized同步代碼塊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03

最新評(píng)論