Human0722's blog Human0722's blog
首页
  • Spring

    • Spring Framework
    • Spring Boot
    • Spring Cloud
  • CCNA
  • Vue

    • Vue2
日本语
导航
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Human0722

Gravity always win
首页
  • Spring

    • Spring Framework
    • Spring Boot
    • Spring Cloud
  • CCNA
  • Vue

    • Vue2
日本语
导航
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Spring Framework

  • Spring Boot

    • HelloWorld
    • 异常处理
      • 参数校验
      • 配置文件
      • Filter & Interceptor
      • Spring MVC 启动流程分析
      • DefineSprintBootStarter
    • Java 类库

    • 数据库

    • 解决方案

    • Java.Content
    • Spring Boot
    Xueliang
    2022-12-07
    目录

    异常处理

    # 异常处理机制

    要想查看异常处理源码,在 idea 中下载spring-webmvc源码,然后双击 shift 搜索 DispatcherServlet 查看。 img 在 Spring MVC 的 org.springframework.web.servlet.DispatcherServlet.java 中,系统通过下面代码尝试调用请求对应的函数,如果代码抛出异常,先将异常 存储在变量 dispatchException 中。最后调用方法 processDispatchResult() 来处理结果。

    try {
    //...
    // Actually invoke the handler.尝试调用请求对应控制器中的函数
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    }catch (Exception ex) {
      // 如果有异常,则先存储起来
      dispatchException = ex;
    }
    // 进入结果处理流程
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    在下面 dispatcheDispatchResult() 方法中,先判断抛出的异常是否属于 ModelAndViewDefiningException 异常,是则走入对应流程,但这不是本文重点。当不满足条件进入带 else 代码块中时,会通过 processHandlerException() 来进入其他异常处理流程。

    //processDispatchResult:
    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);
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    在 processHandlerException() 函数中,通过注册在容器中的HandlerExceptionResolver,遍历匹配最佳的ExceptionResolver来处理这个异常。

    //processHandlerException():
    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        // 遍历匹配处理
    	for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
    	    // 尝试处理
    		exMv = resolver.resolveException(request, response, handler, ex);
    		if (exMv != null) {
    			break;
    		}
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    通过程序断点调式,打印出 this.handlerExceptionReesolvers的值:

    handlerExceptionResolvers:
        |--DefaultErrorAttributes       #默认
        |--HandlerExceptionResolverCompostie    # composite
            |--ExceptionHandlerExceptionResolver    #1
            |-- ResponseStatusExceptionResolver     #2
                |-- ? instanceof ResponseStatusException
                |-- annotation of  ResponseStatus
            |--DefaultHandlerExceptionResolver
    
    1
    2
    3
    4
    5
    6
    7
    8

    当遍历到#1时候,即 ExceptionHandlerExceptionResolver。它的处理机制是在容器中寻找有注解 @HandlerException() 的方法,该注解中的值中 有异常列表,如果抛出的异常在这个列表中,则会调用这个方法来处理。
    如果1不满足条件,则会来到#2处,即ResponseStatusExceptionResolver。首先会在容器中寻找 ResponseStatusException的实例,找不到则寻找 被 @ResponseStatus注解的异常类,如果这个异常类刚好是代码中抛出的异常,则用注解中的值来进行响应。

    下面用 @ExceptionHandler 和 @ResponseStatus 示例。

    # ExceptionHandler

    要在方法上注解 @ExceptionHandler, 首先在方法所在类上注解 @RestControllerAdvice, 为了将该类扫描到Spring IOC容器中。
    @ExceptionHandler 可以传入异常类数组,当这些异常被抛出时,会触发到对应的函数。

    注意是 @RestControllerAdvice, 而不是 @ControllerAdvice,两者处理方式不一样。

    @RestControllerAdvice
    public class GlobalExceptionHander{
      
      @ExceptionHandler({Exception.class})
      public void handler(Exception e) {
          //statement
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    场景:编码时当接口出现异常时,可以抛出自定的异常类,再通过这种方式捕捉异常,封装通用的返回体给客户端。

    // 响应类
    class Response<T> {
      private String code;
      private String message;
      private T data;
      // .. getter setter
    }
    class ServiceException extends RuntimeException {
    
      private String code;
      public ServiceException(String code, String message) {
        super(message);        
        this.code = code;
      }
    }
    
    @RestControllerAdvice
    public class GlobalExceptionHandler {
      @ExceptionHander({ServiceException.class})
      public Response handleServiceException(ServiceException e) {
          e.printStackTrace();
          return Response.builder().code(e.getCode()).message(e.getMessage());
      }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    # ResponseStatus

    ResponseStatus 将异常类和返回信息绑定起来,只要某个抛出异常类,当没有ExceptionHandler能处理时,系统会将这个类上的注解信息封装到通用的返回体中, 直接返回。如果异常没有被 ResponseStatus注解,则进入下一阶段的异常处理流程。

    @ResponseStatus(code = HttpStatus.FORBIDDEN, reasone= "Forbidden")
    class ForbiddenException extends RuntimeException{
    
    }
    //
    xx () {
      throw new ForbiddenException();   // { "status": 403, "error": "Forbidden"}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    通过 ResponseStatus 这种方式灵活性没有 ExceptionHandler 方式来的灵活,后者可以自定义响应体。

    HelloWorld
    参数校验

    ← HelloWorld 参数校验→

    最近更新
    01
    DefineSprintBootStarter
    03-23
    02
    Spring MVC 启动流程分析
    03-23
    03
    Redis
    03-23
    更多文章>
    Theme by Vdoing | Copyright © 2019-2024 Human0722 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式