springboot异常(三):异常处理原理

news2024/9/27 9:46:13

🍅一、BasicErrorController

☘️1.1 描述

BasicErrorControllerSpringboot中默认的异常处理方法,无需额外的操作,当程序发生了异常之后,Springboot自动捕获异常,重新请求到BasicErrorController中,在BasicErrorController中返回一个视图页面。

🌱1.2 原理解析-配置

ErrorMvcAutoConfiguration会配置Springboot中关于异常相关的类。其中有两个类是异常相关的。

1.2.1 BasicErrorController

配制的第一个BeanBasicErrorController类,所有的异常捕获的时候,都会重新请求到这个Controller

@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
                                                 ObjectProvider<ErrorViewResolver> errorViewResolvers) {
  return new BasicErrorController(errorAttributes, this.serverProperties.getError(),                            errorViewResolvers.orderedStream().collect(Collectors.toList()));
}

1.2.2 ErrorPageCustomizer

初始化第二个BeanErrorPageCustomizer类,这个类是发生异常之后返回视图的模板页面

// 初始化一个ErrorPageCustomizer类
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
  return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
// ErrorPageCustomizer类主要的作用
static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {

  private final ServerProperties properties;

  private final DispatcherServletPath dispatcherServletPath;

  protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
    this.properties = properties;
    this.dispatcherServletPath = dispatcherServletPath;
  }

  @Override
  public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
    ErrorPage errorPage = new ErrorPage(
      // 这里获取异常页面的路径,默认是在/error路径下面
      this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
    errorPageRegistry.addErrorPages(errorPage);
  }

  @Override
  public int getOrder() {
    return 0;
  }
}

🌲1.3 原理解析-触发

当异常发生后会重新请求到BasicErrorController中,该类有两个接口一个是针对json的请求,一个是针对text/html的请求。这里主要看text/html的请求,返回一个ModelAndView。返回什么ModelAndView页面,是通过resolveErrorView方法去解析的。如果没有ModelAndView视图,就自己创建一个默认的返回。

// 处理针对请求是text/html的请求
@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);
}
// 处理请求是json的请求
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  HttpStatus status = getStatus(request);
  if (status == HttpStatus.NO_CONTENT) {
    return new ResponseEntity<>(status);
  }
  Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
  return new ResponseEntity<>(body, status);
}
  • resolveErrorView

这里先是获取所有的ErrorViewResolver,然后循环去调用ErrorViewResolverresolveErrorView方法。ErrorViewResolver是一个功能性接口,只有一个方法resolveErrorView

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

ErrorViewResolver只有一个实现类DefaultErrorViewResolver,在DefaultErrorViewResolverresolverErrorView方法中先调用了resolve方法,这里传的参数是响应吗,比如500404等。这个方法的目的是找到响应吗对应的页面比如500.html404.html等,如果没找到就找5xx.html页面。

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  // 这里直接用500来解析是否有这个页面
  ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
  // 如果是空的,就用5xx,去看看有没有这个页面
  if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
    modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
  }
  return modelAndView;
}
  • resolve

resolve方法找到error路径下对应的页面,如果还是不存在就调用resolveResource方法去找。

private ModelAndView resolve(String viewName, Map<String, Object> model) {
  String errorViewName = "error/" + viewName;
  TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
                                                                                         this.applicationContext);
  if (provider != null) {
    return new ModelAndView(errorViewName, model);
  }
  return resolveResource(errorViewName, model);
}
  • resolveResource

resolveResource方法先通过getStaticLocations()获取静态文件路径,然后去路径下判断是否有异常页面存在。

getStaticLocations()包含了四个路径:

1.classpath:/METAINF/resources/

2.classpath:/resources

3.classpath:/static/

4.classpath:/public/

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
  	// 获取静态资源路径
		for (String location : this.resources.getStaticLocations()) {
			try {
				Resource resource = this.applicationContext.getResource(location);
				resource = resource.createRelative(viewName + ".html");
				if (resource.exists()) {
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			}
			catch (Exception ex) {
			}
		}
		return null;
	}

🍇二、HandlerExceptionResolver

🌳1.1描述

HandlerExceptionResolver是通过定义一个类实现HandlerExceptionResolver接口,然后重写resolveException方法,这个方法返回一个ModelAndView类。在ModelAndView中可以定义一些异常相关的处理。

@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("error");
        mv.addObject("error", "报错了");
        mv.addObject("status", "saliwa");
        return mv;
    }
}

🌴1.2原理解析-触发

  • doDispatch

要解析HandlerExceptionResolver的原理要从DispatcherServletdoDispatch开始,我们先看一下这个方法的主要流程,省略掉部分无关代码。先去执行对应请求的方法,如果方法里面发生异常捕获异常,无论是否发生异常都会执行processDispatchResult

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				// 去执行我们要调用的方法,比如我们请求的某个controller方法,如果在这个controller执行过程中
        // 发生了异常或者错误都在这里捕获的,并且用dispatchException这个变量来接收返回的异常类
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			}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));
		}
	}
  • processDispatchResult

processDispatchResult方法,这里省略部分无关代码,首先判读异常是否不为空,如果不为空就执行异常处理逻辑,调用processHandlerException方法。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

  boolean errorView = false;
	// 异常不为空
  if (exception != null) {
    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);
    }
  }
}
  • processHandlerException

这里主要是去找有没有HandlerExceptionResolver类,如果有,就执行他的resolveException方法,这个方法会返回ModelAndView,如果ModelAndView不为空就返回。

前面我们自定义的类就是实现了HandlerExceptionResolver接口,并且重写了resolveException方法,返回了一个ModelAndView,这里异常就处理结束了。

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

  ModelAndView exMv = null;
  if (this.handlerExceptionResolvers != null) {
    // 这里就是我们自己的实现类,包括两个默认的和我们自己定义的
    for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
      exMv = resolver.resolveException(request, response, handler, ex);
      if (exMv != null) {
        break;
      }
    }
  }
  if (exMv != null) {
    return exMv;
  }
  throw ex;
}

🍈三、ControllerAdvice

🌵3.1 描述

ControllerAdvice是全局异常拦截器,配合ExceptionHandler使用。除了可以拦截Java定义的异常,还可以自定义异常。

先自定义一个异常

@Getter
@Setter
public class MyException extends RuntimeException {

    private String errorCode;

    private String errorMessage;

    public MyException () {
        super();
    }

    public MyException(String errorCode, String errorMessage) {
        super(errorMessage);
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }
}

自定义一个全局异常拦截器,拦截自定义的异常和空指针异常。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
  	// 自定异常
    @ExceptionHandler(value = MyException.class)
    public Object restErrorHandler(HttpServletRequest request, MyException e) {
        log.error("报错了: ", e);
        return "错误码:" + e.getErrorCode() + "错误内容:" + e.getErrorMessage();
    }
		// 空指针异常
    @ExceptionHandler(value={java.lang.NullPointerException.class})
    public String nullPointerExceptionHandler(Exception e){
        log.error("报错了 ", e);
        return "错误内容:" + e.getMessage();
    }
}

🌾3.2 原理解析-配置

3.2.1 WebMvcConfigurationSupport

  • handlerExceptionResolver

WebMvcConfigurationSupport类里面要初始化一个HandlerExceptionResolver类,在初始化这个类之后要,要执行addDefaultHandlerExceptionResolvers方法。

@Bean
public HandlerExceptionResolver handlerExceptionResolver(
  @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
  List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
  configureHandlerExceptionResolvers(exceptionResolvers);
  if (exceptionResolvers.isEmpty()) {
    addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
  }
  extendHandlerExceptionResolvers(exceptionResolvers);
  HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
  composite.setOrder(0);
  composite.setExceptionResolvers(exceptionResolvers);
  return composite;
}
  • addDefaultHandlerExceptionResolvers

在这个方法里面先调用了createExceptionHandlerExceptionResolver方法创建了ExceptionHandlerExceptionResolver类。

protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
			ContentNegotiationManager mvcContentNegotiationManager) {

		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);
		}
		exceptionHandlerResolver.afterPropertiesSet();
		exceptionResolvers.add(exceptionHandlerResolver);

		ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
		responseStatusResolver.setMessageSource(this.applicationContext);
		exceptionResolvers.add(responseStatusResolver);

		exceptionResolvers.add(new DefaultHandlerExceptionResolver());
	}

3.2.2 ExceptionHandlerExceptionResolver

  • afterPropertiesSet

在这个方法里面主要是调用了一个方法initExceptionHandlerAdviceCache

@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

这个方法是最重要的,首先获取有ControllerAdvice这个注解的bean,也就是我们自定义的全局异常拦截器,然后将这个bean转换成ExceptionHandlerMethodResolver

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);
			}
			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
			if (resolver.hasExceptionMappings()) {
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
			}
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				this.responseBodyAdvice.add(adviceBean);
			}
		}

		if (logger.isDebugEnabled()) {
			int handlerSize = this.exceptionHandlerAdviceCache.size();
			int adviceSize = this.responseBodyAdvice.size();
			if (handlerSize == 0 && adviceSize == 0) {
				logger.debug("ControllerAdvice beans: none");
			}
			else {
				logger.debug("ControllerAdvice beans: " +
						handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
			}
		}
	}

🌿3.3 原理解析-触发

3.3.1 DispatcherServlet

  • doDispatch

要解析HandlerExceptionResolver的原理要从DispatcherServletdoDispatch开始,我们先看一下这个方法的主要流程,省略掉部分无关代码。先去执行对应请求的方法,如果方法里面发生异常捕获异常,无论是否发生异常都会执行processDispatchResult

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				// 去执行我们要调用的方法,比如我们请求的某个controller方法,如果在这个controller执行过程中
        // 发生了异常或者错误都在这里捕获的,并且用dispatchException这个变量来接收返回的异常类
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			}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));
		}
	}
  • processDispatchResult

processDispatchResult方法,这里省略部分无关代码,首先判读异常是否不为空,如果不为空就执行异常处理逻辑,调用processHandlerException方法。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

  boolean errorView = false;
	// 异常不为空
  if (exception != null) {
    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);
    }
  }
}
  • processHandlerException

这里主要是去找有没有HandlerExceptionResolver类,如果有,就执行他的resolveException方法,这个方法会返回ModelAndView,如果ModelAndView不为空就返回。

这里我们没有自定义HandlerExceptionResolver,只有DefaultErrorAttributesHandlerExceptionResolverComposite

这里先去调用DefaultErrorAttributes的resolverException方法,这个方法返回的是null,然后会继续调用HandlerExceptionResolverComposite类的resolverException方法。

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

  ModelAndView exMv = null;
  if (this.handlerExceptionResolvers != null) {
    for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
      exMv = resolver.resolveException(request, response, handler, ex);
      if (exMv != null) {
        break;
      }
    }
  }
  if (exMv != null) {
    return exMv;
  }
  throw ex;
}

3.3.2 HandlerExceptionResolverComposite

  • resolveException

在这个方法里面会继续找HandlerExceptionResolver,并执行resolve Exception方法。这里的HandlerExceptionResolver有三个ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver

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;
}

首先调用的是ExceptionHandlerExceptionResolverresolveException方法,但是这个方法没有resolveException方法,但是它的抽象父类的父类AbstractHandlerExceptionResolver有这个方法

3.3.3AbstractHandlerExceptionResolver

  • resolveException

这里的resolveException方法中,调用了doResolveException去获取ModelAndView

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));
      }
      // Explicitly configured warn logger in logException method.
      logException(ex, request);
    }
    return result;
  }
  else {
    return null;
  }
}
  • doResolveException

ExceptionHandlerExceptionResolver也没有 doResolveException但是它的父类AbstractHandlerMethodExceptionResolver有这个方法。

3.3.4 AbstractHandlerMethodExceptionResolver

  • doResolveException

这里走的是AbstractHandlerMethodExceptionResolver类的doResolveException方法,这个方法会继续调用doResolveHandlerMethodException,这个方法是在ExceptionHandlerExceptionResolver里面。

@Override
@Nullable
protected final ModelAndView doResolveException(
  HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

  HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
  return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}

3.3.5 ExceptionHandlerExceptionResolver

  • doResolveHandlerMethodException

看下ExceptionHandlerExceptionResolver实现的doResolveHandlerMethodException,这里最重要的是第一行getExceptionHandlerMethod,获取异常处理方法

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
			HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

		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 {
			if (logger.isDebugEnabled()) {
				logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
			}
			// Expose causes as provided arguments as well
			Throwable exToExpose = exception;
			while (exToExpose != null) {
				exceptions.add(exToExpose);
				Throwable cause = exToExpose.getCause();
				exToExpose = (cause != exToExpose ? cause : null);
			}
			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;
		}

		if (mavContainer.isRequestHandled()) {
			return new ModelAndView();
		}
		else {
			ModelMap model = mavContainer.getModel();
			HttpStatus status = mavContainer.getStatus();
			ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
			mav.setViewName(mavContainer.getViewName());
			if (!mavContainer.isViewReference()) {
				mav.setView((View) mavContainer.getView());
			}
			if (model instanceof RedirectAttributes) {
				Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
				RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
			}
			return mav;
		}
	}
  • getExceptionHandlerMethod

可以看出这里是先找到有ControllerAdvicebean的类,然后根据异常类型去匹配这个bean里面定义的ExceptionHandler,这里就找到我们自己定义的全局异常处理的ExceptionHandler,进行异常处理。

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
			@Nullable HandlerMethod handlerMethod, Exception exception) {

  Class<?> handlerType = null;

  if (handlerMethod != null) {
    // Local exception handler methods on the controller class itself.
    // To be invoked through the proxy, even in case of an interface-based proxy.
    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);
    }
    // For advice applicability check below (involving base packages, assignable types
    // and annotation presence), use target class instead of interface-based proxy.
    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;
}

3.3.6 调用时序图

这里的整体调用逻辑如下:

1.首先调用dispatch去执行方法执行目标方法

2.执行完毕之后调用processDispatchResult去处理执行结果

3.如果目标方法抛出了一场就执行processHandlerException去处理异常

4.在processHandlerException中会调用HandlerExceptionResolverCompositeresolveException方法

5.在HandlerExceptionResolverCompositeresolveException方法会继续调用resolveExeption方法

6.这里是调用ExceptionHandlerExceptionResolverresolveException方法

7.但是ExceptionHandlerExceptionResolver没有resolveException方法,但是它父类的父类AbstractHandlerExceptionResolver有这个方法

8.然后在resolveException中又调用了doResolveException方法,ExceptionHandlerExceptionResolver没有这个方法,但是它的父类有

9.在AbstractHandlerMethodExceptionResolverdoResolveException方法中调用了doResolveHandlerMethodException

10.doResolveHandlerMethodExceptionExceptionHandlerExceptionResolver的,里面继续调用了getExceptionHandlerMethod方法

11.整个调用链路就完成了

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2169605.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS设计模式之桥接模式:搭建跨越维度的通路

引言 在软件开发中&#xff0c;我们经常遇到需要对不同的抽象类进行不同的实现的情况&#xff0c;而传统的对象嵌套并不是一个优雅且可扩展的解决方案&#xff0c;因此这正是桥接模式的用武之地。桥接模式通过将抽象与实现分离&#xff0c;使得它们可以独立变化&#xff0c;从…

前缀和(5)_和为k的子数组

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 前缀和(5)_和为k的子数组 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 题目…

列表控件QListWidget

显示模式 有两种显示模式&#xff0c;列表模式和图标模式 // 获取和设置显示模式 QListView::ViewMode viewMode() const void setViewMode(QListView::ViewMode mode) QListView::ViewMode有两个取值 QListView::ListMode 列表模式 QListView::IconMode 图标模式 交替背…

《银河战星:僵局》风灵月影修改器使用指南,轻松驾驭宇宙战场

在策略射击游戏《银河战星&#xff1a;僵局》中&#xff0c;合理利用风灵月影修改器能极大提升你的游戏体验。 以下是简明操作步骤&#xff0c;助你迅速上手&#xff0c;遨游星际&#xff1a; 1.下载安装&#xff1a; 首先&#xff0c;确保从正规渠道获取风灵月影修改器&…

冒泡排序-C语言

1.问题&#xff1a; 从小到大对10个数进行排序&#xff0c;要求使用冒泡排序实现。 2.解答&#xff1a; 排序规律有两种&#xff1a;一种是“升序”&#xff0c;从小到大&#xff1b;另一种是“降序”&#xff0c;从大到小。 3.代码&#xff1a; #include<stdio.h>//头…

保护您的网络:入侵检测系统 IDS 终极指南

IDS 和 IPS&#xff1a;了解异同-CSDN博客 IDS(入​​侵检测系统) 和 IPS(入​​侵防御系统) 之间的区别-CSDN博客 即便是今天&#xff0c;互联网仍与黑客肆意横行的网络无异。 但不必害怕&#xff01;我们有解决您问题的完美解决方案&#xff1a;入侵检测系统 I DS。 IDS就…

本地打开打包后的dist文件报错的解决方法

出现问题 本地直接打开dist文件夹会报错&#xff0c;报错信息类似于&#xff1a; Access to script at file:///D:/assets/index.9cb0ffcc.js from origin null has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, ch…

TypeScript基础语法与面对对象

TypeScript 基础语法 TypeScript 与面向对象 面向对象是一种对现实世界理解和抽象的方法。TypeScript 是一种面向对象的编程语言。面向对象主要有两个概念&#xff1a;对象和类 对象&#xff1a;对象是类的一个实例&#xff08;对象不是找个女朋友&#xff09;&#xff0c;有…

【优选算法】(第四篇)

目录 三数之和&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 四数之和&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 三数之和&#xff08;medium&#xff09; 题目解析 1.题目链接&#xff1a;. - 力扣&#xff08;LeetCode&…

接口返回动态列名及动态列数据结构

接口返回动态列名及动态列数据结构代码方案 在开发过程中&#xff0c;有时我们需要处理动态表头和动态列数据的问题。特别是在一些数据统计或报表类需求中&#xff0c;列名和数据都可能随着时间或条件发生变化。本文将结合实际代码和表格展示&#xff0c;详细说明如何在接口中…

微信阅读网站小程序+ssm论文ppt源码调试讲解

第2章 开发环境与技术 微信阅读网站小程序的编码实现需要搭建一定的环境和使用相应的技术&#xff0c;接下来的内容就是对微信阅读网站小程序用到的技术和工具进行介绍。 2.1 MYSQL数据库 本课题所开发的应用程序在数据操作方面是不可预知的&#xff0c;是经常变动的&#xf…

Linux中的tr命令详解

1&#xff0c; 将小写字母转换为大写字母&#xff1a; echo "hello" | tr a-z A-Z但是限定字母的范围的话&#xff0c;是什么输出你们知道吗&#xff1f; echo "hello" | tr a-l A-L2&#xff0c;删除特定字符&#xff1a; 比如&#xff0c;删除所有的数…

【吊打面试官系列-MySQL面试题】MySQL锁的优化策略?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL锁的优化策略?】面试题&#xff0c;希望对大家有帮助&#xff1b; MySQL锁的优化策略? 1、读写分离 2、分段加锁 3、减少锁持有的时间 4.多个线程尽量以相同的顺序去获取资源不能将锁的粒度过于细化&#xff0c;不然可能会…

算法工程师重生之第十六天(二叉搜索树的最小绝对差 二叉搜索树中的众数 二叉树的最近公共祖先 )

参考文献 代码随想录 一、二叉搜索树的最小绝对差 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输出&#x…

语义元分割学习:一种用于少量样本无线图像分类的TinyML方案

论文标题&#xff1a;Semantic Meta-Split Learning: A TinyML Scheme for Few-Shot Wireless Image Classification 中文标题&#xff1a;语义元分割学习&#xff1a;一种用于少量样本无线图像分类的TinyML方案 作者信息&#xff1a; Eslam Eldeeb, Mohammad Shehab, Hirley…

nvm,一款nodejs版本管理工具

背景 在工作中&#xff0c;我们可能同时在进行2个或者多个不同的项目开发&#xff0c;每个项目的需求不同&#xff0c;进而不同项目必须依赖不同版本的NodeJS运行环境&#xff0c;这种情况下&#xff0c;对于维护多个版本的node将会是一件非常麻烦的事情&#xff0c;nvm就是为…

Dapper 如何确保数据的安全性和防止 SQL 注入攻击?

一、什么是SQL注入攻击 SQL注入攻击是一种常见的网络攻击手段&#xff0c;它利用了应用程序中安全措施不足的问题&#xff0c;允许攻击者插入或“注入”一个或多个SQL语句到原本的查询中。这种攻击可以用于获取、篡改或删除数据库中的数据&#xff0c;甚至可以执行一些数据库管…

java:brew安装rabbitmq以及简单示例

什么是消息队列mq 可以看我之前写的这篇 消息队列MQ rabbitmq简介 RabbitMQ是由erlang语言开发&#xff0c;基于AMQP&#xff08;Advanced Message Queue 高级消息队列协议&#xff09;协议实现的消息队列&#xff0c;它是一种应用程序之间的通信方法&#xff0c;消息队列在…

200smart数据日志的功能

称重设备&#xff08;皮带秤&#xff09;读取到的数据值总是一直在跳变&#xff0c;无法正确识别称重传感器读取上来的值来判断产品的重量&#xff0c;虽然在程序中增加了取平均值功能&#xff08;模拟量输入按PLC扫描周期次数求平均值程序&#xff09;&#xff0c;但效果不理想…

什么是聚集索引?

什么是聚集索引&#xff1f; 1、聚集索引的特点2、如何确定聚集索引3、性能优势 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 聚集索引是一种特殊的索引&#xff0c;它直接包含了表中的所有数据行。所以&#xff0c;通过聚集索引&#xf…