SpringMVC中的异常处理器
SpringMVC中的异常处理器
一、概述
在使用SpringMVC的过程中,应用系统通常都会有需要统一处理未捕获异常的需求,为了将异常处理的逻辑与业务逻辑代码分离开,SpringMVC提供了@ExceptionHandler 统一异常处理的方式。
@ControllerAdvice+@ExceptionHandler是一起使用的,这样我们就可以在集中的地方处理未知异常,打印对应日志,封装返回结果等。
系统开发中处理异常的思路:
- dao异常通常抛给Service
- Service异常通常抛给Controller
- Controller把异常抛给前端控制器(SpringMVC框架)
- 由前端控制器把异常交给异常处理器进行处理
二、异常处理器初始化位置
默认策略
在DispatcherServlet这个类中,然后在initStrategies中完成了异常处理器的初始化:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
//初始化异常处理器
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
看到这里,应该可以猜出大概的实现原理了。
非常类似HandlerMapping和HandlerAdapter的初始化过程:
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
// 从容器中查找是否有HandlerExceptionResolver类型的异常解析器
Map<String, HandlerExceptionResolver> 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.
}
}
// 再从DispatcherServlet.properties中找到默认的HandlerExceptionResolver
// 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");
}
}
}
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
@ControllerAdvice工作原理
但是这个和我们的@ControllerAdvice+@ExceptionHandler注解没有找到任何的相关关系?
RequestMappingHandlerAdapter
经过网上资料查找,可以看到RequestMappingHandlerAdapter组件是实现了InitializingBean接口的,这个时候会来执行接口中的afterPropertiesSet方法。
直接来到:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
// 重点是在这里
initControllerAdviceCache();
// 尽管不是当前的内容,但是可以看看都是在哪里来进行初始化的
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
那么来看一下initControllerAdviceCache方法
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// 直接从IOC容器中查找类上添加了@ControllerAdvice注解的bean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
// 开始来对集合中的@ControllerAdvice类型的bean进行遍历
for (ControllerAdviceBean adviceBean : adviceBeans) {
// 获取得到@ControllerAdvice类型的类型
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 收集没有@RequestMapping注解但是存在@ModelAttribute注解的方法
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
// 收集存在@InitBinder注解的方法
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
// 收集RequestBodyAdvice接口和ResponseBodyAdvice接口的bean添加到集合中
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
// print log
}
这里将类上存在ControllerAdvice注解的bean进行分类:
- 1、方法上存在@ModelAttribute注解且没有@RequestMapping注解;
- 2、方法上存在@InitBinder注解;
- 3、类是RequestBodyAdvice的子类;
- 4、类是ResponseBodyAdvice的子类;
但是始终没有看到@ControllerAdvice+@ExceptionHandler注解的影子。
ExceptionHandlerExceptionResolver
因为ExceptionHandlerExceptionResolver实现了InitializingBean接口,所以重写了其中的方法org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
// 看这个方法即可
initExceptionHandlerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// 遍历IOC容器,找到加了@ControllerAdvice注解的bean,解析其中注解成为ControllerAdviceBean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
// 循环遍历每个@ControllerAdvice注解bean的原始类型
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);
}
}
}
}
那么来看一下ExceptionHandlerMethodResolver的创建流程
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// 循环遍历类上存在@ExceptionHandler的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
// 1、获取得到@ExceptionHandler注解中的异常类型
// 2、没有如果指定异常类型,那么遍历方法参数类型,将是异常类型的参数添加进俩
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
看看具体的添加异常的过程:
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
// KEY为具体的异常,value是具体处理异常的方法
// 但是这里是判断如果有重复的方法处理重复的异常是不可以的
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}
找到了处理异常的方法之后,然后看下一步的处理方式:
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
// 如果mappedMethods不为空
if (resolver.hasExceptionMappings()) {
// 将对应的异常处理类型和解析器保存起来
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
// 如果是当前类型的添加到另外一个集合中来
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
if (logger.isInfoEnabled()) {
logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
}
}
那么容器中此时就有了这么多的异常处理器了。
在ExceptionHandlerExceptionResolver中的成员属性中
Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();
那么到底是在哪里发挥作用的呢?肯定是在异常处理阶段!!
那么来制造一个错误请求:
@RestController
@RequestMapping(path = "/intecepter")
public class IntecepterController {
@GetMapping(path = "/test")
public String test(){
int i = 1 / 0;
return "success";
}
}
然后看一下对应的异常处理器:
@RestControllerAdvice
public class CustomControllerAdvice {
@ExceptionHandler(value = {Exception.class})
public String handlerException(Exception exception){
System.out.println("对应的异常信息是:"+exception);
return "success";
}
}
既然初始化已经看过了,那么接下来我们只需要看如何从exceptionHandlerAdviceCache中取出来对应的ExceptionHandlerMethodResolver中的方法来进行处理异常即可。
利用断点来进行说明:直接来到org.springframework.web.servlet.DispatcherServlet#processDispatchResult方法中
然后来到:org.springframework.web.servlet.DispatcherServlet#processHandlerException中
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
紧接着来到org.springframework.web.servlet.handler.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;
}
然后来到:
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) {
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
然后来到org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException方法中来:
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}
然后来到:
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// 如何找到的呢?
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
// ......
}
来到org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#getExceptionHandlerMethod方法中来:
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// 获取得到异常发生所在controller的类
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);
}
// ......
return null;
}
再来到org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException方法中来,找到如下代码:
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
来到org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle方法中来:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 可以看到providedArgs参数中包含:exception和handlerMethod
// 这也是方法参数中可以书写的内容
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// ......
}
开始利用反射来调用方法,最终执行到了@ControllerAdvice标注了的方法中来。