关于springboot的异常处理以及源码分析(二)

在上一篇文章关于springboot的异常处理以及源码分析(一)我们通过官方文档起手,看到了springboot对于异常的处理大纲。以及我们分析了一些细节,这里我们将再次回顾官方文档,把其余的内容进行剖析,以及其源码逻辑都会做一个梳理。

一、自定义异常

我们上一篇文章通过自己去配置4xx 5xx的页面来实现了自定义异常页面的跳转。但是我们看到文档中还提供了一些其他的自定义异常处理的逻辑,我们这里就来一一解析。

1、@ControllerAdvice注解+@ExceptionHandler注解

1.1、案例演示

在官方文档中有这么一段描述。
在这里插入图片描述
翻译过来就是您还可以定义一个带注释的类来@ControllerAdvice定制 JSON 文档以返回特定的控制器和/或异常类型。如果YourException在与 相同的包中定义的控制器抛出,则使用 POJOAcmeController的 JSON 表示形式。
换句话就是说,如果我们用@ControllerAdvice注解标注了一个类,并且指定了扫描包的范围(不指定就是全部的),那么这个路径下的所有异常都会抛到这里,并且根据你@ExceptionHandler标注的异常类型进行匹配,能匹配上的,就会来到这里来处理异常,返回异常视图。进而我们就可以自己定义异常视图了。那么我们来测试一下。

  • 1、我们先定义一个我们自己异常,以后我们业务中都抛出这个异常然后统一处理
public class MyException extends RuntimeException {
   
	// 省略构造
}
  • 2、然后我们再定义一个异常页面,controllerAdviceHtml.html我们待会让他统一跳到我们这个页面。这个页面异常简单,就一个一级标题。放在静态资源目录下面就行。
    在这里插入图片描述
<!DOCTYPE html>
<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>SSE Chat</title>
	</head>
	<body>
		<h1>AcmeControllerAdviceHtml</h1>
	</body>
</html>
  • 3、接着我们就来定义一个全局异常处理器。
// 异常处理我们自己的controller,实际你不配就是整个包
@ControllerAdvice(basePackageClasses = MyController.class)
public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
   

	// 我们这里处理的异常就是我们自己定义的异常MyException,实际开发,这里可以配置多个,然后根据异常类型来处理,你自己灵活处理就行
	@ExceptionHandler(MyException.class)
	String handleControllerException(HttpServletRequest request, Throwable ex) {
   
		// 返回我们的视图名称,如果正常,他就会跳转去这个页面,模型就是视图,直接返回字符串
		return "controllerAdviceHtml";
	}
}
  • 4、最后我们再来定义一个controller,然后接口抛出我们自己的异常
    看看能不能被这个全局异常处理器处理到。
@RestController
@RequestMapping("/test")
public class MyController {
   
	@GetMapping("testControllerAdvice")
	public String testControllerAdvice() {
   
		// 测试 ControllerAdvice,抛出我们自己的异常
		throw new MyException("testControllerAdvice error");
	}
}

好了,我们来在页面发起这个get请求。
在这里插入图片描述
于是我们验证得到了这个玩意他是好使的,那么为什么好使呢,我们就再来看一下源码。

1.2、源码分析,ExceptionHandlerExceptionResolver登场

在分析之前,我先来梳理一下我们上一篇文章的源码逻辑。后面的几种异常我们就不梳理了,但是最后我会给出完整的流程图。

# 异常源码梳理
1、org.springframework.web.servlet.DispatcherServlet#doDispatch
首先我们在DispatcherServlet的doDispatch方法中找到了真正执行我们目标方法(也就是你的接口)的地方。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

2、在目标方法抛出异常,之后他捕获了异常
// 把异常保存在了dispatchException 中
dispatchException = new NestedServletException("Handler dispatch failed", err);

3、然后在processDispatchResult开始处理异常
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

4、最后来到org.springframework.web.servlet.DispatcherServlet#processHandlerException
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
	exMv = resolver.resolveException(request, response, handler, ex);
	if (exMv != null) {
		break;
	}
}
在这里他遍历所有的异常处理器,然后逐个处理该异常,一旦有一个处理器能处理,就直接跳出了。OK
我们先就过到这里。

我们说他是遍历异常处理器的然后挨个处理的,那么实际上我们上一篇文章在debug的时候看到了这些异常处理器如下图,我们看到其实一共是四个处理器,其中第一个是存放一些页面能返回啥的,并不决定是哪个处理器。
在这里插入图片描述
但是我们在上文debug的时候发现,下面三个啥也没干,就直接过去了。最后走了个白页。算了我不卖关子了,直接来说,下面三个处理器就是处理你自定义异常处理的逻辑。我们这里就来debug看一下。我们直接把断点打在这里,然后发起请求。
在这里插入图片描述
然后我们来到这里的时候发现依然是那四个异常处理器,其中上面那个依然啥也没干,下面三个是一组。我们来到下面三个的处理。我们debug进来来到这里org.springframework.web.servlet.handler.HandlerExceptionResolverComposite#resolveException()
在这里插入图片描述
下面就来遍历这三个处理器,看看能不能处理,能就直接返回视图跳出了。
其中第一个处理器就是ExceptionHandlerExceptionResolver,然后就进入了他的resolveException方法,进行异常的处理。

public ModelAndView resolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
   
	// 这里是判断,这个异常处理器,能不能处理这次请求的异常,这里判断可以处理
	if (shouldApplyTo(request, handler)) {
   
		prepareResponse(ex, response);
		/**
			来到这里处理这个异常,处理逻辑很长,但是主要逻辑就是通过你这个异常
			去判断哪个处理器可以处理,通过注解标记的异常类型,发现是不是能处理
			如果能处理,底层会通过反射调用我们那个方法
			smoketest.simple.config.AcmeControllerAdvice#handleControllerException
			然后返回一个视图,如图1.2.1
		*/
		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 " +
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值