SpringMVC异常处理流程总结

一、异常处理方式一:@ControllerAdvice+@ExceptionHandler

        (1)使用方法

        通过@ControllerAdvice+@Exception的方式便可以指定在请求处理的整个流程中如果出现了@ExceptionHandler注解中指定的这些异常,便可以通过该@ExceptionHandler所标注的方法来处理该类异常,该类中的方法同常规的Controller中的方法一致,如果返回String便会找到相对应的页面进行渲染,如果类上标注了@RestControllerAdvice返回类型为String则会返回JSON字符串。

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler({ArithmeticException.class, NullPointerException.class})
    public String handleException() {
        return "error";
    }
    
}

       (2)原理分析

        SpringMVC的核心组件是DispatcherServlet,而DispatcherServlet的核心处理流程全部在doDispatch方法中,而在这个方法的所有核心步骤中只要抛出了异常都会封装到Exception类型的dispatchException这个变量中,并将该对象传入到processDispatchResult方法中。该方法中会判断是否有异常传入,有异常传入则会调用processHandlerException方法来获取视图,processHandlerException会遍历所有实现了HandlerExceptionResolver接口类的resolveException方法看有没有一个类可以处理该异常。

        总结一下:doDispatch方法的核心流程中抛出异常catch住异常存入dispatchException变量调用processDispatchResult调用processHandlerException遍历所有实现了HandlerExceptionResolver接口类看能否处理当前异常能处理返回视图进行渲染

        关键组件:HandlerExceptionResolver接口

         上图是在controller中抛出除0异常后遍历handlerExceptionResolvers集合时debug观察到的集合中的元素,其中DefaultErrorAttributes虽然实现了HandlerExceptionResolver接口但并不处理任何异常,只负责把异常对象存入request域中。而后三个resolver是在DispatcherServlet初始化时存入的,这三个resovler写在了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

        其中的ExceptionHandlerExceptionResolver负责在标注了@ControllerAdvice的类中找标注了@ExceptionHandler并且标注了当前异常类的方法进行处理,处理后返回视图进行渲染。

        顺带说一下ResponseStatusExceptionResolver这个resovler个人认为实际生产中用的较少就不分析了,而DefaultHandlerExceptionResolver负责处理Spring自己抛出的异常,比如下面这种异常,如果客户端请求/demo/hello时没有带参数a,则会抛出org.springframework.web.bind.MissingServletRequestParameterException异常,该异常是Spring内部异常所以由DefaultHandlerExceptionResolver进行处理。

@RestController
@RequestMapping("/demo")
public class DemoController {

    @RequestMapping("/hello")
    public String hello(@RequestParam("a") int a) {
        return "hello";
    }

}

二、异常处理方式二:BasicErrorController+ErrorViewResolver

        (1)使用方法

        我们使用这种处理方式处理异常其实只需要在/template/error下或者静态资源目录下(​ “classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”)定义对应错误码的页面(如404.html)或者4xx.html、5xx.html页面。

        (2)原理分析

        经过方式一不能处理的异常会通过发送/error请求再次来到DispatcherServlet的核心流程,Springboot为我们注册了BasicErrorController来专门处理/error请求,BasicErrorController内部的处理过程则是通过ErrorViewResolver解析对应的错误码并返回ModelAndView,同样的Springboot也为我们注册了DefaultErrorViewResolver来解析错误码返回ModelAndView。

BasicErrorController处理流程:

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    //略...


	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

    //略...
}
	protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
			Map<String, Object> model) {
		for (ErrorViewResolver resolver : this.errorViewResolvers) {
			ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
			if (modelAndView != null) {
				return modelAndView;
			}
		}
		return null;
	}

DefaultErrorViewResolver解析错误视图主要的流程是:

  1. resolveErrorView方法首先得到错误的http状态码,并根据状态码名称调用resolve方法:
    1. resolve方法去/templates/error下面寻找名称与状态码对应的模板,如404.html,如果有就会将其渲染成视图,返回ModelAndView对象;
    2. 如果没有,再调用resolveResource去静态资源目录下寻找:​ “classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”
    3. 若仍然没有,resolve方法最终返回ModelAndView为null;
  2. 当ModelAndView为null,再去验证错误代码是否是4xx或5xx,然后再按照上面resolve方法的逻辑去找是否有4xx.html或5xx.html;
  3. 如果仍然没找到,返回null;

代码如下:

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

    //略...

    	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}


    //略...
}

        如果我们对DefaultErrorViewResolver处理对应错误码的方式不满意,则可以通过自定义ErrorViewResolver接口的实现类加入到容器中来替换DefaultErrorViewResolver的默认行为。如果我们对BasicErrorController的处理方式不满意,比如我们想直接返回Json字符串,则可以通过自定义ErrorController接口的实现类加入到容器中来替换BasicErrorController

        如果通过DefaultErrorViewResovler仍不能处理当前错误码的/error请求,则会返回视图名为error的ModelAndView,该视图error最终会被BeanNameViewResolver这个视图解析器解析得到一个bean名称为error的View对象,调用该对象的渲染方法,最终为我们呈现出来的就是Springboot默认的错误白页。  

        总结:异常请求处理流程

其实方式一和方式二都属于异常请求处理流程的一部分,方式一无法处理的异常就会由方式二进行处理。异常请求处理的流程主要分成五步:

  1. 解析错误请求抛出异常
  2. 尝试处理错误请求(方式一处理)
  3. 如果处理失败,会给底层response发送错误信息;然后再重新发送一个/error请求
  4. /error请求被ErrorController处理(方式二处理)
  5. 若ErrorController中的ErrorViewResolver仍不能处理返回视图,则new一个视图名为error的ModelAndView对象,最终该视图会由Springboot自动装配的BeanNameViewResolver解析得到一个bean名称为error的View对象,该View对象进行渲染后即得到Springboot的默认错误白页

        附:Springboot自动装配的异常处理组件

        上述的许多组件都不是我们自己装入容器的,这些组件是由Springboot为我们自动装入的。Springboot在其自动配置包spring-boot-autoconfigure中的ErrorMvcAutoConfiguration类中主要为我们自动装配了包括DefaultErrorAttributes、BasicErrorController、DefaultErrorViewResolver、View(bean名称为error)、BeanNameViewResolver、

        这里声明下,本文的异常处理流程是在Springboot搭建的SSM项目环境下,如果是原生的Web.xml搭建的SSM项目则没有Springboot装配的这些组件,如果需要这些组件提供的功能需要自动手动进行导入到容器中。

        参考博客

        常请求处理流程 发送/error请求以及得到error视图的详细原理_bejsoiv的博客-CSDN博客

        

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值