异常解析器也是springmvc的一个非常重要组件,在编写程序代码时,若抛出异常了,那么springmvc是通过异常解析器来获取异常并进行后续的处理
看下接口定义的方法
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
入参除了request和response,还有发生异常所在的handler,以及异常对象。
在WebMvcSupport中,定义了异常解析器的bean
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
这个bean方法返回的是HandlerExceptionResolverComposite,内部维护着spring容器的异常解析器列表,这里有两个地方需要关注,首先是addDefaultHandlerExceptionResolvers,就是spring会默认添加三个异常解析器,一个是ExceptionHandlerExceptionResolver,这个处理的是程序中注解了@ExceptionHandler的,第二个DefaultHandlerExceptionResolver,这个是处理一些通常的异常,具体可查看官方文档。第三个是较少用的ResponseStatusExceptionResolver
另一个要关注的是extendHandlerExceptionResolvers,这个是留给子类重写,扩展使用的。
来看下最常用的ExceptionHandlerExceptionResolver
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
ContentNegotiationManager mvcContentNegotiationManager) {
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
exceptionHandlerResolver.setResponseBodyAdvice(
Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
if (this.applicationContext != null) {
exceptionHandlerResolver.setApplicationContext(this.applicationContext);
}
exceptionHandlerResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerResolver);
ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
responseStatusResolver.setMessageSource(this.applicationContext);
exceptionResolvers.add(responseStatusResolver);
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
通过这可以看到这个解析器的很多属性和HandlerAdapter的一样,有ArgumentResolvers、ReturnValueHandlers、JsonViewResponseBodyAdvice,这是因为假如程序抛出异常被@ExceptionHandler捕获了,那么程序会执行这个方法,整个流程相当于和@RequestMapping方法执行的处理逻辑一致。因此,还会发现ExceptionHandlerExceptionResolver内部也会维护@ControllerAdvice注解的ResponseBodyAdvice,这个跟进afterPropertiesSet方法便可得知
private void initExceptionHandlerAdviceCache() {
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
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 (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
}
ExceptionHandlerExceptionResolver内部两个map exceptionHandlerAdviceCache维护的是注解了@ControllerAdvice的bean,且存在注解了@ExceptionHandler的方法,responseBodyAdvice维护的就是注解了@ControllerAdvice的ResponseBodyAdvice了
以上便是spring容器对异常解析器的一个实例化过程。接下来,看下程序发生异常springmvc是如何处理的
直接看http的入口DispatcherServlet的doDispatch,当发生异常时,会执行这行代码
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
一直跟进代码会走到HandlerExceptionResolverComposite的resolveException,
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
这里就是循环执行spring容器默认添加的三个异常解析器的resolveException方法,重点关注下最常用的ExceptionHandlerExceptionResolver
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
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 {
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) {
}
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
}
这个方法的逻辑和HandlerAdapter执行Handler的很像,先构造ServletInvocableHandlerMethod ,再添加argumentResolvers和returnValueHandlers,最后再调用ServletInvocableHandlerMethod 的invokeAndHandle方法。
因此我们重点放在第一个步骤构造ServletInvocableHandlerMethod 对象上。
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
//发生异常方法所在的Class对象
handlerType = handlerMethod.getBeanType();
//根据Class对象查询是否有对应的ExceptionHandlerMethodResolver对象
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
//没有的话,生成一个新的ExceptionHandlerMethodResolver对象,并放入缓存中
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
//解析exception对象,若发送异常的Class对象有对应的方法(注解有@ExceptionHandler的,且类型匹配的)
Method method = resolver.resolveMethod(exception);
if (method != null) {
//若有这样的方法,那么直接封装返回
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
//程序走到这里,说明发生异常的controller没有注解@ExceptionHandler方法,需要通过全局异常处理器来执行
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> 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;
}
这个方法主要有两种情况,一种是发生异常所在controller便有注解了@ExceptionHandler方法,;另一种是通过全局异常处理器(@ControllerAdvice)的@ExceptionHandler方法。
对于第一种的,springmvc内部有个缓存exceptionHandlerCache,key是controller的Class对象,value是ExceptionHandlerMethodResolver,防止程序每次发生异常都需要重新new一个ExceptionHandlerMethodResolver对象,ExceptionHandlerMethodResolver根据异常类型获取到对应的method对象。
假如第一种情况没匹配上,那么会执行全局异常处理器,当然了,会判断全局异常处理器是否有拦截了当前发生异常的Handler,advice.isApplicableToBeanType(handlerType)
假如有拦截,那么也会根据ExceptionHandlerMethodResolver 来解析出处理异常的method对象。
当拿到ServletInvocableHandlerMethod 对象后,剩下的执行逻辑可参考HandlerAdapter执行正常程序代码,未抛出异常的逻辑
假如所有的异常解析器都没匹配到此次http请求发生的异常,那么springmvc容器会抛出NestedServletException,程序又会重新走一次DispatcherServlet的doDispatch方法,springmvc会返回Internal Server Error这样的错误信息