Java全局异常处理,@ControllerAdvice异常拦截原理解析【简单易懂】

https://www.bilibili.com/video/BV1sS411c7Mo


在这里插入图片描述




最近在做系统升级的时候,引发了一个BUG,原本系统是有一个异常处理器A,引入了某个底包中也带了一个异常处理器B,最终走了底包的异常处理器B。 A对于异常的时候会返回HTTP状态码为500,B对于异常处理器返回的HTTP状态码为200,前端基于HTTP状态码进行提示的,就出了问题

本篇文章我们就来讨论一下在JavaWeb中的全局异常处理器是何时何地如何执行的。

在进行学习之前需要先知道:HTTP执行流程,SpringMVC执行流程


一、全局异常处理器的类型


全局异常处理器的父接口是 HandlerExceptionResolver,简单来说就是实现或间接实现它的类就叫全局异常处理器。

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerExceptionResolver {

    @Nullable
    ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

HandlerExceptionResolver 的继承关系图
在这里插入图片描述


1-1、实现方式一


SpringBoot项目最大的特点就是注解,在SpringBoot项目中全局异常拦截的注解是@ControllerAdvice (@RestControllerAdvice = @ControllerAdvice + @ResponseBody)


使用 @ControllerAdvice的类最终会生成 ExceptionHandlerExceptionResolver


1-2、实现方式二


重写 doResolveHandlerMethodException 方法,然后注册当前的bean

public class ExceptionHandler extends AbstractHandlerMethodExceptionResolver {

    private final static Logger logger = LoggerFactory.getLogger(ExceptionHandler.class);

    @Override
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception ex) {

       
        return new ModelAndView();
    }
}

二、全局异常拦截点


2-1、入口


org.springframework.web.servlet.DispatcherServlet#doDispatch 这个方法就是SpringMVC的执行流程的核心代码了,下面是简化代码

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 {
          // ...
          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) {
        // ....
    }
    catch (Throwable err) {
        // ....
    }
    finally {
        // ...       
    }
}

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

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

    boolean errorView = false;

    if (exception != null) {
      Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
      // 异常处理
      mv = processHandlerException(request, response, handler, exception);
      errorView = (mv != null);
       
    }

    // ...
}

org.springframework.web.servlet.DispatcherServlet#processHandlerException

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) {
       // 遍历循环所有的拦截器来尝试处理这个异常(拦截器已经按照 order 排好序了)
       for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
          exMv = resolver.resolveException(request, response, handler, ex);
          // 只有返回了 ModelAndView 才结束,不然一直往下走
          if (exMv != null) {
             break;
          }
       }
    }
    // ...
    // 如果没有全局异常处理器 可以处理这个异常 就继续抛出去
    throw ex;
}

2-2、全局异常拦截器是如何注入到 DispatcherServlet 的


上面看到是从 handlerExceptionResolvers 从获取所有的异常处理器,它是一个list

@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;

在DispatcherServlet里面有一个onRefresh方法,它是重写的父类FrameworkServlet的,在初始化ServletBean的时候会被调用一次,它里面会做很多初始化的操作,其中一个就是获取容器里面的全局异常拦截器


一层层看上去其实是 Servlet接口的 init方法触发的

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    // ...
    
    initHandlerExceptionResolvers(context);
    
    // ...
}

找到bean容器里面的所有异常拦截器,把它存在 handlerExceptionResolvers 里面,并排序


private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;

    if (this.detectAllHandlerExceptionResolvers) {
       // 从bean容器里面找到所有的 HandlerExceptionResolver
       Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
             .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
       if (!matchingBeans.isEmpty()) {
          this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
          // 排序
          AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
       }
    }
     // ...
}

三、ControllerAdvice 如何解析、执行


3-1、解析


在springframework 中有这样一个类 ExceptionHandlerExceptionResolver

package org.springframework.web.servlet.mvc.method.annotation;

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
       implements ApplicationContextAware, InitializingBean {
    
    // ...
}

⚠️:可以回到【全局异常处理器的类型】的图看看,ExceptionHandlerExceptionResolver其实就是全局异常处理器HandlerExceptionResolver的子类


它实现了 InitializingBean,重写了afterPropertiesSet(这个方法会在bean初始化完之后执行)

@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBodyAdvice beans
    initExceptionHandlerAdviceCache();
    // ...
}

initExceptionHandlerAdviceCache 会把所有使用了@ControllerAdvice 的bean找到并把它存在自己的参数里面

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

private void initExceptionHandlerAdviceCache() {
    if (getApplicationContext() == null) {
       return;
    }
    
    // 找到所有使用了 @ControllerAdvice 的bean
    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);
       }
       // 解析全部的 ExceptionHandler 注解
       ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
       if (resolver.hasExceptionMappings()) {
           // 存入当前的类参数里面
          this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
       }
       if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
          this.responseBodyAdvice.add(adviceBean);
       }
    }
    // ...
}

org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans

public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
    ListableBeanFactory beanFactory = context;
    if (context instanceof ConfigurableApplicationContext) {
       // Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above
       beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();
    }
    List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
    // 遍历所有的bean
    for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) {
       if (!ScopedProxyUtils.isScopedTarget(name)) {
          // 找到符合的bean
          ControllerAdvice controllerAdvice = beanFactory.findAnnotationOnBean(name, ControllerAdvice.class);
          if (controllerAdvice != null) {
             // 存起来
             adviceBeans.add(new ControllerAdviceBean(name, beanFactory, controllerAdvice));
          }
       }
    }
    // 排序
    OrderComparator.sort(adviceBeans);
    return adviceBeans;
}

配合@ControllerAdvice 注解的通常是 @ExceptionHandler 它用来制定具体的异常,把所有的 ExceptionHandler都存入了 mappedMethods 中org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver

public ExceptionHandlerMethodResolver(Class<?> handlerType) {
    for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
       for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
          addExceptionMapping(exceptionType, method);
       }
    }
}
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 + "}");
    }
}

至此@ControllerAdvice的解析完成

  1. 生成了一个ExceptionHandlerExceptionResolver,它通过多级实现了 HandlerExceptionResolver
  2. 所有使用@ControllerAdvice的类都存在了 exceptionHandlerAdviceCache 中
  3. 所有使用 @ExceptionHandler 的方法否存在了mappedMethods 中

3-2、执行


  1. 从【2-1】得知,执行异常处理器的时候是执行 HandlerExceptionResolver.resolveException方法(它只有这一个方法)
  2. 从【3-1】得知,所有使用 @ControllerAdvice 注解的类都被存在了ExceptionHandlerExceptionResolver 中
  3. 从【1】得知,ExceptionHandlerExceptionResolver的继承关系如下图
    在这里插入图片描述

一层层去看调用关系,最终会执行的是 (这个很简单直接去看即可)org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

执行过程就是循环exceptionHandlerAdviceCache中的每一个全局拦截器,再循环每个拦截器里面的mappedMethods看哪个可以匹配上,就执行哪个


四、其它


4-1、设置HTTP状态码


大多数情况下我们会自定义返回值code,比如未鉴权,返回给前端HTTP状态码是200,code为401,但在某些情况下也会直接返回HTTP状态码401,可以使用 @ResponseStatus

@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(Exception.class)
public ResultObj bizExceptionHandler(Exception e) {
    

    log.info("全局异常拦截", e);
    return ResultObj.success();
}

4-2、异常处理器排序


springframework 里面提供了一个Ordered 接口,实现它重写里面 getOrder 方法就可以进行排序了


4-3、所谓全局异常

并不是系统任何异常都会被它所拦截,因为我们已经知道它的执行点是在MVC的流程中,所以就只有HTTP异常才会被拦截处理

<think>好的,我现在需要给用户解释一下“Gateway全局拦截器”这个概念。首先,我得确定用户指的是哪种技术领域的Gateway。通常来说,网关(Gateway)在计算机网络中是一个重要的组件,用于连接不同的网络,进行协议转换、安全控制等。但是在软件开发中,尤其是微服务架构中,API Gateway也是一个常见的概念,用于处理请求路由、认证、限流等功能。这里的全局拦截器可能指的是在API Gateway中全局应用的拦截器,用于对所有请求进行统一处理。 接下来,我需要考虑用户可能的背景。如果用户是开发者,特别是使用Spring Cloud Gateway或者类似框架的话,全局拦截器可能涉及过滤器(Filter)的概念。在Spring Cloud Gateway中,全局过滤器(Global Filter)会对所有的路由请求生效,用来实现跨 cutting的关注点,比如日志记录、权限验证、请求头修改等。这时候需要详细说明其作用、如何配置以及使用场景。 另外,也有可能用户指的是其他类型的网关,比如Zuul或者Nginx的拦截器机制,但考虑到现在Spring Cloud Gateway比较流行,可能以这个为例更合适。需要确认不同技术中的实现是否有差异,但核心概念应该是类似的:全局拦截器在所有请求到达目标服务之前或之后执行某些逻辑。 然后,我需要组织回答的结构。首先定义什么是Gateway全局拦截器,然后说明它的作用,比如统一处理请求、增强安全性、日志记录等。接着,可以举几个具体的例子,比如认证拦截器、日志拦截器、限流拦截器等。然后可能需要讲解如何实现一个全局拦截器,以Spring Cloud Gateway为例,给出代码示例,说明如何编写Global Filter类,并注册到应用中。此外,还要提到执行顺序、如何控制拦截器的优先级等。 同时,需要注意区分全局拦截器和特定路由的拦截器的区别,强调全局拦截器的影响范围更广,适用于所有请求。还要提到可能的注意事项,比如性能影响,确保拦截器的逻辑高效,避免阻塞请求处理流程。 可能用户的问题中还隐含着如何解决某些实际问题,比如如何统一添加请求头、如何进行权限校验等,所以在回答中可以结合这些实际应用场景,让解释更具体。 最后,检查是否有遗漏的点,比如与其他类型拦截器的对比,或者在不同框架中的实现差异,是否需要进一步说明。确保回答准确、易懂,并且结构清晰,帮助用户全面理解Gateway全局拦截器的概念和应用。</think>Gateway全局拦截器是微服务架构中API网关的核心功能之一,用于对所有经过网关的请求/响应进行统一拦截处理。以下从工作原理到实现方式的详细解析: 一、核心作用 1. 全局流量管控:作用于所有路由请求,无差别拦截 2. 统一逻辑处理:认证鉴权、流量染色、日志记录等通用逻辑集中处理 3. 请求链路增强:添加统一请求头、参数校验、协议转换等预处理 4. 安全防护屏障:防重放攻击、SQL注入检测等安全校验 二、技术实现(以Spring Cloud Gateway为例) 1. 实现接口: ```java @Component public class AuthGlobalFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 前置处理逻辑 ServerHttpRequest request = exchange.getRequest(); if (!checkToken(request.getHeaders())) { return unauthorizedResponse(exchange); } // 传递到下一个过滤器 return chain.filter(exchange).then(Mono.fromRunnable(() -> { // 后置处理逻辑 logResponse(exchange.getResponse()); })); } } ``` 2. 执行流程: ``` 客户端请求 -> Gateway Handler Mapping -> 路由匹配 -> Gateway Filter Chain (执行所有GlobalFilter) -> 目标微服务 -> 返回响应(后置处理) ``` 三、典型应用场景 1. JWT统一鉴权 ```java // 验证Authorization头中的JWT String token = request.getHeaders().getFirst("Authorization"); Jws<Claims> claims = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token.replace("Bearer ", "")); ``` 2. 流量染色 ```java // 添加灰度标识 exchange.getRequest().mutate() .header("X-Traffic-Tag", "canary-version-01") .build(); ``` 3. 请求日志记录 ```java // 记录请求信息 MDC.put("requestId", UUID.randomUUID().toString()); log.info("Request {} {} from {}", request.getMethod(), request.getURI(), request.getRemoteAddress()); ``` 四、进阶配置技巧 1. 过滤器顺序控制: ```yaml spring: cloud: gateway: default-filters: - name: GlobalFilter1 args: order: -100 - name: GlobalFilter2 args: order: 0 ``` 2. 性能优化建议: - 异步非阻塞处理(使用Reactor API) - 缓存高频校验结果(如权限信息) - 设置合理的超时时间 五、注意事项 1. 避免在过滤器中执行耗时同步操作 2. 异常处理需覆盖所有分支路径 3. 生产环境应配置熔断降级策略 4. 重要过滤器需要幂等设计 通过合理设计全局拦截器,可实现API网关70%以上的公共逻辑处理,建议结合具体业务需求设计过滤器链,并配合监控系统进行全链路追踪。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值