mvc 异常处理源码解析(3)

目录

准备

  1. 准备一个controller类, 里面抛出一个异常
java 复制代码
@RestController
@RequestMapping("/hello")
public class HelloController {


    @GetMapping("/h")
    public String hello(HttpServletResponse response) throws ArithmeticException {

        try {
            int a = 1 / 0;
        } catch (Exception e) {
            throw e;
        }
        return "hello";
    }

}
  1. 配置一个全局异常处理类
java 复制代码
/**
 * 异常处理器
 */
@RestControllerAdvice
public class BDExceptionHandler {

    /**
     * 参数异常
     * @param e 异常
     * @return BaseResult
     */
    @ExceptionHandler(ArithmeticException.class)
    public R doBaseExceptionHandler(ArithmeticException e) {
        return R.error(e.getMessage());
    }
}
  1. 接下来访问url, 跟踪源码

源码跟踪

进入到mvc的入口类DispatcherServlet的 doDispatch方法, 可以发现整体逻辑被try catch包围, 当第3步抛出异常之后就会被捕获, dispatchException对象则不为空,最后进入processDispatchResult方法进行处理

java 复制代码
	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 {
				//1.获取处理器链路
				//2.执行拦截器前置方法
				//3.调用controller方法, 返回结果集
				//4.执行拦截器后置方法
			}
			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()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

进入processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

java 复制代码
	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 {
				//处理其他所有的异常
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
		//省略下面代码

进入mv = processHandlerException(request, response, handler, exception);

java 复制代码
	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;
		if (this.handlerExceptionResolvers != null) {
			//this.handlerExceptionResolvers里面默认有两个处理异常的类
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				// 遍历,判断使用那个类来处理
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		if (exMv != null) {
			//省略代码
			return exMv;
		}

		throw ex;
	}

看一下上面for循环里面的this.handlerExceptionResolvers对象的结构 :

DefaultErrorAttributes : 没做任何处理, 只是将异常设置到request对象的属性当中;

HandlerExceptionResolverComposite : 里面有三个异常处理类, 分别的作用是 :

ExceptionHandlerExceptionResolver:基于 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 实现类。例如通过 @ControllerAdvice 注解自定义异常处理器,加上@ExceptionHandler注解指定方法所需要处理的异常类型
ResponseStatusExceptionResolver:基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类。例如在方法上面添加 @ResponseStatus 注解,指定该方法发生异常时,需要设置的 code 响应码和 reason 错误信息
DefaultHandlerExceptionResolver:默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码。例如 HTTP Method 不支持,则在这个实现类中往响应中设置错误码和错误信息

原文链接:https://blog.csdn.net/Running666/article/details/130786561

进入HandlerExceptionResolverComposite类的resolveException(); 遍历上面的三种异常处理类, 判断应该使用哪个处理

java 复制代码
	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;
	}

由于只设置了第一种异常处理方式, 所以异常会由ExceptionHandlerExceptionResolver来处理, 进入handlerExceptionResolver.resolveException()

java 复制代码
	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 debug message when warn logger is not enabled.
				if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
					logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
				}
				
				logException(ex, request);
			}
			return result;
		}
		else {
			return null;
		}
	}

进入doResolveException(request, response, handler, ex)方法;

java 复制代码
	@Override
	@Nullable
	protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
			HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
			
		//根据controller类和异常来判断由哪个类来处理这个异常
		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();
		ArrayList<Throwable> exceptions = new ArrayList<>();
		try {
			//省略代码
			Object[] arguments = new Object[exceptions.size() + 1];
			exceptions.toArray(arguments);  // efficient arraycopy call in ArrayList
			arguments[arguments.length - 1] = handlerMethod;
			//反射调用异常处理类
			exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
		}
		catch (Throwable invocationEx) {
			// Any other than the original exception (or a cause) is unintended here,
			// probably an accident (e.g. failed assertion or the like).
			if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
				logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
			}
			// Continue with default processing of the original exception...
			return null;
		}
		//省略
	}

主要逻辑就是看如何找到处理对应异常的方法, 进入getExceptionHandlerMethod(handlerMethod, exception);

进入到了ExceptionHandlerExceptionResolver 类中, 在这个类里面有两个关键的属性,exceptionHandlerCache和exceptionHandlerAdviceCache, 两个都是map, exceptionHandlerAdviceCache的初始化下面再说,exceptionHandlerCache的初始化想知道的自己去查一下吧

java 复制代码
	private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
			new ConcurrentHashMap<>(64);

	private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
			new LinkedHashMap<>();

exceptionHandlerCache里面存的就是在当前controller里面包含@ExceptionHandler注解的方法, key: controller类, value : 方法包装类
exceptionHandlerAdviceCache里面存的就是标注了@ControllerAdvice注解的类,里面带有@ExceptionHandler注解的方法, key:

@RestControllerAdvice注解标注的类, value: 方法包装类

java 复制代码
	@Nullable
	protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
			@Nullable HandlerMethod handlerMethod, Exception exception) {

		Class<?> handlerType = null;
		//从exceptionHandlerCache获取异常对应的处理方法, 由于没有配置, 肯定获取不到
		if (handlerMethod != null) {
			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());
			}
		}
		//遍历全局的异常处理类, 看是否可以获取到对应的处理方法
		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;
	}

进入Method method = resolver.resolveMethod(exception);, 最终进入到resolveMethodByExceptionType()方法, 在ExceptionHandlerMethodResolver类中

ExceptionHandlerMethodResolver 中有两个关键属性mappedMethods,和exceptionLookupCache, 两个也都是map, 两个都是根据异常获取对应的异常处理方法, mappedMethods在程序启动时候初始化, exceptionLookupCache相当于一个缓存,为了提高查询速度

java 复制代码
	@Nullable
	public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
		//从缓存中获取处理方法, 如果获取不到,返回null(第一次执行此方法肯定获取不到)
		Method method = this.exceptionLookupCache.get(exceptionType);
		if (method == null) {
			//如果获取不到, 则去mappedMethods获取
			method = getMappedMethod(exceptionType);
			//将获取的结果缓存到exceptionLookupCache中
			this.exceptionLookupCache.put(exceptionType, method);
		}
		return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
	}

进入getMappedMethod(exceptionType);

java 复制代码
	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
		List<Class<? extends Throwable>> matches = new ArrayList<>();
		//遍历所有可以处理的异常, 判断与抛出的异常是否匹配
		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
			if (mappedException.isAssignableFrom(exceptionType)) {
				matches.add(mappedException);
			}
		}
		if (!matches.isEmpty()) {
			//如果有多个方法可以匹配来处理异常,则进行排序
			if (matches.size() > 1) {
				matches.sort(new ExceptionDepthComparator(exceptionType));
			}
			//获取第一个方法来处理异常,返回方法
			return this.mappedMethods.get(matches.get(0));
		}
		else {
			return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
		}
	}

到此为止, 已经获取到对应的处理异常的方法了, 在上面的doResolveHandlerMethodException方法中, 最后通过反射调用该方法, 处理异常

接下来讨论一下ExceptionHandlerExceptionResolver类里面的exceptionHandlerCache和exceptionHandlerAdviceCache初始化和

ExceptionHandlerMethodResolver类中的mappedMethods初始化

ExceptionHandlerExceptionResolver初始化

ExceptionHandlerExceptionResolver实现了InitializingBean接口, 猜测初始化肯定在afterPropertiesSet()方法中, 我们至于要查找ExceptionHandlerExceptionResolver是如何注入到springboot容器中的, 及afterPropertiesSet()方法逻辑即可

ExceptionHandlerExceptionResolver注入

实际上,ExceptionHandlerExceptionResolver并不会成为bean交给Spring容器管理。但是在WebMvcConfigurationSupport初始化过程中,会手动调用afterPropertiesSet()进行默认初始化

还记得上面HandlerExceptionResolverComposite类里面有三个异常处理类吗? 看一下他们是如何注入的

在WebMvcConfigurationSupport类里面的handlerExceptionResolver()方法:

java 复制代码
	@Bean
	public HandlerExceptionResolver handlerExceptionResolver(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
		List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
		configureHandlerExceptionResolvers(exceptionResolvers);
		//如果是空, 在下面的if里面将三个异常处理类添加到集合中
		if (exceptionResolvers.isEmpty()) {
			addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
		}
		extendHandlerExceptionResolvers(exceptionResolvers);
		HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
		composite.setOrder(0);
		//赋值属性, 最后返回
		composite.setExceptionResolvers(exceptionResolvers);
		return composite;
	}

进入到addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);

java 复制代码
	protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
			ContentNegotiationManager mvcContentNegotiationManager) {
		//创建ExceptionHandlerExceptionResolver 对象
		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);
		}
		//手动调用afterPropertiesSet()给属性初始化
		exceptionHandlerResolver.afterPropertiesSet();
		//添加第一个
		exceptionResolvers.add(exceptionHandlerResolver);

		ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
		responseStatusResolver.setMessageSource(this.applicationContext);
		//添加第二个
		exceptionResolvers.add(responseStatusResolver);
		//添加第三个
		exceptionResolvers.add(new DefaultHandlerExceptionResolver());
	}

所以我们只需要看WebMvcConfigurationSupport是如何注入的就可以了, 实际上他是通过WebMvcAutoConfiguration类来间接注入的

而WebMvcAutoConfiguration类的注入是通过自动装配来注册的

ExceptionHandlerExceptionResolver中exceptionHandlerAdviceCache初始化

java 复制代码
	@Override
	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);
		}
	}

进入initExceptionHandlerAdviceCache()方法

java 复制代码
	private void initExceptionHandlerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}
		//获取带有ControllerAdvice注解的类
		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);
			}
			//构建value对象, 存入到exceptionHandlerAdviceCache中
			//ExceptionHandlerMethodResolver中mappedMethods初始化逻辑也在其中
			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
			if (resolver.hasExceptionMappings()) {
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
			}
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				this.responseBodyAdvice.add(adviceBean);
			}
		}
	}

ExceptionHandlerMethodResolver中mappedMethods初始化

mappedMethods的初始化逻辑很简单, 在上面的initExceptionHandlerAdviceCache()方法中我们已经找到了带有ControllerAdvice的所有类, 将类的类型作为参数创建了ExceptionHandlerMethodResolver对象, mappedMethods的初始化就在构造方法中

进入ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType) 方法

java 复制代码
	public ExceptionHandlerMethodResolver(Class<?> handlerType) {
		//获取到handlerType类中所有的带有ExceptionHandler注解的方法,
		for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
			//解析ExceptionHandler注解中的属性, 因为可以配置多个异常类型, 所有返回的是集合, 进行遍历
			for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
				//将异常类型作为key, 方法作为value, 存入到mappedMethods中
				addExceptionMapping(exceptionType, method);
			}
		}
	}

	private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
		List<Class<? extends Throwable>> result = new ArrayList<>();
		//获取method上的ExceptionHandler注解的所有的异常类型,存入到result中, 返回
		detectAnnotationExceptionMappings(method, result);
		if (result.isEmpty()) {
			for (Class<?> paramType : method.getParameterTypes()) {
				if (Throwable.class.isAssignableFrom(paramType)) {
					result.add((Class<? extends Throwable>) paramType);
				}
			}
		}
		if (result.isEmpty()) {
			throw new IllegalStateException("No exception types mapped to " + method);
		}
		return result;
	}


	private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
		Method oldMethod = this.mappedMethods.put(exceptionType, method);
		if (oldMethod != null && !oldMethod.equals(method)) {
			throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
					exceptionType + "]: {" + oldMethod + ", " + method + "}");
		}
	}

结尾

上面已经说了到逻辑代码发生异常之后, 我们自定义的异常处理类如何加载处理这些异常(同时包括系统启动时的一些系统异常), 但是如果抛出一种没有处理类的异常会怎么样呢? 下篇文章在讨论一下

相关推荐
Predestination王瀞潞8 小时前
MVC 架构→分层架构→微服务架构 架构演进
微服务·架构·mvc
Lyyaoo.2 天前
Spring MVC 与三层架构
spring·架构·mvc
xiaodaidai丶3 天前
解决Sa-Token在 Spring MVC + WebFlux 混合架构中流式接口报错SaTokenContext 上下文尚未初始化的问题
spring·架构·mvc
xiaodaidai丶3 天前
Spring Web MVC的异步请求解读
spring boot·spring·mvc
Thomas.Sir3 天前
SpringMVC 工作原理深入解析
spring·设计模式·mvc·spring mvc
毅炼3 天前
JVM常见问题总结(2)
java·jvm·mvc
Lyyaoo.4 天前
Spring MVC中用于处理HTTP请求的常用注解
spring·http·mvc
刀法如飞16 天前
一款Go语言Gin框架MVC脚手架,满足大部分场景
go·mvc·gin
莫寒清17 天前
Spring MVC:@PathVariable 注解详解
java·spring·mvc