开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第27天,点击查看活动详情
前言
在上一篇文章中,我们简单说明了如何使用@RestControllerAdvice与@ExceptionHandler一起完成统一异常处理,但是我们目前还不知道@ExceptionHandler到底是如何实现的;在这篇文章中,我们准备对其中的原理一探究竟,以便我们能够更好地理解如何运用这两个注解;
收集@ExceptionHandler注解的方法
我们首先来看一下Spring是如何收集注解了@ExceptionHandler的方法的,我们找到ExceptionHandlerExceptionResolver.afterPropertiesSet()方法,因为ExceptionHandlerExceptionResolver实现了InitializingBean,所以在ExceptionHandlerExceptionResolver初始化完毕后立马会执行afterPropertiesSet():
public void afterPropertiesSet() {
// 收集ExceptionHandler方法
this.initExceptionHandlerAdviceCache();
......
}
我们来看看具体是如何收集的:
private void initExceptionHandlerAdviceCache() {
if (this.getApplicationContext() != null) {
// 找到所有被@ControllerAdvice注解的Bean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
Iterator var2 = adviceBeans.iterator();
// 遍历所有的Bean
while(var2.hasNext()) {
ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next();
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
// 找到被@ExceptionHandler注解的Bean,并放进exceptionHandlerAdviceCache缓存
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
if (this.logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
this.logger.debug("ControllerAdvice beans: none");
} else {
this.logger.debug("ControllerAdvice beans: " + handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
}
}
}
}
上面源码就做了以下2步:
1.找到所有的
ControllerAdvice Bean对象,其实就是被@ControllerAdvice注解的类;2.遍历所有的
ControllerAdvice Bean对象,并找出其中被@ExceptionHandler注解的Bean,因为并不是所有的ControllerAdvice Bean对象都会有@ExceptionHandler注解;
异常拦截处理
前面我们把异常处理方法全部收集好了后,接下来就要开始找到exceptionHandlerAdviceCache在哪里被拿来使用了,我们可以看到DispatcherServlet.doDispatch()方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try{
......
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
......
}catch(Exception e){
dispatchException = e;
}catch (Throwable t){
dispatchException = new NestedServletException("Handler dispatch failed", t);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
......
}
上面代码中,ha.handle()就是执行我们的Controller中的目标方法,Controller抛出的任何异常都会被捕捉到,最后把异常给dispatchException赋值,然后再执行processDispatchResult()方法来处理异常:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChainmappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
boolean errorView = false;
// 有异常
if (exception != null) {
// 如果是返回的ModelAndViewDefiningException,那么直接获取ModelAndView
if (exception instanceof ModelAndViewDefiningException) {
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException)exception).getModelAndView();
} else {
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
// 调用异常处理方法
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}
......
}
1.如果
Controller抛出了ModelAndViewDefiningException,那么直接从里面提取ModelAndView返回;2.如果抛出的异常不是
ModelAndViewDefiningException,那么只能通过异常处理方法来操作;
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Objecthandler, Exception ex) throws Exception {
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
Iterator var6 = this.handlerExceptionResolvers.iterator();
// 遍历异常处理器
while(var6.hasNext()) {
HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
// 调用异常处理方法
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
......
}
小结
至此,我们就通过源码解析的方式了解了@ExceptionHandler的工作原理,它通过ExceptionHandlerExceptionResolver的初始化操作解析了所有被@ExceptionHandler注解的方法,并包装成ExceptionHandlerMethodResolver;另外在Controller被调用后,将根据抛出的异常来选择对应的ExceptionHandlerMethodResolver来处理;