首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Java SpringBoot OpenApi @ApiResponse显示返回对象错误

Java SpringBoot OpenApi @ApiResponse显示返回对象错误
EN

Stack Overflow用户
提问于 2021-11-11 10:50:59
回答 2查看 4.2K关注 0票数 0

我在我的OpenApi项目中使用SpringBoot 3来生成Swagger页面。

POM.xml中的依赖项:

<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> <version>1.5.12</version> </dependency>

在Controller类中,我在方法上面定义了以下注释。

代码语言:javascript
运行
复制
@Operation(
        summary = "Get a list of letters for a specific user",
        description = "Get a list of letters for a specific user",
        tags = {"letters"}
)
@ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "success", content = {@Content(
                                                                    mediaType = "application/json",
                                                                    array = @ArraySchema(schema = @Schema(implementation = LetterDTO.class)))}),
        @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
        @ApiResponse(responseCode = "401", description = "UNAUTHORIZED"),
        @ApiResponse(responseCode = "403", description = "Forbidden"),
        @ApiResponse(responseCode = "404", description = "NOT_FOUND: Entity could not be found")}
)
@GetMapping(value = "letters/user/{userId}", produces = {"application/json"})
public List<LetterDTO> getLettersForUser(
    ...
)

Swagger的输出显示了代码200的正确响应,这是一个LetterDTO对象列表。

但是代码401的响应也显示了一个LetterDTO对象的列表。我没有为代码401定义任何响应对象。我期望Swagger生成与代码400相同的响应对象,这是一个包含错误代码和错误消息的默认返回对象。

为什么Swagger使用与为代码200定义的返回对象相同的返回对象?我原以为Swagger会生成默认的返回对象。这是斯威格的臭虫吗?

EN

回答 2

Stack Overflow用户

发布于 2022-04-17 08:21:01

我通常会像这样配置API响应:

代码语言:javascript
运行
复制
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "400", description = "Invalid request", content = @Content)

如果未指定content,则使用相应控制器方法的返回类型。content = @Content告诉Swagger,响应中没有内容。

对于@ApiGetOne,Swagger将显示(屏幕截图来自不同的DTO类):

为了简单性和可重用性,我通常用可重用的助手注释包装这些注释,这样我的端点就没有那么多的注释,并且我不需要控制器中的ResponseEntity,例如:

代码语言:javascript
运行
复制
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET,
    produces = MediaType.APPLICATION_JSON_VALUE)
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "400", description = "Invalid request", content = @Content)
@ApiResponse(responseCode = "500", description = "Internal error", content = @Content)
public @interface ApiGet {

  @AliasFor(annotation = RequestMapping.class)
  String[] value() default {};

  @AliasFor(annotation = RequestMapping.class)
  String[] path() default {};

}

您还可以使用更多的API响应来扩展这些注释,例如,为某些端点添加404,创建另一个具有@ApiGet的注释:

代码语言:javascript
运行
复制
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ApiGet
@ApiResponse(responseCode = "404", description = "Not found", content = @Content)
public @interface ApiGetOne {

  @AliasFor(annotation = ApiGet.class)
  String[] value() default {};

  @AliasFor(annotation = ApiGet.class)
  String[] path() default {};

}

最后,在任何端点上使用它们(使用Java 17):

代码语言:javascript
运行
复制
public record HelloWorldDto(String recipientName) {
  public String getMessage() {
    return "Hello, %s".formatted(recipientName);
  }
}
代码语言:javascript
运行
复制
public record ErrorDto(String message) {
}
代码语言:javascript
运行
复制
@RestController
@RequestMapping("api/test")
@Tag(name = "Demo", description = "Endpoints for testing")
public class DemoController {
  ...

  @ApiGet("/hello")
  public HelloWorldDto sayHello() {
    return new HelloWorldDto("stranger");
  }

  @ApiGetOne("/hello/{id}")
  public HelloWorldDto sayHelloWithParam(@PathVariable int id) {
    final var person = myPersonRepo.getById(id); // might throw a NotFoundException which is mapped to 404 status code
    return new HelloWorldDto(person.name());
  }
}

将异常映射到自定义错误响应:

代码语言:javascript
运行
复制
@ControllerAdvice
public class ErrorHandler {

  private static final Logger log = LoggerFactory.getLogger(ErrorHandler.class);

  @ExceptionHandler
  public ResponseEntity<ErrorDto> handle(Exception exception) {
    log.error("Internal server error occurred", exception);

    return response(HttpStatus.INTERNAL_SERVER_ERROR, "Unknown error occurred.");
  }

  @ExceptionHandler
  public ResponseEntity<ErrorDto> handle(NotFoundException exception) {
    return response(HttpStatus.NOT_FOUND, exception.getMessage());
  }

  private ResponseEntity<ErrorDto> response(HttpStatus status, String message) {
    return ResponseEntity
        .status(status)
        .body(new ErrorDto(message));
  }
}

我很喜欢这个装置,因为

  • I最终得到了几个可重用的注释,这些注释足以满足典型CRUD端点的
  • --我不需要在控制器方法
  • 中构建ResponseEntity -- @ControllerAdvice作为可重用错误处理

H 122的中心点,所有这些都使我的控制器/端点保持干净和简单的H 223H 124,然后继续测试简单的H 225/code>F 226

更新2022/04/20

只需修复一个bug,在那里我们有一个返回图像而不是JSON的端点。在这种情况下,为了防止HttpMessageNotWritableException: No converter for [class ErrorDto] with preset Content-Type 'image/jpeg',您需要像这样检查请求的Accept头(使用报头作为后盾):

代码语言:javascript
运行
复制
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDto> handle(final Exception exception, final WebRequest webRequest) {
  return createResponse(HttpStatus.INTERNAL_SERVER_ERROR, "Some error", webRequest);
}

protected ResponseEntity<ErrorDto> createResponse(final HttpStatus httpStatus,
                                                  final String message,
                                                  final WebRequest webRequest) {
  final var accepts = webRequest.getHeader(HttpHeaders.ACCEPT);
  if (!MediaType.APPLICATION_JSON_VALUE.equals(accepts)) {
    return ResponseEntity.status(httpStatus)
        .header("my-error", message)
        .build();
  }

  return ResponseEntity
      .status(status)
      .body(new ErrorDto(message));
}
票数 1
EN

Stack Overflow用户

发布于 2021-11-11 11:11:32

这是一个不断出现的问题,因为Java方法只能具有返回类型。您的方法有一个响应类型的List<LetterDTO>,所以无论您返回的HTTP,响应都是该结构的。

为了解决这一问题,大多数人采取以下办法之一:

  • --具有相应响应对象的Object的返回类型,实际上已重定向。在这种情况下,用户必须依赖于API文档来处理响应,correctly.
  • Another方法是在响应中有自己的包装器。这也需要文档支持。
  • 第三种方法是让您的@RequestMapping()选择器使用优化的选择,而不仅仅是端点路径。例如,也使用请求方法。但是,这样做的缺点是定义了新的REST端点。

但总的来说,没有一条容易的出路。

编辑:添加示例

让我们假设这些简单的对象作为数据结构。

代码语言:javascript
运行
复制
@NoArgsConstructor @AllArgsConstructor
@Getter @Setter
private static class LetterDTO{
    String from;
}

@NoArgsConstructor @AllArgsConstructor
@Getter @Setter
private static class ErrorResp{
    String message;
}

然后,对于第一个使用Object作为返回值的情况,可以执行如下操作:

代码语言:javascript
运行
复制
@GetMapping(value = "letters/user/{userId}", produces = {"application/json"})
public Object getLettersForUser( @PathVariable( "userId") String input, HttpServletResponse resp ) {
    if( input.equalsIgnoreCase( "a" ) ) {
        resp.setStatus( 200 );
        return Arrays.asList( new LetterDTO( "A Friend" ) );
    }
    else{
        resp.setStatus( 415 ); //Invalid user
        return new ErrorResp( "Invalid user" );
    }
}

或者更好地使用SpringMVC方式,即返回ResponseEntity

代码语言:javascript
运行
复制
@GetMapping(value = "letters2/user/{userId}", produces = {"application/json"})
public ResponseEntity<Object> getLettersForUser2( @PathVariable( "userId") String input, HttpServletResponse resp ) {
    if( input.equalsIgnoreCase( "a" ) ) {
        return ResponseEntity.ok().body( Arrays.asList( new LetterDTO( "A Friend" ) ) );
    }
    else{
        return ResponseEntity.status( 415 ).body( Arrays.asList( new ErrorResp( "Invalid user" ) ) );
    }
}

现在必须清楚地将类用作返回值。与Object不同,您可以作为包装器返回一些结构,例如:

代码语言:javascript
运行
复制
@NoArgsConstructor @AllArgsConstructor
@Getter @Setter
private static class APIResponse{
    String message;
    Object data;
}

从这些例子中可以看出,API的文档变得非常重要,因为很难从签名中收集响应的内容-- 200、415等。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69927033

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档