首页 > 其他分享 >SpringMVC中的异常处理器

SpringMVC中的异常处理器

时间:2023-03-07 18:00:10浏览次数:68  
标签:SpringMVC servlet adviceBean 处理器 new null 异常 method

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标注了的方法中来。

标签:SpringMVC,servlet,adviceBean,处理器,new,null,异常,method
From: https://www.cnblogs.com/likeguang/p/17188980.html

相关文章

  • 6 异常机制
    异常机制1什么是异常软件程序在运行过程中,非常可能遇到刚刚提到的这些异常问题,我们叫异常,英文是:Exception,意思是例外。异常指程序运行中出现的不期而至的各种......
  • Android异常重启并进入Recovery(恢复)模式的原因之一
    1、init.rc简述init.rc文件由系统第一个启动的init程序解析,此文件由语句组成,主要包含了四种类型的语句:Action,Commands,Services,Options.2、服务(services)服务是指那些需......
  • 8_SpringMVC
    SpringMVCSpringMVC技术和Servlet技术功能等同,均属于web层开发技术学习目标掌握基于SpringMvc获取请求参数与响应json数据操作熟练应用基于REST风格的请求路径设置与......
  • 将springmvc.xml配置到resources目录
    web.xml配置<?xmlversion="1.0"encoding="UTF-8"?><web-appxmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-in......
  • 为什么单个测试结果正确,连续测试结果异常?
    单个跑正常,多个跑异常;因为上一个测试的输入内容还没有被读完就goto/break/continue了。这种情况,需要读取上一个输入中剩余的部分。例子:std::getline(std::cin,s);//......
  • 异常
    异常1、什么是异常哪些不是异常语法错误逻辑错误不可预知的非正常的情况例如:网络中断,用户不合适的输入,硬盘已满,操作系统崩溃,内存溢出等2、异常的体系结......
  • 自定义异常
    自定义异常(日常用不到)自定义异常方法:继承异常类等价于我们创造一个类,可以在里面处理产生异常后拥有的逻辑自定义异常:publicclassExceptionGeekLeeextendsExcepti......
  • 异常处理
    异常异常定义异常指的是程序运行过程中出现不期而遇的各种状况,如:文件找不到,网络断开等所有异常的超类:java.lang.Throwable,分为Error和Exception两大类,前者是致命的,一般......
  • 使用注解开发SpringMVC,也是以后开发的模板(重点)
    注解版配置SpringMVC(重点)第一步:新建一个moudel,添加web支持!建立包结构top.lostyou.controller第二步:由于maven可能存在资源过滤问题,我们将配置完善<!--在build中......
  • springmvc配置文件
    <?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"......