在Spring里,我们可以使用@ControllerAdvice来声明一些全局性的东西,其用法主要有以下三点:
1、@ExceptionHandler注解标注的方法:用于捕获Controller中抛出的不同类型的异常,从而达到异常全局处理的目的;
2、@ModelAttribute注解标注的方法:表示此方法会在执行目标Controller方法之前执行;
3、@InitBinder注解标注的方法:用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;
在我们日常的开发过程中,通常会根据业务定义属于自己的异常,所以通过定制自己的异常处理器,来处理项目中大大小小、各种各样的异常。目前最常用的方式应该是使用@ControllerAdvice+@ExceptionHandler的组合来实现。
@ControllerAdvice
@ResponseBody
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ExceptionControllerAdvice {
@ExceptionHandler(BaseException.class)
public ResultVO
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/exception")
public void testException() {
throw new BusinessException("异常测试");
}
}
BusinessException继承自BaseException,业务逻辑中抛出异常,最终会走到ExceptionControllerAdvice中的baseException方法中,最终封装成ResultVO对象返回给前台
我们直接看初始化时的核心代码,DispatcherServlet类中的initStrategies
//DispatcherServlet
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
//异常处理组件,重点看这个
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
我们重点看第8行initHandlerExceptionResolvers方法
//DispatcherServlet
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
该方法是找到所有HandlerExceptionResolver接口的实例,并保存到handlerExceptionResolvers中,其中有一个类型是ExceptionHandlerExceptionResolver的bean,我们看看它的类图
可以看到实现了InitializingBean接口,看过Spring源码的都应该知道,实现了InitializingBean接口的bean在初始化过程中会调用其afterPropertiesSet方法,我们看下
//ExceptionHandlerExceptionResolver
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
initExceptionHandlerAdviceCache();
if (this.argumentResolvers == null) {
List resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
重点看第5行initExceptionHandlerAdviceCache方法
//ExceptionHandlerExceptionResolver
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
//找到所有@ControllerAdvice注解的bean,并封装成ControllerAdviceBean对象
List 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);
}
//把bean的类型封装成一个解析器,会把异常和对应的处理方法放到mappedMethods中
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
//把bean和对应的解析器放到exceptionHandlerAdviceCache里
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
if (logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " +
handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
}
}
}
这一步找到所有@ControllerAdvice注解的bean,并封装成解析器,放到exceptionHandlerAdviceCache里,记住这个exceptionHandlerAdviceCache,会在真正业务调用时用到
我们从前端控制器DispatcherServlet的核心方法doDispatch开始看
//DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//对调用结果处理,重点看这里
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
我们看54行,这里已经完成的业务的调用,如果系统出现异常,会被捕获,赋值到dispatchException,最后传入processDispatchResult方法,我们重点看下processDispatchResult方法
//DispatcherServlet
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//如果有异常,异常不为空
if (exception != null) {
//判断异常是否为ModelAndViewDefiningException的实例
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//如果不是ModelAndViewDefiningException的实例
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.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
可以看到9~22行是存在异常时,对异常的处理,重点看下19行processHandlerException方法
//DispatcherServlet
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
//这个handlerExceptionResolvers就是我们初始化时设置的
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
//这里遍历每一个HandlerExceptionResolver,尝试去处理异常,结果如果不为空,表示处理成功
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
看第10~20行,可以看到通过遍历每一个HandlerExceptionResolver去尝试处理异常,结果如果不为空,表示处理成功,这里的handlerExceptionResolvers有两个,实际处理的是HandlerExceptionResolverComposite,我们继续进去看
//HandlerExceptionResolverComposite
@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler,Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
//这里遍历每一个HandlerExceptionResolver,尝试去处理异常,结果如果不为空,表示处理成功
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
这里和上一步一样,遍历每一个HandlerExceptionResolver,尝试去处理异常,结果如果不为空,表示处理成功,这里的HandlerExceptionResolver有三个,我们看下ExceptionHandlerExceptionResolver的resolveException方法
我们发现ExceptionHandlerExceptionResolver的resolveException方法在其父类AbstractHandlerExceptionResolver里,继续进去
//AbstractHandlerExceptionResolver
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print warn message when warn logger is not enabled...
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// warnLogger with full stack trace (requires explicit config)
logException(ex, request);
}
return result;
}
else {
return null;
}
}
继续第9行doResolveException进去,到AbstractHandlerMethodExceptionResolver的doResolveException方法中
//AbstractHandlerMethodExceptionResolver
@Override
@Nullable
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}
继续点击doResolveHandlerMethodException进去,最终来到ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法
//ExceptionHandlerExceptionResolver
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
//这一步是核心方法,获取处理异常的HandlerMethod,也就是我们一开始配置的方法
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
try {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
Throwable cause = exception.getCause();
if (cause != null) {
// Expose cause as provided argument as well
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
}
else {
// Otherwise, just the given exception as-is
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
}
catch (Throwable invocationEx) {
// Any other than the original exception is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (invocationEx != exception && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}
第8行是核心方法,获取处理异常的HandlerMethod,也就是我们一开始配置的方法,点进去
//ExceptionHandlerExceptionResolver
@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class> handlerType = null;
if (handlerMethod != null) {
//先获取局部异常处理对象(就是在当前请求所在Controller类中的@ExceptionHandler对象)
handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
//根据异常类型获取其中匹配的异常处理
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
//是否代理类
if (Proxy.isProxyClass(handlerType)) {
//如果是代理类,获取到到实际的被代理类的类型
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
//遍历当前容器内controllerAdvice的集合,其实只有一个controllerAdvice,就是我们前面配置的全局异常处理器
//exceptionHandlerAdviceCache就是之前初始化的过程中设置的
for (Map.Entry entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
if (advice.isApplicableToBeanType(handlerType)) {
//获取解析器
ExceptionHandlerMethodResolver resolver = entry.getValue();
//根据异常类型获取其中匹配的异常处理
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}
return null;
}
getExceptionHandlerMethod方法可以分为以下几个步骤
先获取局部异常处理对象(就是在当前请求所在Controller类中的@ExceptionHandler对象)
如果没找到,再通过全局异常处理器去处理
我们重点看36行resolveMethod方法是怎么获取匹配的异常处理的,点击进到ExceptionHandlerMethodResolver类中,一直往下,来到getMappedMethod这个重要的方法中
@Nullable
private Method getMappedMethod(Class extends Throwable> exceptionType) {
List> matches = new ArrayList<>();
//遍历该解析器下对应的异常处理方法的key,mappedMethods在初始化的时候会赋值
for (Class extends Throwable> mappedException : this.mappedMethods.keySet()) {
//判断遍历的异常是否等于传入的异常,或者是其父类、超类
if (mappedException.isAssignableFrom(exceptionType)) {
//是的话,加入到集合里
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
//根据自定义的排序规则进行排序
matches.sort(new ExceptionDepthComparator(exceptionType));
//排序后取第一条
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}
getMappedMethod方法可以分为以下几个步骤
遍历mappedMethods(异常和处理方法的映射关系),找到匹配的处理方法
如果找到了匹配的方法,按照异常的深度排序,取出第一个返回
到此,我们走完所有流程