首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >springboot全局异常处理解析

springboot全局异常处理解析

作者头像
叔牙
发布2025-10-20 18:31:46
发布2025-10-20 18:31:46
4700
代码可运行
举报
运行总次数:0
代码可运行

一、概述

业务服务通常以提供标准api的方式提供业务能力,不管是自研产品的前后端交互,还是作为开放平台为外部提供restful api,或者是对接三方服务,都需要或者被需要提供一套标准的响应能力,这其中就包括异常处理能力,比如我们调用别人的支付能力,不管成功还是失败,都希望看到公共或者代表特定语义的错误码,反过来也一样,给前端或者外部提供的接口,在出现异常场景处理罗辑时,直接把一堆异常信息抛给调用方是不合理也是无法接受的,那也就需要保证在非网络或者基础套件等非预期、不可捕获处理的异常场景下,需要把所有的可能异常进行捕获以及转义处理,返回调用方可识别的错误信息。

有些开发人员包括我自己,曾经喜欢使用在controller最外层捕获所有可能的异常,然后打印日志并给出语义化错误信息,比如:

代码语言:javascript
代码运行次数:0
运行
复制
@GetMapping("/page")
public CommonResult<PageResp<OrderInfoVO>> pageOrderInfo(OrderListParam param,HttpServletRequest request) {
    try {
        ParameterChecker.notNull(param,"param");
        String token = this.getToken(request);
        return IResult.getSuccessResult(this.orderManager.pageOrderInfo(param,token,operator));
    } catch (IllegalArgumentException e){
        log.error("OrderController.pageOrderInfo occur error;param={}",param,e);
        return IResult.getFailureResult(IError.PARAM_ILLEGAL);
    } catch (Exception e) {
        log.error("OrderController.pageOrderInfo occur error;param={}",param,e);
        return IResult.getFailureResult(IError.SYSTEM_ERROR);
    }
}

当然这样写也没有太大问题,保证每一个接口实现都在最外层捕获所有异常,也就不会出现异常逃逸抛出堆栈信息给调用方,但是这样写不够优雅。springboot提供了很多种统一异常处理能力,从而避免异常逃逸以及太多重复性的try catch代码块。

本篇文章重点介绍下几种常用的统一异常处理方式。

二、@ExceptionHandler方式

在SpringBoot中,@ExceptionHandler是实现统一异常处理的核心注解,通常与 @ControllerAdvice配合使用,用于捕获并处理全局或控制器范围内的异常,返回结构化的错误响应。

工作原理详细内容本篇暂不展开,通过流程图简单看一下@ExceptionHandler对异常的拦截和封装处理:

重点看一下@ExceptionHandler的使用方式:

代码语言:javascript
代码运行次数:0
运行
复制
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(AdminRuntimeException.class)
    public IResult<Void> handBaseException(AdminRuntimeException exception) {
        return IResult.fail(exception.getCode(),exception.getMsg());
    }
    //... 省略 ...
    @ExceptionHandler(Exception.class)
    public IResult<Void> handException(Throwable throwable) {
        log.error("发生系统异常:", throwable.getMessage());
        return IResult.fail(IError.SYSTEM_ERROR);
    }
}

定义带@RestControllerAdvice注解的类,然后方法使用@ExceptionHandler标注,代表处理不同的异常,处理逻辑时从异常子类追溯到Exception以及Throwable,也就是说粒度越细,返回响应越具备语义性,对于非业务异常、以及基础服务、基础组件异常,通常需要用Exception或Throwable来捕获,这种捕获给出的响应基本不具备可理解性。

三、HandlerExceptionResolver方式

HandlerExceptionResolver是Spring MVC提供的一个接口,用于实现全局异常处理。当Spring MVC应用中的任何组件Controller、Service 等)抛出异常时,DispatcherServlet会遍历所有注册的 HandlerExceptionResolver,调用它们的resolveException方法来处理该异常。通过实现此接口并注册自定义的异常解析器,可以统一处理系统中的异常。

与@ExceptionHandler类似,尽量细化捕获异常,然后给出语义化响应,最后捕获根异常模糊化处理,看一下实现:

代码语言:javascript
代码运行次数:0
运行
复制
@Configuration
public class BizExceptionResolver implements HandlerExceptionResolver {
private Logger logger = LogManager.getLogger(BizExceptionResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
                                         Exception ex) {
String outPutJson;
//业务异常
if(ex instanceof IllegalArgumentException){
			logger.error("公共捕捉[IllegalArgumentException]异常:", ex);
			outPutJson = CommonWebResponse.error(ExceptionEnum.PARAMETER_IS_INCORRECT.getCode(), ExceptionEnum.PARAMETER_IS_INCORRECT.getMsg()).toString();
		}else if(ex instanceof BizException) {
			logger.error("公共捕捉[BizException]异常:", ex);
			outPutJson = CommonWebResponse.error(((BizException)ex).getCode(),((BizException)ex).getMsg()).toString();
		} else if(ex instanceof ConstraintViolationException) {
			logger.error("捕捉参数类型限制异常[ConstraintViolationException]异常:{}", Throwables.getStackTraceAsString(ex));
			outPutJson = CommonWebResponse.error(ExceptionEnum.PARAM_IS_ERROR.getCode(), ExceptionEnum.PARAM_IS_ERROR.getMessage()).toString();
		}else{
			logger.error("公共捕捉[Exception]异常:",ex);
			outPutJson = CommonWebResponse.error(ExceptionEnum.INTERNAL_SERVER_EXCEPTION).toString();
		}
try {
this.outPutJson(response, outPutJson);
		} catch (IOException e) {
			logger.error("输出错误信息异常: {}", Throwables.getStackTraceAsString(e));
		}
return new ModelAndView();
	}
public void outPutJson(HttpServletResponse res, String jsonStr) throws IOException {
		res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
		res.getWriter().write(jsonStr);
		res.getWriter().flush();
		res.getWriter().close();
	}
}

上述处理方式是捕获所有可能的异常然后做语义化或者模糊化处理,将封装后的响应写到response中返回返回视图调用。大致调用链路如下:

四、ResponseEntityExceptionHandler方式

ResponseEntityExceptionHandler是Spring框架中用于处理控制器方法中抛出的异常并返回适当响应的类,它是一个基类,可以被继承和扩展,用来定制特定异常的处理逻辑,例如捕获Spring MVC 中的4XX或者5XX等请求错误。通过继承此基类并重写其方法,开发者可以为特定的异常类型定义个性化的错误响应,确保API响应的一致性和友好可读性。

ResponseEntityExceptionHandler将springmvc可能抛出的异常都封装成ResponseEntity对象,通常与@RestControllerAdvice或者@ControllerAdvice结合使用。另外ResponseEntityExceptionHandler更适用于restful类型api的异常处理。

从源码可以看到ResponseEntityExceptionHandler本质上也是使用@ExceptionHandler方式捕获和处理异常的,如果上述列出的异常,则可以实现该类做自定义封装响应,但是如果是上述没有枚举的异常类型,特别是业务中自己定义的异常,重写handleExceptionInternal方法没有什么用途,要么重写handleException方法,@ExceptionHandler中添加自定义异常,并在方法中添加自定义异常处理逻辑,要么在子类中添加@ExceptionHandler标注的方法处理自定义异常:

代码语言:javascript
代码运行次数:0
运行
复制
@RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler(BizException.class)
    public ResponseEntity<CommonWebResponse> handBizException(BizException e) {
        return new ResponseEntity<>(CommonWebResponse.error(e.getCode(),e.getMsg()), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

要么添加自定义HandlerExceptionResolver将自定义异常交给ResponseEntityExceptionHandler处理,但是这与前边两种异常处理方式是重复的,所以在有自定义异常处理的场景,单独使用ResponseEntityExceptionHandler无法满足,要结合@ExceptionHandler使用,所以真实使用场景中不建议使用该方式。

五、ErrorController方式

ErrorController(在SpringBoot中通常指BasicErrorController)是一种Spring Boot默认的全局异常处理机制,它通过实现ErrorController接口来处理应用程序中的基本错误,例如404 Not Found错误。它接收未被其他处理器捕获的异常,并将请求重定向到/error路径进行统一处理。默认使用BasicErrorController处理。

image.png
image.png

我们可以自定义ErrorController实现,编写error路径处理逻辑:

@RestController

代码语言:javascript
代码运行次数:0
运行
复制
public class CustomErrorController implements ErrorController {
    // 处理所有未被捕获的异常(映射 /error 路径)
    @RequestMapping("/error")
    public CommonWebResponse<?> handleError(HttpServletRequest request) {
        // 获取原始异常
        Throwable ex = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
        Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        // 构造统一响应
        if (ex instanceof BizException) {
            BizException e = (BizException) ex;
            return CommonWebResponse.error(e.getMessage(), e.getMessage());
        } else if (statusCode != null) {
            return CommonWebResponse.error(String.valueOf(statusCode), "系统错误:" + statusCode);
        } else {
            return CommonWebResponse.error("500", "系统繁忙,请稍后再试");
        }
    }
}

这里核心是从request获取异常。回到DispatcherServlet中processHandlerException处理异常的方法。

这里会调用WebUtils的exposeErrorRequestAttributes方法将异常放到HttpServletRequest中,所以可以直接从HttpServletRequest中获取异常。

代码语言:javascript
代码运行次数:0
运行
复制
public static void exposeErrorRequestAttributes(HttpServletRequest request, Throwable ex,
    @Nullable String servletName) {
  //省略
  exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_ATTRIBUTE, ex);
  //省略
}

这样使用自定义ErrorController也能实现全局异常的处理。

六、AOP方式

AOP是比较通用的能力,比如缓存、事务等都用到了AOP,当然我们也能AOP来实现异常的统一拦截处理。需要做的是定义切面,拦截所有controller类的方法,对异常进行处理和语义化返回响应。

需要引入aop依赖:

代码语言:javascript
代码运行次数:0
运行
复制
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义切面:

代码语言:javascript
代码运行次数:0
运行
复制
@Aspect
@Component
@Slf4j
public class GlobalExceptionAspect {
    /**
     * 定义切点:拦截所有Controller方法
     */
    @Pointcut("execution(* com.typhoon.controller..*.*(..))")
    public void controllerMethods() {}
    /**
     * 环绕通知:捕获并处理异常
     */
    @Around("controllerMethods()")
    public Object handleException(ProceedingJoinPoint joinPoint) {
        try {
            // 执行目标方法
            return joinPoint.proceed();
        } catch (Throwable e) {
            // 处理异常
            return handleThrowable(e, joinPoint);
        }
    }
    private Object handleThrowable(Throwable e, ProceedingJoinPoint joinPoint) {
        // 获取方法签名信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        // 记录异常日志
        log.error("执行 {}.{} 方法时发生异常: {}", className, methodName, e.getMessage(), e);
        // 根据异常类型返回不同的错误响应
        if (e instanceof BizException) {
            BizException bizEx = (BizException) e;
            return CommonWebResponse.error(bizEx.getCode(), bizEx.getMessage());
        } else {
            return CommonWebResponse.error("500", "系统繁忙,请稍后再试");
        }
    }
}

这样实现后,AOP拦截所有controller抛出的异常,然后根据需要进行语义化响应返回。

总结

前边介绍了几种常用的异常拦截处理方式,当然还有其他比如过滤器、拦截器处理异常方式,具体使用可根据项目需要和个人习惯,目前来看官方推荐使用@ExceptionHandler,比较简单实用,另外HandlerExceptionResolver也是一种比较好用的异常处理方式,其他方式从理解成本、便捷性角度来看,不推荐使用。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-09-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、@ExceptionHandler方式
  • 三、HandlerExceptionResolver方式
  • 四、ResponseEntityExceptionHandler方式
  • 五、ErrorController方式
  • 六、AOP方式
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档