乐趣区

关于springboot:SpringBoot如何优雅地进行响应数据封装异常处理

背景

越来越多的我的项目开始基于前后端拆散的模式进行开发,这对后端接口的报文格式便有了肯定的要求。通常,咱们会采纳 JSON 格局作为前后端替换数据格式,从而缩小沟通老本等。

这篇文章,就带大家理解一下基于 SpringBoot 框架来封装返回报文以及对立异样解决。

报文根本格局

个别报文格式通常会蕴含状态码、状态形容(或谬误提示信息)、业务数据等信息。在此基础上,不同的架构师、我的项目搭建者可能会有所调整。但从整体上来说,基本上都是大同小异。

在 SpringBoot 我的项目中,通常接口返回的报文中至多蕴含三个属性:

  • code:申请接口的返回码,胜利或者异样等返回编码,例如定义申请胜利。
  • message:申请接口的形容,也就是对返回编码的形容。
  • data:申请接口胜利,返回的业务数据。

示例报文如下:

{
  "code":200,
  "message":"SUCCESS",
  "data":{"info":"测试胜利"}
}

在上述报文格式中,不同的设计者是会有一些一致的,特地是 code 值的定义。如果齐全基于 RESTful API 设计的话,code 字段可能就不须要存在了,而是通过 HTTP 协定中提供的 GET、POST、PUT、DELETE 操作等来实现资源的拜访。

但在实践中,不论是出于目前国内大多数程序员的习惯,还是受限于 HTTP 协定提供的操作方法的局限性,很少齐全遵循 RESTful API 形式进行设计。通常都是通过自定义 Code 值的模式来赋予它业务意义或业务谬误编码。

尽管能够不必齐全恪守 RESTful API 格调来定义 Code,在 Code 值的自定义中,也存在两种模式:遵循 HTTP 状态码和自主定义。

像下面的示例,用 200 示意返回胜利,这就是遵循 HTTP 响应状态码的模式来返回,比方还有其余的 400、401、404、500 等。当然,还有齐全自主定义的,比方用 0 示意胜利,1 示意失败,而后再跟进通用编码、业务分类编码等进行定义。

在此,笔者暂不评论每种模式的好坏,只列举了惯例的几种模式,大家理解对应的状况,做到成竹在胸,有所抉择即可。

响应参数封装实际

创立一个 SpringBoot 我的项目,并引入 Lombok 依赖(精简代码),对应的外围依赖如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

创立枚举类,用于定义返回的错误码:

@Getter
@AllArgsConstructor
public enum ResponseCodeEnums {SUCCESS(200, "success"),
    FAIL(500, "failed"),

    HTTP_STATUS_200(200, "ok"),
    HTTP_STATUS_400(400, "request error"),
    HTTP_STATUS_401(401, "no authentication"),
    HTTP_STATUS_403(403, "no authorities"),
    HTTP_STATUS_500(500, "server error");

    private final int code;

    private final String message;
}

这里只定义了一些通用的、基于的 HTTP 响应状态码,业务相干的编码可依据业务需要进行定义。

定义对立返回后果实体类:

@Data
public class ResponseInfo<T> {

    /**
     * 状态码
     */
    protected int code;

    /**
     * 响应信息
     */
    protected String message;

    /**
     * 返回数据
     */
    private T data;

    public static <T> ResponseInfo<T> success() {return new ResponseInfo<>();
    }

    public static <T> ResponseInfo<T> success(T data) {return new ResponseInfo<>(data);
    }

    public static <T> ResponseInfo<T> fail(String message) {return new ResponseInfo<>(ResponseCodeEnums.FAIL.getCode(), message);
    }

    public ResponseInfo() {this.code = ResponseCodeEnums.SUCCESS.getCode();
        this.message = ResponseCodeEnums.SUCCESS.getMessage();}

    public ResponseInfo(ResponseCodeEnums statusEnums) {this.code = statusEnums.getCode();
        this.message = statusEnums.getMessage();}

    /**
     * 若没有数据返回,能够人为指定状态码和提示信息
     */
    public ResponseInfo(int code, String msg) {
        this.code = code;
        this.message = msg;
    }

    /**
     * 有数据返回时,状态码为 200,默认提示信息为“操作胜利!”*/
    public ResponseInfo(T data) {
        this.data = data;
        this.code = ResponseCodeEnums.SUCCESS.getCode();
        this.message = ResponseCodeEnums.SUCCESS.getMessage();}

    /**
     * 有数据返回,状态码为 200,人为指定提示信息
     */
    public ResponseInfo(T data, String msg) {
        this.data = data;
        this.code = ResponseCodeEnums.SUCCESS.getCode();
        this.message = msg;
    }
}

在 ResponseInfo 中使用了泛型和公共办法、构造方法的封装,不便在业务中应用。示例中只提供了局部办法的封装,依据本身业务场景和须要可进一步封装。

对立报文封装在接口中的应用:

@Slf4j
@RestController
public class TestController {@RequestMapping("/calc")
    public ResponseInfo<String> calc(Integer id) {
        try {
            // 模仿异样业务代码
            int num = 1 / id;
            log.info("计算结果 num={}", num);
            return ResponseInfo.success();} catch (Exception e) {return ResponseInfo.fail("零碎异样,请分割管理员!");
        }
    }
}

在浏览器中拜访:http://localhost:8080/calc,返回后果如下:

{
    "code": 500,
    "message": "零碎异样,请分割管理员!",
    "data": null
}

这是因为没传递 id 参数,导致业务抛异样,走异样报文返回。

在浏览器中拜访:http://localhost:8080/calc?id=1,返回后果如下:

{
    "code": 200,
    "message": "success",
    "data": null
}

失常返回后果。

对立异样解决

在上述实例中,咱们通过 try…catch 的模式捕捉异样,并进行解决。在 SpringBoot 中,咱们能够通过 RestControllerAdvice 注解来定义全局异样解决,这样就无需每处都 try…catch 了。

@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {

    /**
     * 参数格局异样解决
     */
    @ExceptionHandler({IllegalArgumentException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseInfo<String> badRequestException(IllegalArgumentException ex) {log.error("参数格局不非法:{}", ex.getMessage());
        return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + ""," 参数格局不符!");
    }

    /**
     * 权限有余异样解决
     */
    @ExceptionHandler({AccessDeniedException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ResponseInfo<String> badRequestException(AccessDeniedException ex) {return new ResponseInfo<>(HttpStatus.FORBIDDEN.value() + "", ex.getMessage());
    }

    /**
     * 参数缺失异样解决
     */
    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseInfo<String> badRequestException(Exception ex) {return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + ""," 短少必填参数!");
    }

    /**
     * 空指针异样
     */
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo<String> handleTypeMismatchException(NullPointerException ex) {log.error("空指针异样,{}", ex.getMessage());
        return ResponseInfo.fail("空指针异样");
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo<String> handleUnexpectedServer(Exception ex) {log.error("零碎异样:", ex);
        return ResponseInfo.fail("零碎产生异样,请分割管理员");
    }

    /**
     * 零碎异样解决
     */
    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo<String> exception(Throwable throwable) {log.error("零碎异样", throwable);
        return new ResponseInfo<>(HttpStatus.INTERNAL_SERVER_ERROR.value() + "零碎异样,请分割管理员!");
    }
}

在上述办法中,对一些常见的异样进行了对立解决。通常状况下,依据业务须要还会定义业务异样,并对业务异样进行解决,大家能够依据本人我的项目中异样的应用状况进行拓展。

对于 @RestControllerAdvice 的几点阐明:

  • @RestControllerAdvice 注解蕴含了 @Component 注解,会把被注解的类作为组件交给 Spring 来治理。
  • @RestControllerAdvice 注解蕴含了 @ResponseBody 注解,异样解决完之后给调用方输入一个 JSON 格局的封装数据。
  • @RestControllerAdvice 注解有一个 basePackages 属性,该属性用来拦挡哪个包中的异样信息,个别不指定,拦挡我的项目工程中的所有异样。
  • 在办法上通过 @ExceptionHandler 注解来指定具体的异样,在办法中解决该异样信息,最初将后果通过对立的 JSON 构造体返回给调用者。

从新定义一个接口:

    @RequestMapping("/calc1")
    public ResponseInfo<String> calc1(Integer id) {
        // 模仿异样业务代码
        int num = 1 / id;
        log.info("计算结果 num={}", num);
        return ResponseInfo.success();}

在申请的时候,不传递 id 值,即在浏览器中拜访:

{
    "code": 500,
    "message": "空指针异样",
    "data": null
}

能够看到对立异样解决对空指针异样进行了拦挡解决,并返回了 ExceptionHandlerAdvice 中定义的对立报文格式。

小结

在应用 SpringBoot 或其余我的项目中,对立的报文格式和对立的异样解决都是必须的。本篇文章介绍了基于 SpringBoot 的实现,如果你的我的项目中采纳了其余的技术栈,则可思考对应的解决形式。同时,日常中很多相似的性能都能够对立进行解决,防止大量有效的硬编码。

博主简介:《SpringBoot 技术底细》技术图书作者,热爱钻研技术,写技术干货文章。

公众号:「程序新视界」,博主的公众号,欢送关注~

技术交换:请分割博主微信号:zhuan2quan

退出移动版