首页 > 其他分享 >Springboot 开发 -- 统一异常处理最佳实践

Springboot 开发 -- 统一异常处理最佳实践

时间:2024-06-01 20:33:15浏览次数:32  
标签:AjaxResult return Springboot -- 最佳 error msg public String

引言

在企业级应用开发中,异常处理是保障系统稳定性和可维护性的关键环节。Spring Boot 提供了一系列优雅的异常处理机制,帮助开发者更高效地管理和响应异常情况。本文将总结 Spring Boot 中异常处理的最佳实践。

一、异常处理原则

在进行异常处理之前,需要明确几个基本的处理原则:

  • 使用异常而非返回码:避免使用错误码,因为它们会使调用者代码变得复杂,容易出错。
  • 提供异常发生的环境说明:每个异常都应提供足够的上下文信息,以便于快速定位问题。
  • 避免返回 null 值:返回 null 值会增加调用者检查 null 的负担,推荐使用 Optional 或自定义异常。

二、Spring Boot 中的异常处理机制

Spring Boot 基于 Spring Framework,因此继承了其异常处理机制。Spring 通过使用 @ControllerAdvice@RestControllerAdvice 注解的类来全局处理异常。
在这里插入图片描述

1. @ControllerAdvice 配置全局异常处理

Springboot 提供了一个 @ControllerAdvice 注解以及 @ExceptionHandler注解前者是用来开启全局的异常捕获后者则是说明捕获哪些异常,对那些异常进行处理

@RestControllerAdvice 是 @ControllerAdvice 的特化,专门用于@RestController。

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage());
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

2. 异常处理的定制化

  • 对于不同类型的异常,可以定制化处理逻辑。
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<Object> handleCustomException(CustomException ex, WebRequest request) {
    ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getCustomMessage());
    return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
  • 自定义异常CustomException.java
public class CustomException extends RuntimeException {
    private String customMessage;

    public CustomException(String message) {
        super(message);
        this.customMessage = message;
    }

    public String getCustomMessage() {
        return customMessage;
    }
}
  • 异常信息的封装
public class ErrorDetails {
    private Date timestamp;
    private String message;

    public ErrorDetails(Date timestamp, String message) {
        this.timestamp = timestamp;
        this.message = message;
    }

    // Getters and Setters
}
  • 使用 @ResponseStatus
    在控制器方法中,可以使用 @ResponseStatus 注解来指定异常的 HTTP 状态码。
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    // 异常类实现
}

三、实际使用案例

1. 自定义全局异常处理类 GlobalExceptionHandler.java

package com.dz.springdemo.exception;

import com.dz.springdemo.constant.HttpStatus;
import com.dz.springdemo.entity.AjaxResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * @description: 全局异常处理器
 * @author: dazhong2012
 * @date: 2024/5/29 21:49
 **/
@RestControllerAdvice
public class GlobalExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 权限校验异常
     */
    @ExceptionHandler(AccessDeniedException.class)
    public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
            HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
    {
        log.error(e.getMessage(), e);
        Integer code = e.getCode();
        return Objects.nonNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
    }

    /**
     * 请求路径中缺少必需的路径变量
     */
    @ExceptionHandler(MissingPathVariableException.class)
    public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
        return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
    }

    /**
     * 请求参数类型不匹配
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
        return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
    }

    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生未知异常.", requestURI, e);
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生系统异常.", requestURI, e);
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult handleBindException(BindException e)
    {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
    {
        log.error(e.getMessage(), e);
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return AjaxResult.error(message);
    }

}

2. 自定义业务异常类

package com.dz.springdemo.exception;

/**
 * 业务异常
 * 
 * @author dazhong2012
 */
public final class ServiceException extends RuntimeException
{
    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    private Integer code;

    /**
     * 错误提示
     */
    private String message;

    /**
     * 错误明细,内部调试错误
     *
     * 和 {@link CommonResult#getDetailMessage()} 一致的设计
     */
    private String detailMessage;

    /**
     * 空构造方法,避免反序列化问题
     */
    public ServiceException()
    {
    }

    public ServiceException(String message)
    {
        this.message = message;
    }

    public ServiceException(String message, Integer code)
    {
        this.message = message;
        this.code = code;
    }

    public String getDetailMessage()
    {
        return detailMessage;
    }

    @Override
    public String getMessage()
    {
        return message;
    }

    public Integer getCode()
    {
        return code;
    }

    public ServiceException setMessage(String message)
    {
        this.message = message;
        return this;
    }

    public ServiceException setDetailMessage(String detailMessage)
    {
        this.detailMessage = detailMessage;
        return this;
    }
}

3.自定义接口响应类 AjaxResult.java

package com.dz.springdemo.entity;


import com.dz.springdemo.constant.HttpStatus;

import java.util.HashMap;
import java.util.Objects;

/**
 * @description: 接口响应类
 * @author: dazhong2012
 * @date: 2024/5/29 22:21
 **/
public class AjaxResult extends HashMap<String, Object>
{
    private static final long serialVersionUID = 1L;

    /** 状态码 */
    public static final String CODE_TAG = "code";

    /** 返回内容 */
    public static final String MSG_TAG = "msg";

    /** 数据对象 */
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult()
    {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     * 
     * @param code 状态码
     * @param msg 返回内容
     */
    public AjaxResult(int code, String msg)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     * 
     * @param code 状态码
     * @param msg 返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (Objects.nonNull(data))
        {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 返回成功消息
     * 
     * @return 成功消息
     */
    public static AjaxResult success()
    {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     * 
     * @return 成功消息
     */
    public static AjaxResult success(Object data)
    {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     * 
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg)
    {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     * 
     * @param msg 返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult warn(String msg)
    {
        return AjaxResult.warn(msg, null);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult warn(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.WARN, msg, data);
    }

    /**
     * 返回错误消息
     * 
     * @return 错误消息
     */
    public static AjaxResult error()
    {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     * 
     * @param msg 返回内容
     * @return 错误消息
     */
    public static AjaxResult error(String msg)
    {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     * 
     * @param msg 返回内容
     * @param data 数据对象
     * @return 错误消息
     */
    public static AjaxResult error(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * 返回错误消息
     * 
     * @param code 状态码
     * @param msg 返回内容
     * @return 错误消息
     */
    public static AjaxResult error(int code, String msg)
    {
        return new AjaxResult(code, msg, null);
    }

    /**
     * 是否为成功消息
     *
     * @return 结果
     */
    public boolean isSuccess()
    {
        return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
    }

    /**
     * 是否为警告消息
     *
     * @return 结果
     */
    public boolean isWarn()
    {
        return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG));
    }

    /**
     * 是否为错误消息
     *
     * @return 结果
     */
    public boolean isError()
    {
        return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
    }

    /**
     * 方便链式调用
     *
     * @param key 键
     * @param value 值
     * @return 数据对象
     */
    @Override
    public AjaxResult put(String key, Object value)
    {
        super.put(key, value);
        return this;
    }
}

标签:AjaxResult,return,Springboot,--,最佳,error,msg,public,String
From: https://blog.csdn.net/dazhong2012/article/details/139278153

相关文章

  • WSL子系统文件迁移至其他磁盘
    WSL系统迁移准备工作,查看存在的linux子系统wsl-l#适用于Linux的Windows子系统分发:#Ubuntu-22.04(默认)#docker-desktop#docker-desktop-data以Ubuntu-22.04为例,注意记录下系统原来的用户名,按顺序执行:#关闭所有wsl服务wsl--shutdown#将WSL文件导出为tar文......
  • 【Python内功心法】:深挖内置函数,释放语言潜能
    文章目录......
  • 鸿蒙HarmonyOS实战-Web组件(请求响应和页面调试)
    ......
  • 数据分析实战2---时间序列分析
    项目背景:本实训以自行车租赁统计数据为例,使用Pandas中的时间序列分析方法,探究自行车租赁数据随时间及天气变化的分布情况。采用的数据可在Kaggle网站(BikeSharingDemandStella|Kaggle)下载任务步骤:1.导入模块importpandasaspdimportnumpyasnpimportmatplotlib.......
  • remo/海尔系随身wifi改串、开adb、去控
    方案记录如下,使用后果自负!如果有人用来逃费,甚至拿出去倒卖的话,我只能说...格局小了!remo自家的棒子太棒了!!!料很足,信号T0级别竟然有两张esim,一张电信一张联通这里介绍的是不需要编程器的玩法关于卡槽:看运气,有的有,有的需要自己焊改串、开adb:自行百度(被警告了)打开AT端口,不出......
  • 项目管理---计算机英语常见词汇
    项目管理英语词汇常见的计算机词汇Agile敏捷APIApplicationProgramminInterface应用程序编程接口ApplicationLayer应用层ArtificialIntelligence人工智能Augmented Reality增强现实Availability可用性B/S:Browser/ServerBeidouSatelliteNavigationSystem......
  • ERROR Failed to compile with 1 error
    解决方法一:重新运行:npmrunserve(每个人情况不定)解决方法二:可能是文件中有中文名,将该项目文件名称及该项目文件的上一层命名为纯英文。重新:npmrunserve解决方法三:修改相关的 webpack 配置文件把 index.html 文件重命名为 index.ejs 文件在 node_nodul......
  • 2024最新西瓜视频收益玩法,一台电脑即可 新手小白简单操作单号月入1800+
    在数字时代的浪潮中,短视频领域成为了一个巨大的流量池。抖音,无疑站在了这股浪潮的顶端,吸引了无数的观众和创作者。然而,对于初出茅庐的新手来说,要想在抖音中脱颖而出,并非易事。很多时候,成功似乎和运气有着千丝万缕的联系,这让许多新手感到无从下手。幸运的是,随着视频号的......
  • 人工智能未来的发展
    人工智能(AI)未来的发展将呈现多元化、深度化和广泛化的趋势。以下是关于AI未来发展的详细分析:技术进步:智能化程度提升:随着算法和计算能力的不断提升,AI将更加智能化,能够更好地理解人类语言、情感和行为,实现更自然的交互。深度学习与强化学习融合:这两种学习方法的结合将使AI......
  • 生活中的人工智能
    生活中的人工智能应用广泛,涵盖了多个领域,以下是一些主要的应用场景和详细介绍:智能家居:智能家居控制:利用AI技术,实现对智能家居设备的智能化控制,如智能灯光、智能窗帘、智能音响、智能门锁等设备的语音控制、手势控制、移动APP控制等。智能家居场景:通过AI技术实现智能家居场......