@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處理異常的大致原理
- ExceptionHandlerExceptionResolver是Spring MVC缺省被啓用的一個HandlerExceptionResolver,它會被作爲一個組合模式HandlerExceptionResolver bean中的一個元素進入到bean容器中;ExceptionHandlerExceptionResolver在初始化時會收集@ExceptionHandler以供使用
- DispatcherServlet初始化時,會蒐集所有HandlerExceptionResolver bean記錄到自己的策略組件屬性List handlerExceptionResolvers
- 當異常發生時,DispatcherServlet會遍歷handlerExceptionResolvers中每個HandlerExceptionResolver對象試圖對該異常進行處理。
- ExceptionHandlerExceptionResolver處理異常
- 先從發生異常的控制器方法所在類查找是否存在使用註解@ExceptionHandler並能處理該異常的方法
- 如果找不到,從所有@ControllerAdvice註解類中查找使用註解@ExceptionHandler並能處理該異常的方法;
參考文章
SpringMvc @InitBinder: https://www.cnblogs.com/lvbinbin2yujie/p/10459303.html
ControllerAdvice文檔
SpringBoot源碼解析-ExceptionHandler處理異常的原理
Spring Web : 註解@ExceptionHandler的工作原理