@RestControllerAdvice

@RestControllerAdvice介紹

官方文檔

作爲的特殊化@Component,允許通過類路徑掃描自動檢測實現類。 通常用於定義@ExceptionHandler,
@InitBinder和@ModelAttribute 適用於所有@RequestMapping方法的方法。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] assignableTypes() default {};

    Class<? extends Annotation>[] annotations() default {};
}
  • value
@AliasFor(value="basePackages")
public abstract String[] value

basePackages包名

例如: @ControllerAdvice("org.my.pkg")等價於 @ControllerAdvice(basePackages="org.my.pkg")。
  • basePackages
@AliasFor(value="value")
public abstract String[] basePackages

basePackages包數組

 e.g.:
  @ControllerAdvice(basePackages="org.my.pkg") 
  or @ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"}).  

我們常用的就是value(),basePackages(),來限定控制器;annotations(),basePackageClasses()也能。

@InitBinder

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
    String[] value() default {};
}

該註解用於標識初始化{@link org.springframework.web.bind.WebDataBinder}的方法,該方法將用於填充帶註釋的處理程序方法的命令和表單對象參數。我們可以理解爲將Web請求中的屬性轉成我們需要的類型。

@ModelAttribute

該註解的方法可以處理Model中的數據;

@ExceptionHandler

這個註解就是我們最常用的呢;用來進行統一異常處理。

@RestControllerAdvice
public class BaseExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public Result exception(Exception e){
        return new Result(StatusCode.ERROR,false,e.getMessage());
    }
}

異常到達@ExceptionHandler的基本流程
在這裏插入圖片描述

InvocableHandlerMethod.doInvoke():可以捕獲到異常,並拋出異常throw (RuntimeException) targetException;

DispatcherServlet.doDispatch():處理異常

DispatcherServlet.processDispatchResult():處理程序調用的結果;該結果可以是ModelAndView或要解析爲ModelAndView的Exception;如果exception != null,

/**
	 * Handle the result of handler selection and handler invocation, which is
	 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
	 */
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;
		// 如果exception != null
		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
		  	// 初始化handler 
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				//處理異常
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
						"': assuming HandlerAdapter completed request handling");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

DispatcherServlet.processHandlerException():通過註冊的HandlerExceptionResolvers確定錯誤ModelAndView。
HandlerExceptionResolverComposite.resolveException() 通過遍歷已配置的異常解析器列表來解決異常。 第一個返回ModelAndView實例的對象將獲勝。否則,返回{@code null}
AbstractHandlerExceptionResolver.resolveException():它會先檢查此解析器是否應該應用於給定的處理程序。
AbstractHandlerExceptionResolver.resolveException

@Override
	@Nullable
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) {
		//檢查此解析器是否應該應用於給定的處理程序
		if (shouldApplyTo(request, handler)) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
			}
			prepareResponse(ex, response);
			//符合,進行處理
			ModelAndView result = doResolveException(request, response, handler, ex);
			if (result != null) {
				logException(ex, request);
			}
			return result;
		}
		else {
			return null;
		}
	}

ExceptionHandlerExceptionResolver.doResolveHandlerMethodException():找到一個{@code @ExceptionHandler}方法並調用它來處理引發的異常

ServletInvocableHandlerMethod.invokeAndHandle():獲得返回數據。
InvocableHandlerMethod.doInvoke():使用給定的參數值調用處理方法;
@ExceptionHandler:自定義處理異常

核心類ExceptionHandlerExceptionResolver
initExceptionHandlerAdviceCache方法:找出所有的@ControllerAdvice下的@ExceptionHandler;放入到exceptionHandlerAdviceCache中

	private void initExceptionHandlerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for exception mappings: " + getApplicationContext());
		}
	// 找出所有的ControllerAdvice
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		AnnotationAwareOrderComparator.sort(adviceBeans);
		
	// 遍歷ControllerAdvice
		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class<?> beanType = adviceBean.getBeanType();
			if (beanType == null) {
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
			}
			//找出所有的@ExceptionHandler
			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
			if (resolver.hasExceptionMappings()) {
			//  放入到 Map<ControllerAdviceBean, ExceptionHandlerMethodResolver>
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
				if (logger.isInfoEnabled()) {
					logger.info("Detected @ExceptionHandler methods in " + adviceBean);
				}
			}
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				this.responseBodyAdvice.add(adviceBean);
				if (logger.isInfoEnabled()) {
					logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
				}
			}
		}
	}

ExceptionHandler處理異常的大致原理

  1. ExceptionHandlerExceptionResolver是Spring MVC缺省被啓用的一個HandlerExceptionResolver,它會被作爲一個組合模式HandlerExceptionResolver bean中的一個元素進入到bean容器中;ExceptionHandlerExceptionResolver在初始化時會收集@ExceptionHandler以供使用
  2. DispatcherServlet初始化時,會蒐集所有HandlerExceptionResolver bean記錄到自己的策略組件屬性List handlerExceptionResolvers
  3. 當異常發生時,DispatcherServlet會遍歷handlerExceptionResolvers中每個HandlerExceptionResolver對象試圖對該異常進行處理。
  4. ExceptionHandlerExceptionResolver處理異常
    1. 先從發生異常的控制器方法所在類查找是否存在使用註解@ExceptionHandler並能處理該異常的方法
    2. 如果找不到,從所有@ControllerAdvice註解類中查找使用註解@ExceptionHandler並能處理該異常的方法;

參考文章

SpringMvc @InitBinder: https://www.cnblogs.com/lvbinbin2yujie/p/10459303.html

ControllerAdvice文檔
SpringBoot源碼解析-ExceptionHandler處理異常的原理
Spring Web : 註解@ExceptionHandler的工作原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章