深入聊一聊springboot项目全局异常处理那些事儿

目录
  • 前言
  • 问题一:全局异常抽离出来后,业务错误码如何定义?
  • 问题二:全局异常因引入了和业务相同的依赖jar,但jar存在版本差异
  • 问题三:引入maven optional标签后,因业务没引入全局异常需要的jar,导致项目启动报错
  • 总结
  • demo链接

前言

之前我们业务团队在处理全局异常时,在每个业务微服务中都加入了@RestControllerAdvice+@ExceptionHandler来进行全局异常捕获。某次领导在走查代码的时候,就提出了一个问题,为什么要每个微服务项目都要自己在写一套全局异常代码,为什么不把全局异常块抽成一个公共的jar,然后每个微服务以jar的形式引入。后面业务团队就根据领导的要求,把全局异常块单独抽离出来封装成jar。今天聊的话题就是关于把全局异常抽离出来,发生的一些问题

问题一:全局异常抽离出来后,业务错误码如何定义?

之前团队的业务错误码定义是:业务服务前缀 + 业务模块 + 错误码,如果是识别不了的异常,则使用业务前缀 + 固定模块码 + 固定错误码。

之前的全局异常伪代码如下

@RestControllerAdvice
@Slf4j
public class GlobalExceptionBaseHandler {

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public AjaxResult handleException(Exception e) {
        String servicePrifix = "U";
        String moudleCode = "001";
        String code = "0001";
        String errorCode = servicePrifix + moudleCode + code;
        String msg = e.getMessage();
        if(StringUtils.isEmpty(msg)){
            msg = "服务端异常";
        }
        log.error(msg, e);
        return AjaxResult.error(msg, errorCode);
    }
    }

现在全局异常抽离出来后,那个业务服务前缀如何识别?之前未抽离时,业务服务前缀各个业务服务直接写死在代码里。

当时我们临时的解决方案是通过spring.application.name来解决。因为全局异常代码块抽离出来后,最终还是要被服务引入的。因此获取业务服务前缀的伪代码可以通过如下获取

public enum  ServicePrefixEnum {

    USER_SERVICE("U","用户中心");

    private final String servicePrefix;

    private final String serviceDesc;

    ServicePrefixEnum(String servicePrefix,String serviceDesc) {
        this.servicePrefix = servicePrefix;
        this.serviceDesc = serviceDesc;
    }

    public String getServicePrefix() {
        return servicePrefix;
    }

    public String getServiceDesc() {
        return serviceDesc;
    }
}
public String getServicePrefix(@Value("${spring.application.name}") String serviceName){
      return ServicePrefixEnum.valueOf(serviceName).getServicePrefix();
    }

但这种方案其实是存在弊端

弊端一: 通过枚举硬编码,预设了目前了微服务名称,一旦项目改变了微服务名,就找不到服务前缀了。

弊端二: 如果新上线了业务服务模块,这个枚举类还得改动

后面我们在全局异常jar中增加了自定义业务码的配置,业务人员仅需在springboot配置文件配置,形如下

lybgeek:
  bizcode:
    prefix: U

此时全局异常改造示例形如下

@RestControllerAdvice
@Slf4j
public class GlobalExceptionBaseHandler {

    @Autowired
    private ServiceCodeProperties serviceCodeProperties;

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public AjaxResult handleException(Exception e) {
        String servicePrifix = serviceCodeProperties.getPrifix();
        String moudleCode = "001";
        String code = "0001";
        String errorCode = servicePrifix + moudleCode + code;
        String msg = e.getMessage();
        if(StringUtils.isEmpty(msg)){
            msg = "服务端异常";
        }
        log.error(msg, e);
        return AjaxResult.error(msg, errorCode);
    }
}

问题二:全局异常因引入了和业务相同的依赖jar,但jar存在版本差异

如果全局异常直接如下写,是不存在问题。示例如下

@RestControllerAdvice
@Slf4j
public class GlobalExceptionBaseHandler {

    @Autowired
    private ServiceCodeProperties serviceCodeProperties;

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public AjaxResult handleException(Exception e) {
        String servicePrifix = serviceCodeProperties.getPrifix();
        String moudleCode = "001";
        String code = "0001";
        String errorCode = servicePrifix + moudleCode + code;
        String msg = e.getMessage();
        if(StringUtils.isEmpty(msg)){
            msg = "服务端异常";
        }
        log.error(msg, e);
        return AjaxResult.error(msg, HttpStatus.INTERNAL_SERVER_ERROR.value());
    }

    @ExceptionHandler(BizException.class)
    public AjaxResult handleException(BizException e)
    {
        return AjaxResult.error(e.getMessage(), e.getErrorCode());
    }

}

即全局异常直接分为业务异常和Execption这两种,这样划分的弊端在于没办法细分异常,而且也使项目组定义的模块码和业务码没法细分。因此我们也列出常用可以预知的系统异常,示例如下

/**
     *参数验证失败
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public AjaxResult handleException(ConstraintViolationException e)
    {
        log.error("参数验证失败", e);
        return AjaxResult.error("参数验证失败", HttpStatus.BAD_REQUEST.value());
    }

   /**
     * 数据库异常
     * @param e
     * @return
     */
    @ExceptionHandler({SQLException.class, MybatisPlusException.class,
            MyBatisSystemException.class, org.apache.ibatis.exceptions.PersistenceException.class,
            BadSqlGrammarException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public AjaxResult dbException(Exception e) {
        String msg = ExceptionUtil.getExceptionMessage(e);
        log.error(msg, e);
        return AjaxResult.error(msg,HttpStatus.BAD_REQUEST.value());
    }

    /**
     * 数据库中已存在该记录
     * @param e
     * @return
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public AjaxResult handleException(DuplicateKeyException e)
    {
        log.error("数据库中已存在该记录", e);
        return AjaxResult.error("数据库中已存在该记录", HttpStatus.CONFLICT.value());
    }

不过这样导致了一个问题,就是全局异常和业务方使用相同的依赖jar,但存在版本差异时,可能就会存在依赖冲突,导致业务项目启动报错。因此解决方案就是在pom文件加入optional标签。示例如下

<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <optional>true</optional>
        </dependency>

这标签的意思这jar坐标是可选的,因此如果项目中已经有引入该jar的坐标,就直接用该jar的坐标

问题三:引入maven optional标签后,因业务没引入全局异常需要的jar,导致项目启动报错

这个问题的产生:举个示例,我们的业务微服务项目有聚合层,某些聚合层是不需要依赖存储介质,比如mysql。因此这些聚合层项目pom就不会引入类似mybatis相关的依赖。但我们的全局异常又需要类似mybatis相关的依赖,这样导致如果要引用全局异常模块,有得额外加入业务方不需要的jar。

因此springboot的条件注解就派上用场了,利用@ConditionalOnClass注解。示例如下

@RestControllerAdvice
@Slf4j
@ConditionalOnClass({SQLException.class, MybatisPlusException.class,
        MyBatisSystemException.class, org.apache.ibatis.exceptions.PersistenceException.class,
        BadSqlGrammarException.class, DuplicateKeyException.class})
public class GlobalExceptionDbHandler {

    /**
     * 数据库异常
     * @param e
     * @return
     */
    @ExceptionHandler({SQLException.class, MybatisPlusException.class,
            MyBatisSystemException.class, org.apache.ibatis.exceptions.PersistenceException.class,
            BadSqlGrammarException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public AjaxResult dbException(Exception e) {
        String msg = ExceptionUtil.getExceptionMessage(e);
        log.error(msg, e);
        return AjaxResult.error(msg,HttpStatus.BAD_REQUEST.value());
    }

    /**
     * 数据库中已存在该记录
     * @param e
     * @return
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public AjaxResult handleException(DuplicateKeyException e)
    {
        log.error("数据库中已存在该记录", e);
        return AjaxResult.error("数据库中已存在该记录", HttpStatus.CONFLICT.value());
    }
}

@ConditionalOnClass这个注解的作用就是如果classpath存在指定的类,则该注解上的类会生效。

同时这边有个细节点,就是全局异常可能就得细分,即把原来的大一统的全局异常,按业务场景分开,比如存储介质相关的存储异常,web相关异常

总结

本文主要讲当将全局异常抽离成jar,可能会发生的问题。这边有涉及到一些细节点没讲,比如为啥要定义服务前缀+业务模块码+错误码,其实主要还是为了好排查问题。

也许有朋友会问,你们都搞了微服务,难道不上分布式链路追踪?根据分布式链路追踪可以很方便定位到整个链路了。但真的开发微服务的时候,如果公司原来就就没运维平台,有时候为了成本考量,测试、开发环境都不会上的分布式链路追踪的,甚至线上项目初期也不会上分布式链路追踪。因此定义好相关的业务码就变得格外重要

到此这篇关于springboot项目全局异常处理那些事儿的文章就介绍到这了,更多相关springboot项目全局异常处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-exception

(0)

相关推荐

  • SpringBoot学习之全局异常处理设置(返回JSON)

    SpringBoot学习--全局异常处理设置(返回JSON) 需求 现在习惯使用ajax的方式发起请求,所以经常需要服务端返回一个json或者字符串. 控制全局的异常处理. 如果在单个方法中使用try,catch把方法包裹起来,工作量大,而且会异常的抛出而导致@Transactional注解的方法事务不会回滚. 说明 使用@ControllerAdvice注解 使用@ExceptionHandler注解 @ControllerAdvice 该注解是spring2.3以后新增的一个注解,主要是用来

  • SpringBoot如何优雅的处理全局异常

    前言 本篇文章主要介绍的是SpringBoot项目进行全局异常的处理. SpringBoot全局异常准备 说明:如果想直接获取工程那么可以直接跳到底部,通过链接下载工程代码. 开发准备 环境要求 JDK:1.8 SpringBoot:1.5.17.RELEASE 首先还是Maven的相关依赖: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.

  • SpringBoot如何优雅地处理全局异常详解

    前言 之前用springboot的时候,只知道捕获异常使用try{}catch,一个接口一个try{}catch,这也是大多数开发人员异常处理的常用方式,虽然屡试不爽,但会造成一个问题,就是一个Controller下面,满屏幕的try{}catch,看着一点都不优雅,一点都不符合小明的气质,憋了这么久,小明今天终于决定对所有异常实施统一处理的方案. 开发准备 JDK8.正常的springboot项目 代码编写 通用异常处理 其实Spring系列的项目全局异常处理方式早已存在,只不过我们一直忙于搬

  • BGPMPLS VPN的实现技术

    BGP/MPLS VPN的实现技术 摘要 MPLS VPN技术是未来构建VPN的发展方向,越来越受到客户和运营商的青睐.BGP/MPLS VPN是第三层MPLS VPN技术.本文在对VRF.RD.RT几个重要概念进行解释之后,对BGP/MPLS VPN的数据转发过程和路由信息分发过程进行了较为详细的叙述.最后通过分析BGP/MPLS VPN的特点,分析了其市场前景. 关键词 多协议标签交换(MPLS) 虚拟局域网(VPN) 一.VPN技术概述 VPN(Virtual Private Networ

  • springboot全局异常处理详解

    一.单个controller范围的异常处理 package com.xxx.secondboot.web; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import

  • Springboot之自定义全局异常处理的实现

    前言: 在实际的应用开发中,很多时候往往因为一些不可控的因素导致程序出现一些错误,这个时候就要及时把异常信息反馈给客户端,便于客户端能够及时地进行处理,而针对代码导致的异常,我们一般有两种处理方式,一种是throws直接抛出,一种是使用try..catch捕获,一般的话,如果逻辑的异常,需要知道异常信息,我们往往选择将异常抛出,如果只是要保证程序在出错的情况下 依然可以继续运行,则使用try..catch来捕获. 但是try..catch会导致代码量的增加,让后期我们的代码变得臃肿且难以维护.当

  • 深入聊一聊springboot项目全局异常处理那些事儿

    目录 前言 问题一:全局异常抽离出来后,业务错误码如何定义? 问题二:全局异常因引入了和业务相同的依赖jar,但jar存在版本差异 问题三:引入maven optional标签后,因业务没引入全局异常需要的jar,导致项目启动报错 总结 demo链接 前言 之前我们业务团队在处理全局异常时,在每个业务微服务中都加入了@RestControllerAdvice+@ExceptionHandler来进行全局异常捕获.某次领导在走查代码的时候,就提出了一个问题,为什么要每个微服务项目都要自己在写一套全

  • SpringBoot实现全局异常处理方法总结

    目录 全局异常处理 配置全局异常 全局异常处理的升级 加入自定义异常处理 处理Controller数据绑定.数据校验的异常 GlobalExceptionHandler全部代码 总结 在项目开发中出现异常时很平常不过的事情,我们处理异常也有很多种方式,可能如下: public int div(int a ,int b){ int c=0; try{ c=a/b; }catch (Exception ex){ ex.printStackTrace(); } return c; } 如果我们这样处理

  • springboot结合全局异常处理实现登录注册验证

    在学校做一个校企合作项目,注册登录这一块需要对注册登录进行输入合法的服务器端验证,因为是前后端分离开发,所以要求返回JSON数据. 方法有很多,这觉得用全局异常处理比较容易上手 全局异常处理 首先来创建一个sprIngboot的web项目或模块,目录结构如下 实体类User.java @Data public class User { private String userName; private String passwold; } 实体类UserResult.java 把数据封装到这里返回

  • springboot实现全局异常处理及自定义异常类

    目录 全局异常处理及自定义异常类 全局异常处理 定义一个业务异常的枚举 全局异常处理配置 springbootRestful使用 springboot返回ModelAndView 全局异常处理及自定义异常类 全局异常处理 定义一个处理类,使用@ControllerAdvice注解. @ControllerAdvice注解:控制器增强,一个被@Component注册的组件. 配合@ExceptionHandler来增强所有的@requestMapping方法. 例如:@ExceptionHandl

  • SpringBoot项目优雅的全局异常处理方式(全网最新)

    前言 在日常项目开发中,异常是常见的,但是如何更高效的处理好异常信息,让我们能快速定位到BUG,是很重要的,不仅能够提高我们的开发效率,还能让你代码看上去更舒服,SpringBoot的项目已经对有一定的异常处理了,但是对于我们开发者而言可能就不太合适了,因此我们需要对这些异常进行统一的捕获并处理. 一.全局异常处理方式一 SpringBoot中,@ControllerAdvice 即可开启全局异常处理,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用@ExceptionHandl

  • 详解如何在SpringBoot项目中使用全局异常处理

    目录 1. 创建自定义异常 2.创建全局异常处理器 3.创建测试控制器 在完整的项目开发中,异常的出现几乎是无法避免的:如果凡是有可能出现异常的地方,我们都手动的使用try-catch将其捕获的话,虽然也能达到处理异常的效果,但是这样做会使得代码显得十分臃肿并且后期不好维护,也不利于多人系统开发. 在Spring Boot中提供了统一处理异常的方法,SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用Exceptio

  • springboot全局异常处理代码实例

    这篇文章主要介绍了springboot全局异常处理代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言: 开发中异常的处理必不可少,常用的就是 throw 和 try catch,这样一个项目到最后会出现很多冗余的代码,使用全局异常处理避免过多冗余代码. 全局异常处理: 1.pom 依赖(延续上一章代码): <dependencies> <!-- Spring Boot Web 依赖 --> <!-- Web 依赖

  • SpringBoot中如何统一接口返回与全局异常处理详解

    目录 背景 统一接口返回 定义API返回码枚举类 定义正常响应的API统一返回体 定义异常响应的API统一返回体 编写包装返回结果的自定义注解 定义返回结果拦截器 WebMvc配置类拦截器注册者添加返回结果拦截器 编写响应体处理器 接口调用 测试结果 全局异常处理 编写自定义异常基类 编写自定义业务异常类 定义全局异常处理类 接口调用 测试结果 总结 背景 在分布式.微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式.前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,

  • SpringBoot全局异常处理方式

    目录 SpringBoot全局异常处理 springboot全局异常处理--@ControllerAdvice+ExceptionHandler 一.全局捕获异常后,返回json给浏览器 二.全局捕获异常后,返回页面给浏览器 SpringBoot全局异常处理 为了让客户端能有一个更好的体验,当客户端发送请求到服务端发生错误时服务端应该明确告诉客户端错误信息. SpringBoot内置的异常处理返回的界面太杂乱,不够友好.我们需要将异常信息做封装处理响应给前端.本文介绍的为将错误信息统一封装成如下

随机推荐