千家信息网

springboot项目全局异常处理会遇到哪些问题

发表于:2024-11-12 作者:千家信息网编辑
千家信息网最后更新 2024年11月12日,这篇文章主要介绍了springboot项目全局异常处理会遇到哪些问题,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。问题一:全局异常抽离
千家信息网最后更新 2024年11月12日springboot项目全局异常处理会遇到哪些问题

这篇文章主要介绍了springboot项目全局异常处理会遇到哪些问题,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

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

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

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

@RestControllerAdvice@Slf4jpublic 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@Slf4jpublic 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@Slf4jpublic 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标签。示例如下

            com.baomidou            mybatis-plus-boot-starter            true        

这标签的意思这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相关异常

感谢你能够认真阅读完这篇文章,希望小编分享的"springboot项目全局异常处理会遇到哪些问题"这篇文章对大家有帮助,同时也希望大家多多支持,关注行业资讯频道,更多相关知识等着你来学习!

0