SpringBoot-23-全局异常机制+RESTful统一规范

简介: SpringBoot-23-全局异常机制+RESTful统一规范

SpringBoot-23-全局异常机制+RESTful统一规范

1.为什么需要全局异常机制?



如果我们团队在开发项目的时候,程序出现异常是正常的,比如因为业务操作没有按照流程,程序的运行异常等等,我们不可能也不应该每一处异常进行单独处理,或者不处理将异常信息直接抛出给用户,这样会导致用户体验差。



因此设置统一的异常处理机制具有以下好处:


  • 输出日志可以增强log的可读性,对我们自己排除bug也增加优势,
  • 提高用户体验性
  • 降低前后端开发和维护成本(如果每一个后台开发抛出一场形式不一样,没有统一规范,前端每一个请求都会有一套处理异常逻辑,代码质量低,维护成本高)


那么我们需要如何的开发规范呢?


Service、Controlle等层捕获的异常,需要转换为自定义异常,然后对外抛出


设置统一的RESTful规范,常见的状态使用http状态,如果是业务比较复杂的,比如智能制造类系统,也要设置自定义的状态和相对应的message。


将捕获的异常message信息,通过自定义异常转换为易读易懂的message信息。


获取catch的时候,不要直接Exception,要尽可能分类exception,使得异常变得清晰。


之前章节也讲过


全局异常处理和


RESTful统一规范响应数据格式


不熟悉的朋友可以去看一下。


代码实现

RESTful统一返回规范设置


我们在上一章节实际上已经介绍过了,但是怕一些人没有看过,这里再进行介绍一次

  • 设置统一的IResultCode返回码接口
/**
 * 统一返回结果接口
 */
public interface IResultCode {
    /**
     * 返回码
     *
     * @return int
     */
    int getCode();
    /**
     * 返回消息
     *
     * @return String
     */
    String getMsg();
}


  • 设置ResultCode的返回码接口的实现
@Getter
@AllArgsConstructor
public enum  ResultCode implements  IResultCode{
    /**
     * 操作成功
     */
    SUCCESS(200, "操作成功"),
    /**
     * 业务异常
     */
    FAILURE(400, "业务异常"),
    /**
     * 服务异常
     */
    ERROR(500, "服务异常"),
    /**
     * 参数错误
     */
    GLOBAL_PARAM_ERROR(4000, "参数错误");
    /**
     * 状态码
     */
    final int code;
    /**
     * 消息内容
     */
    final String msg;
}
  • 统一返回结果Result
@Data
@Getter
public class Result<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    private int code;
    private String msg;
    private long time;
    private T data;
    private Result() {
        this.time = System.currentTimeMillis();
    }
    private Result(IResultCode resultCode) {
        this(resultCode, null, resultCode.getMsg());
    }
    private Result(IResultCode resultCode, String msg) {
        this(resultCode, null, msg);
    }
    private Result(IResultCode resultCode, T data) {
        this(resultCode, data, resultCode.getMsg());
    }
    private Result(IResultCode resultCode, T data, String msg) {
        this(resultCode.getCode(), data, msg);
    }
    private Result(int code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
        this.time = System.currentTimeMillis();
    }
    /**
     * 返回状态码
     *
     * @param resultCode 状态码
     * @param <T>        泛型标识
     * @return ApiResult
     */
    public static <T> Result<T> success(IResultCode resultCode) {
        return new Result<>(resultCode);
    }
    public static <T> Result<T> success(String msg) {
        return new Result<>(ResultCode.SUCCESS, msg);
    }
    public static <T> Result<T> success(IResultCode resultCode, String msg) {
        return new Result<>(resultCode, msg);
    }
    public static <T> Result<T> data(T data) {
        return data(data, "处理成功");
    }
    public static <T> Result<T> data(T data, String msg) {
        return data(ResultCode.SUCCESS.code, data, msg);
    }
    public static <T> Result<T> data(int code, T data, String msg) {
        return new Result<>(code, data, data == null ? "承载数据为空" : msg);
    }
    public static <T> Result<T> fail() {
        return new Result<>(ResultCode.FAILURE, ResultCode.FAILURE.getMsg());
    }
    public static <T> Result<T> fail(String msg) {
        return new Result<>(ResultCode.FAILURE, msg);
    }
    public static <T> Result<T> fail(int code, String msg) {
        return new Result<>(code, null, msg);
    }
    public static <T> Result<T> fail(IResultCode resultCode) {
        return new Result<>(resultCode);
    }
    public static <T> Result<T> fail(IResultCode resultCode, String msg) {
        return new Result<>(resultCode, msg);
    }
    public static <T> Result<T> condition(boolean flag) {
        return flag ? success("处理成功") : fail("处理失败");
    }
}



全局异常处理

  • 设置BaseException作为全局基础异常和特定的验证码异常处理类
@Data
public class BaseException extends RuntimeException {
    private static final long serialVersionUID = 5782968730281544562L;
    private int status = INTERNAL_SERVER_ERROR.value();
    public BaseException() {
    }
    public BaseException(String message) {
        super(message);
    }
    public BaseException(int status, String message) {
        super(message);
        this.status = status;
    }
}
@Data
public class ValidateCodeException extends RuntimeException {
    private static final long serialVersionUID = -7285211528095468156L;
    private int status = INTERNAL_SERVER_ERROR.value();
    public ValidateCodeException() {
    }
    public ValidateCodeException(String msg) {
        super(msg);
    }
    public ValidateCodeException(int code, String message) {
        super(message);
        this.status = code;
    }
}



  • 创建全局异常处理类BaseExceptionHandler

使用**@RestControllerAdvice+@ExceptionHandler**

具体介绍可以看我的全局异常处理介绍这里只说实现过程

@Slf4j
@ResponseBody
@RestControllerAdvice
public class BaseExceptionHandler {
    /**
     * BaseException 异常捕获处理
     * @param ex 自定义BaseException异常类型
     * @return Result
     */
    @ExceptionHandler(BaseException.class)
    public Result<?> handleException(BaseException ex) {
        log.error("程序异常:" + ex.toString());
        return Result.fail(ex.getStatus(), ex.getMessage());
    }
    /**
     * BaseException 异常捕获处理
     * @param ex 自定义BaseException异常类型
     * @return Result
     */
    @ExceptionHandler(ValidateCodeException.class)
    @ResponseStatus
    public Result<?> handleValidateCodeException(ValidateCodeException  ex) {
        log.error("验证码错误:" + ex.getMessage());
        return Result.fail(ex.getStatus(), ex.getMessage());
    }
    /**
     * FileNotFoundException,NoHandlerFoundException 异常捕获处理
     * @param exception 自定义FileNotFoundException异常类型
     * @return Result
     */
    @ExceptionHandler({FileNotFoundException.class, NoHandlerFoundException.class})
    public Result<?> noFoundException(Exception exception) {
        log.error("程序异常==>errorCode:{}, exception:{}", HttpStatus.NOT_FOUND.value(), exception.getMessage());
        return Result.fail(HttpStatus.NOT_FOUND.value(), exception.getMessage());
    }
    /**
     * NullPointerException 空指针异常捕获处理
     * @param ex 自定义NullPointerException异常类型
     * @return Result
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<?> handleException(NullPointerException ex) {
        log.error("程序异常:{}" + ex.toString());
        return Result.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage());
    }
    /**
     * 通用Exception异常捕获
     * @param ex 自定义Exception异常类型
     * @return Result
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<?> handleException(Exception ex) {
        log.error("程序异常:" + ex.toString());
        String message = ex.getMessage();
        if (StringUtils.contains(message, "Bad credentials")){
            message = "您输入的密码不正确";
        } else if (StringUtils.contains(ex.toString(), "InternalAuthenticationServiceException")) {
            message = "您输入的用户名不存在";
        }
        return Result.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }
}


控制层TestController的实现

@RequestMapping("test")
@RestController
public class TestController {
    @GetMapping("/base/{name}")
    public void BaseException(@PathVariable("name") String name) {
        System.out.println("Hello: BaseException "+ name);
        throw  new BaseException(HttpStatus.MULTI_STATUS,"错误");
    }
    @GetMapping("/valid/{name}")
    public void ValidateCodeException(@PathVariable("name") String name) {
        System.out.println("Hello:  ValidateCodeException "+ name);
        throw  new ValidateCodeException(ResultCode.GLOBAL_PARAM_ERROR.getCode(),ResultCode.GLOBAL_PARAM_ERROR.getMsg());
    }
}


测试


使用postman分别测试

  • http://localhost:8080/test/base/hah
  • http://localhost:8080/test/valid/hah

测试结果如下:


c79a7cc7363f460db2f4880bc9bfac5e.png


在上面图标记处不知道大家发现一个问题,自定义业务状态码和HttpStatus状态码存在不一致的情况,如果相应自定义的业务状态码,在HttpStatus存在,相应HttpStatus和自定义状态码一致需要怎么办呢?


HTTP和自定义状态码一致代码实现

@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        //如果响应结果是JSON数据类型
        if(selectedContentType.equalsTypeAndSubtype(
                MediaType.APPLICATION_JSON)){
            int code= ((Result) body).getCode();
            if(code>0 && code<512) {
                //HTTP响应结果设置状态码,状态码就是IResultCode的code,二者达到统一
                response.setStatusCode(
                        HttpStatus.valueOf(((Result) body).getCode())
                );
            }
            return body;
        }
        return body;
    }
}


测试结果:


ed4b27716b2742ca98dd9a05479d9a3d.png


如果您觉得本文不错, 欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!


目录
相关文章
|
6天前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
27 2
|
13天前
|
前端开发 小程序 Java
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
本文详细介绍了如何在SpringBoot项目中统一处理接口返回结果及全局异常。首先,通过封装`ResponseResult`类,实现了接口返回结果的规范化,包括状态码、状态信息、返回信息和数据等字段,提供了多种成功和失败的返回方法。其次,利用`@RestControllerAdvice`和`@ExceptionHandler`注解配置全局异常处理,捕获并友好地处理各种异常信息。
91 0
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
|
1月前
|
Java 数据库 开发者
深入剖析 SpringBoot 的 SPI 机制
【8月更文挑战第10天】在软件开发中,SPI(Service Provider Interface)机制是一种重要的服务发现和加载机制,尤其在构建模块化、可扩展的系统时尤为重要。SpringBoot作为Spring家族的一员,其内置的SPI机制不仅继承了Java SPI的设计思想,还进行了优化和扩展,以适应Spring Boot特有的需求。本文将深入剖析SpringBoot中的SPI机制,揭示其背后的原理与应用。
55 7
|
1月前
|
Java 开发者 Spring
"揭秘SpringBoot魔法SPI机制:一键解锁服务扩展新姿势,让你的应用灵活飞天!"
【8月更文挑战第11天】SPI(Service Provider Interface)是Java的服务提供发现机制,用于运行时动态查找和加载服务实现。SpringBoot在其基础上进行了封装和优化,通过`spring.factories`文件提供更集中的配置方式,便于框架扩展和组件替换。本文通过定义接口`HelloService`及其实现类`HelloServiceImpl`,并在`spring.factories`中配置,结合`SpringFactoriesLoader`加载服务,展示了SpringBoot SPI机制的工作流程和优势。
36 5
|
1月前
|
Java API 数据库
【神操作!】Spring Boot打造RESTful API:从零到英雄,只需这几步,让你的Web应用瞬间飞起来!
【8月更文挑战第12天】构建RESTful API是现代Web开发的关键技术之一。Spring Boot因其实现简便且功能强大而深受开发者喜爱。本文以在线图书管理系统为例,展示了如何利用Spring Boot快速构建RESTful API。从项目初始化、实体定义到业务逻辑处理和服务接口实现,一步步引导读者完成API的搭建。通过集成JPA进行数据库操作,以及使用控制器类暴露HTTP端点,最终实现了书籍信息的增删查改功能。此过程不仅高效直观,而且易于维护和扩展。
34 1
|
11天前
|
安全 Java UED
掌握SpringBoot单点登录精髓,单点登录是一种身份认证机制
【8月更文挑战第31天】单点登录(Single Sign-On,简称SSO)是一种身份认证机制,它允许用户只需在多个相互信任的应用系统中登录一次,即可访问所有系统,而无需重复输入用户名和密码。在微服务架构日益盛行的今天,SSO成为提升用户体验和系统安全性的重要手段。本文将详细介绍如何在SpringBoot中实现SSO,并附上示例代码。
29 0
|
14天前
|
消息中间件 Java Kafka
深入SpringBoot的心脏地带:掌握其核心机制的全方位指南
【8月更文挑战第29天】这段内容介绍了在分布式系统中起到异步通信与解耦作用的消息队列,并详细探讨了三种流行的消息队列产品:RabbitMQ、RocketMQ 和 Kafka。RabbitMQ 是一个基于 AMQP 协议的开源消息队列系统,支持多种消息模型,具有高可靠性及稳定性;RocketMQ 则是由阿里巴巴开源的高性能分布式消息队列,支持事务消息等多种特性;而 Kafka 是 LinkedIn 开源的分布式流处理平台,以其高吞吐量和良好的可扩展性著称。文中还提供了使用这三种消息队列产品的示例代码。总之,这三款产品各有优势,适用于不同场景。
9 0
|
14天前
|
消息中间件 Java Kafka
SpringBoot大揭秘:如何轻松掌握其核心机制?
【8月更文挑战第29天】这段内容介绍了在分布式系统中起到异步通信与解耦作用的消息队列,并详细探讨了三种流行的消息队列产品:RabbitMQ、RocketMQ 和 Kafka。RabbitMQ 是一个基于 AMQP 协议的开源消息队列系统,支持多种消息模型,具有高可靠性及稳定性;RocketMQ 则是由阿里巴巴开源的高性能分布式消息队列,支持事务消息等多种特性;而 Kafka 是 LinkedIn 开源的分布式流处理平台,以其高吞吐量和良好的可扩展性著称。文中还提供了使用这三种消息队列产品的示例代码。
10 0
WXM
|
1月前
|
存储 缓存 Java
|
18天前
|
消息中间件 Java 开发工具
【Azure 事件中心】Spring Cloud Stream Event Hubs Binder 发送Event Hub消息遇见 Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially 异常
【Azure 事件中心】Spring Cloud Stream Event Hubs Binder 发送Event Hub消息遇见 Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially 异常