Spring Boot 3.0 新特性 - 问题详情和错误响应

位置:首页>文章>详情   分类: 教程分享 > Java教程   阅读(1265)   2024-04-17 10:32:55

从Spring 6和Spring Boot 3开始,Spring框架支持“HTTP API的问题详细信息”规范RFC 7807。本Spring Boot 教程将详细指导您完成这一新增强。

1.问题详细说明[RFC 7807]

该RFC定义了简单的JSON和XML文档格式,可用于向API消费者传达问题细节。这在HTTP状态代码不足以描述HTTP API问题的情况下非常有用。

以下是从一个银行账户转移到另一个银行帐户时发生的问题的示例,我们的帐户中没有足够的余额。

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en

{
	"status": 403,
	"type": "https://bankname.com/common-problems/low-balance",
	"title": "You not have enough balance",
	"detail": "Your current balance is 30 and you are transterring 50",
	"instance": "/account-transfer-service"
}

 

关键信息如下:

  • status: 服务器生成的HTTP状态代码。
  • type: 标识问题类型以及如何解决问题的URL。默认值 value is – about:blank.
  • title: 问题的简短说明。
  • detail: 问题的详细说明.
  • instance: 发生此问题的服务的URL。默认值是当前请求URL。

2.Spring框架中的支持

以下是Spring框架中支持问题细节规范的主要抽象:

2.1.问题详情

它是表示问题细节模型的主要对象。如前一节所述,它包含标准字段,非标准字段可以作为属性添加。

ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "message");
pd.setType(URI.create("http://my-app-host.com/errors/not-found"));
pd.setTitle("Record Not Found");

要添加非标准字段,请使用setProperty()方法。

pd.setProperty("property-key", "property-value");

2.2.错误响应

此接口公开HTTP错误响应详细信息,包括HTTP状态、响应标头和ProblemDetail类型的主体。与仅发送ProblemDetail对象相比,它可以用于向客户端提供更多信息。

所有Spring MVC异常都实现ErrorResponse接口。因此,所有MVC异常都已经符合规范。

2.3.错误响应异常

它是一个非常基本的ErrorResponse实现,我们可以将其作为一个方便的基类来创建更具体的异常类。

ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "Null Pointer Exception");
pd.setType(URI.create("http://my-app-host.com/errors/npe"));
pd.setTitle("Null Pointer Exception");

throw new ErrorResponseException(HttpStatus.INTERNAL_SERVER_ERROR, pd, npe);

2.4.ResponseEntityExceptionHandler

它是@ControllerAdvice的一个方便的基类,它根据RFC规范和任何ErrorResponseException处理所有SpringMVC异常,并用主体呈现错误响应。

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
	
	@ExceptionHandler(CustomException.class)
  public ProblemDetail handleCustomException(CustomException ex, WebRequest request) {

    ProblemDetail pd = //build object..
    return pd;
  }
}

我们可以从任何@ExceptionHandler或任何@RequestMapping方法返回ProblemDetail或ErrorResponse以呈现RFC 7807响应。

3.使用ResponseEntity发送ProblemDetail

在失败的情况下,创建ProblemDetail类的新实例,填充相关信息并将其设置到ResponseEntity对象中。

假设当id大于100时,以下API失败。

@Value("${hostname}")
private String hostname;

@GetMapping(path = "/employees/v2/{id}")
public ResponseEntity getEmployeeById_V2(@PathVariable("id") Long id) {
  if (id < 100) {
    return ResponseEntity.ok(new Employee(id, "lokesh", "gupta", "admin@leftso.com"));
  } else {
    ProblemDetail pd = ProblemDetail
        .forStatusAndDetail(HttpStatus.NOT_FOUND, "Employee id '" + id + "' does no exist");
    pd.setType(URI.create("http://my-app-host.com/errors/not-found"));
    pd.setTitle("Record Not Found");
    pd.setProperty("hostname", hostname);
    return ResponseEntity.status(404).body(pd);
  }
}

接下来,让我们用id=101进行测试。它将返回RFC规范中的响应。

{
    "detail": "Employee id '101' does no exist",
    "hostname": "localhost",
    "instance": "/employees/v2/101",
    "status": 404,
    "title": "Record Not Found",
    "type": "http://my-app-host.com/errors/not-found"
}

4.从REST控制器引发ErrorResponseException

发送问题详细信息的另一种方法是从@RequestMapping处理程序方法中抛出ErrorResponseException实例。

这在我们已经有无法发送到客户端的异常(例如NullPointerException)的情况下尤其有用。在这种情况下,我们在ErrorResponseException中填充基本信息并抛出它。Spring MVC处理程序内部处理此异常并将其解析为RFC指定的响应格式。

@GetMapping(path = "/v3/{id}")
public ResponseEntity getEmployeeById_V3(@PathVariable("id") Long id) {
  try {
  	//somthing抛出了此异常
    throw new NullPointerException("Something was expected but it was null");
  }
  catch (NullPointerException npe) {
    ProblemDetail pd = ProblemDetail
        .forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR,
            "Null Pointer Exception");
    pd.setType(URI.create("http://my-app-host.com/errors/npe"));
    pd.setTitle("Null Pointer Exception");
    pd.setProperty("hostname", hostname);
    throw new ErrorResponseException(HttpStatus.NOT_FOUND, pd, npe);
  }
}

5.将ProblemDetail添加到自定义异常

大多数应用程序都创建更接近其业务域/模型的异常类。一些这样的异常可能是RecordNotFoundException、TransactionLimitException等。它们更易读,并在代码中简洁地表示错误场景。

大多数时候,这些异常都是RuntimeException的子类。

public class RecordNotFoundException extends RuntimeException {
  private final String message;
  public RecordNotFoundException(String message) {
    this.message = message;
  }
}

我们从代码中的几个地方抛出这些异常。

@GetMapping(path = "/v1/{id}")
public ResponseEntity getEmployeeById_V1(@PathVariable("id") Long id) {
  if (id < 100) {
    return ResponseEntity.ok(...);
  } else {
    throw new RecordNotFoundException("Employee id '" + id + "' does no exist");
  }
}

在此类异常中添加问题详细信息的最佳方法是在@ControllerAdvice类中。我们必须在@ExceptionHandler(RecordNotFoundException.class)方法中处理异常,并添加所需的信息。

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
  @Value("${hostname}")
  private String hostname;
  @ExceptionHandler(RecordNotFoundException.class)
  public ProblemDetail handleRecordNotFoundException(RecordNotFoundException ex, WebRequest request) {
    ProblemDetail body = ProblemDetail
        .forStatusAndDetail(HttpStatusCode.valueOf(404),ex.getLocalizedMessage());
    body.setType(URI.create("http://my-app-host.com/errors/not-found"));
    body.setTitle("Record Not Found");
    body.setProperty("hostname", hostname);
    return body;
  }
}

6. 在单元测试中验证ProblemDetail响应

我们还可以在单元测试中使用RestTemplate测试验证上述部分的问题细节响应。

@Test
public void testAddEmployee_V2_FailsWhen_IncorrectId() {
  try {
    this.restTemplate.getForObject("/employees/v2/101", Employee.class);
  } catch (RestClientResponseException ex) {
    ProblemDetail pd = ex.getResponseBodyAs(ProblemDetail.class);
    assertEquals("Employee id '101' does no exist", pd.getDetail());
    assertEquals(404, pd.getStatus());
    assertEquals("Record Not Found", pd.getTitle());
    assertEquals(URI.create("http://my-app-host.com/errors/not-found"), pd.getType());
    assertEquals("localhost", pd.getProperties().get("hostname"));
  }
}

注意,如果我们使用Spring Webflux,我们可以使用WebClient API来验证返回的问题细节。

@Test
void testAddEmployeeUsingWebFlux_V2_FailsWhen_IncorrectId() {
  this.webClient.get().uri("/employees/v2/101")
      .retrieve()
      .bodyToMono(String.class)
      .doOnNext(System.out::println)
      .onErrorResume(WebClientResponseException.class, ex -> {
        ProblemDetail pd = ex.getResponseBodyAs(ProblemDetail.class);
        //assertions...
        return Mono.empty();
      })
      .block();
}

执行v3接口测试

 @Test
  public void testAddEmployee_V3_FailsWhen_IncorrectId() {
    try {
      this.restTemplate.getForObject("/employees/v3/101", Employee.class);
    } catch (RestClientResponseException ex) {
      ProblemDetail pd = ex.getResponseBodyAs(ProblemDetail.class);
      System.out.println(format(ex.getStatusCode(), ex.getResponseHeaders(), pd));
    }
  }



以上测试执行结果参考
执行结果1


执行结果2

执行结果3
 

7. 总结

在这个Spring 6(Spring Boot 3.0)教程中,我们学习了Spring框架中支持问题细节规范的新特性。在这个特性之后,我们可以从@ExceptionHandler或@RequestMapping方法返回ProblemDetail或ErrorResponse的实例,Spring框架会在API响应中添加必要的模式。

本教程中讨论的代码适用于Spring应用程序和Spring boot应用程序。

 

地址:https://www.leftso.com/article/1116.html

相关阅读

从Spring 6和Spring Boot 3开始,Spring框架支持“HTTP API的问题详细信息”规范RFC 7807。本Spring Boot 教程将详细指导您完成这一新增强。1.问题...
Spring Boot 2.0 有哪些新特性_Spring Boot 2.0新功能,在本文中,我们将探讨为Spring Boot 2.0计划的一些更改和功能。我们还会描述这些变化如何帮助我们提高...
从Spring 6和Spring Boot 3开始,与OpenFeign和Retrofit等其他声明式客户端类似,Spring框架支持以Java接口的形式创建RSocket服务,并为RSocke...
从Spring 6和Spring Boot 3开始,Spring framework支持将远程HTTP服务代理为带有HTTP交换注解方法的Java接口。类似的库,如OpenFeign和Retro...
Spring Boot 2.1 新特性,已升级Spring 版本为5.1,支持servlet 4.0,支持Tomcat 9.0等等
Spring Boot 2.0 Redis整合,通过spring boot 2.0整合Redis作为spring缓存框架的实现。
spring boot 2.0 security 5.0 整合,实现自定义表单登录。spring boot 2.0框架使用。
Spring Boot 2.0 绑定properties属性资源文件 Spring Boot 2.0 读取properties配置文件值 Spring Boot 2.0获取properties配...
Spring Boot 1.x升级到Spring Boot 2.0迁移指南
Spring Boot 2.0,Spring框架的Spring Boot 中的Spring Boot Actuator变化讲解。并且了解如何在Spring Boot 2.0中使用Actuator...