重学SpringBoot3-Problemdetails

重学SpringBoot3-Problemdetails

CoderJia 121 2024-03-15

随着 Spring Boot 3 的发布,Spring Framework 6 继续在提升开发者体验和应用性能方面迈出重要步伐。在众多引人注目的新特性中,对 Problem Details(问题详情)的支持尤为值得关注。这一特性基于 RFC 7807 标准,旨在为 HTTP API 提供一种标准化的错误响应格式。本文将深入探讨 Spring Boot 3 中 Problem Details 的概念、应用及其对微服务架构的潜在影响。

Problem Details的概念

RFC 7807: https://www.rfc-editor.org/rfc/rfc7807

在 RESTful 架构的 API 设计中,如何有效地表达错误信息一直是一个挑战。RFC 7807 定义的 Problem Details 机制提供了一种标准化的方式,通过具体、一致的格式来传达错误信息,使得客户端能够更容易地理解和处理 API 响应中的错误。

一个典型的 Problem Details 响应包含以下几个基本字段:

  • type:一个URI,指向错误类型的详细描述文档(可选)。
  • title:简短、人类可读的错误概述。
  • status:HTTP状态码。
  • detail:人类可读的解释,提供更多关于问题的细节。
  • instance:指向导致问题的具体请求或实例的URI(可选)。

ProblemDetails配置类

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.ProblemDetailsErrorHandlingConfiguration

当满足以下条件时,这个 Problem Details 配置类会被加载:

  1. 属性 spring.mvc.problemdetails.enabled 的值为 true;
  2. 没有其他相同类型的 Bean 被定义。

这个配置类中,定义了一个名为 problemDetailsExceptionHandler 的方法,该方法返回一个新的 ProblemDetailsExceptionHandler 类型实例,是一个标注了 @ControllerAdvice 集中处理系统异常。这个实例是继承自 ResponseEntityExceptionHandler ,用于处理控制器中的异常,并返回相应的错误信息。

ProblemDetailsExceptionHandler类

当前版本支持的异常类型,即如果系统出现以下异常,会被 SpringBoot 支持以 RFC 7807规范方式返回错误数据:

	@ExceptionHandler({
			HttpRequestMethodNotSupportedException.class,
			HttpMediaTypeNotSupportedException.class,
			HttpMediaTypeNotAcceptableException.class,
			MissingPathVariableException.class,
			MissingServletRequestParameterException.class,
			MissingServletRequestPartException.class,
			ServletRequestBindingException.class,
			MethodArgumentNotValidException.class,
			HandlerMethodValidationException.class,
			NoHandlerFoundException.class,
			NoResourceFoundException.class,
			AsyncRequestTimeoutException.class,
			ErrorResponseException.class,
			MaxUploadSizeExceededException.class,
			ConversionNotSupportedException.class,
			TypeMismatchException.class,
			HttpMessageNotReadableException.class,
			HttpMessageNotWritableException.class,
			MethodValidationException.class,
			BindException.class
		})

在Spring Boot 3中使用Problem Details

Spring Boot 3 中引入了对 Problem Details 的支持,使得开发者可以轻松地在自己的应用中应用这一标准。通过使用 Spring Boot 提供的工具和注解,你可以快速地为你的 API 添加对 Problem Details 的支持,从而提升 API 的可用性和易用性。

未配置Problem Details

例如对一个 仅支持 POST 请求的接口采用 GET 方式调用,如果是 HTML 页面展示则会出现白页:

HTML页面

如果是获取 JSON 则返回如下信息:

JSON 格式

配置Problem Details

在 Spring Boot 3 应用中,首先确保你的项目引入了 Spring Boot 的 Web 模块。

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

配置文件中开启 Problem Details:

spring.mvc.problemdetails.enabled=true

开启Problem Details

自定义异常处理器

可以通过定义异常处理器来使用 Problem Details,并且支持自定义异常。

首先看下,如果系统发生了不支持的异常类型,就不能被处理成符合 Problem Details 标准的响应:

java.lang.ArithmeticException: / by zero

不支持的异常

接下来,通过定义异常处理器支持这个除零异常:

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.net.URI;


/**
 * @author CoderJia
 * @create 2024/03/15 10:00
 * @Description
 **/
@RestControllerAdvice(basePackages = "com.coderjia.springboot304web.controller")
public class GlobalExceptionHandler {

    @ExceptionHandler(ArithmeticException.class)
    public ProblemDetail handleCustomException(ArithmeticException ex) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
        problemDetail.setTitle("服务器发生异常");
        problemDetail.setType(URI.create("https://coderjia.cn/errors/xxx"));
        return problemDetail;
    }
}

发生此类异常显示如下:

定义异常处理器

自定义Problem Details内容

除了标准字段 typetitlestatusdetailinstance 外,还可以通过 setProperty() 自定义一些属性:

    @ExceptionHandler(ArithmeticException.class)
    public ProblemDetail handleCustomException(ArithmeticException ex) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
        problemDetail.setTitle("服务器发生异常");
        problemDetail.setType(URI.create("https://coderjia.cn/errors/xxx"));
        problemDetail.setProperty("errorCategory", "Generic");
        problemDetail.setProperty("timestamp", Instant.now());
        return problemDetail;
    }

image-20240315100658635

自定义异常继承 ErrorResponseException

除了以上方法,还可以直接继承 ErrorResponseException ,然后直接抛出该异常,会被处理成符合 Problem Details 标准的响应。

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.ErrorResponseException;

import java.net.URI;
import java.time.Instant;

/**
 * @author CoderJia
 * @create 2024/03/15 9:39
 * @Description
 **/
public class DivideByZeroException extends ErrorResponseException {


    public DivideByZeroException(String customMsg) {
        super(HttpStatus.NOT_FOUND, asProblemDetail(customMsg), null);
    }

    private static ProblemDetail asProblemDetail(String customMsg) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, customMsg);
        problemDetail.setTitle("除零异常");
        problemDetail.setType(URI.create("https://coderjia.cn/errors/xxx"));
        problemDetail.setProperty("timestamp", Instant.now());
        return problemDetail;
    }
}

直接抛出异常

获取正常

实践建议

使用 Problem Details 的主要优点是提高了 API 错误处理的一致性和可理解性。通过提供标准化的错误响应格式,客户端开发者可以更容易地理解和处理 API 返回的错误信息。在实践中,建议为你的 API 定义一套统一的错误类型,并为这些错误类型提供详细的文档。这样,当 API 返回错误时,客户端开发者可以通过 type 字段提供的 URI 访问到关于错误类型的详细说明,从而更好地理解和处理错误。