SpringFramework之ControllerAdvice註解的原始碼分析
SpringFramework版本5.0.9.release。
我們會通過@ControllerAdvice和@ExceptionHandler來處理異常,Springmvc是如何進行處理的呢?
ControllerAdviceBean有個重要的方法findAnnotatedBeans,如下List-1
List-1
public class ControllerAdviceBean implements Ordered { ... public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext applicationContext) { List<ControllerAdviceBean> beans = new ArrayList(); String[] var2 = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class); int var3 = var2.length; for(int var4 = 0; var4 < var3; ++var4) { String name = var2[var4]; if (applicationContext.findAnnotationOnBean(name, ControllerAdvice.class) != null) { beans.add(new ControllerAdviceBean(name, applicationContext)); } } return beans; } ...
如List-1所示,從applicationContext中獲取所有的ControllerAdvice註解的Bean,之後封裝到ControllerAdviceBean中。
來看下ExceptionHandlerExceptionResolver,它的類繼承圖如下圖1所示:
圖1
ExceptionHandlerExceptionResolver是HandlerExceptionResolver,所以在Springmvc的doDispatch中會呼叫它。實現了InitializingBean,所以有afterPropertiesSet方法,如下List-2所示:
List-2
@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); ... } private void initExceptionHandlerAdviceCache() { ... List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans); for (ControllerAdviceBean adviceBean : adviceBeans) { Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); if (resolver.hasExceptionMappings()) { 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); } } } }
List-2中,initExceptionHandlerAdviceCache方法呼叫List-1中ControllerAdviceBean的findAnnotatedBeans方法,獲取所有ControllerAdvice的bean,之後排序,所以當有多個ControllerAdivce註解的類且需要排序時,可以實現spring的Order介面來實現。
之後遍歷ControllerAdviceBean,之後獲取Bean的class類,傳入到ExceptionHandlerMethodResolver的構造方法中,如下List-3所示:
List-3
public class ExceptionHandlerMethodResolver {
...
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
(AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null);
...
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
...
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
...
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
detectAnnotationExceptionMappings(method, result);
if (result.isEmpty()) {
for (Class<?> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
}
return result;
}
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
ExceptionHandler ann = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
result.addAll(Arrays.asList(ann.value()));
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}
- 找到方法上有ExceptionHandler註解的方法。
- detectExceptionMappings方法獲取ExceptionHandler的value值,如果我們沒有設定ExceptionHandler的value,那麼遍歷方法的引數,如果引數是Throwable的子類,就將改型別放入result中,所以由此可知道,我們可以不設定ExceptionHandler的value,只需要將方法的引數設定為Throwable的子類即可,spring會自動識別。
- addExceptionMapping方法將結果放入mappedMethods這個map中,key是Throwable,而value則是method。
再回到List-2中,initExceptionHandlerAdviceCache方法中,將構造好的ControllerAdviceBean和ExceptionHandlerMethodResolver放入到exceptionHandlerAdviceCache(是個map)中。
HandlerExceptionResolver是個介面,如下List-4所示:
List-4
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
AbstractHandlerExceptionResolver.resolveException->AbstractHandlerMethodExceptionResolver.doResolveException->ExceptionHandlerExceptionResolver.doResolveHandlerMethodException。
接下來,來看Springmvc中是如何處理我們的ControllerAdvice的。
DispatcherServlet中,doDispatch()->processDispatchResult()->processHandlerException(),如下List-5所示,會遍歷HandlerExceptionResovler來處理。
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
ExceptionHandlerExceptionResolver是如何加入到Springmvc中handlerExceptionResolvers的,是因為DispatcherServlet.properties中HandlerExceptionResolver的值有ExceptionHandlerExceptionResolver,所以會被Spring自動加入進去。
Spring通過上面的方式,將捕獲到的異常交給ExceptionHandlerExceptionResolver.doResolveHandlerMethodException來處理,通過多次轉換,最終呼叫我們設定帶有ExceptionHandler註解的方法。
通過原始碼分析,帶有ControllerAdvice和ExceptionHandler註解的攔截處理的執行先與HandlerInterceptor的afterCompletion。