Spring:如何在运行时选择响应类型?

Spring:如何在运行时选择响应类型?,spring,spring-mvc,spring-boot,Spring,Spring Mvc,Spring Boot,我想在方法中选择运行时的响应媒体类型 例如,以下代码: @RequestMapping(value = "/getRecord", produces = {"application/octet-stream", "application/json;charset=UTF-8" }) public byte[] getData( @RequestParam(value="id", required=true) Integer id) throws IOException {

我想在方法中选择运行时的响应媒体类型

例如,以下代码:

@RequestMapping(value = "/getRecord",
    produces = {"application/octet-stream", "application/json;charset=UTF-8" })
public byte[] getData(
    @RequestParam(value="id", required=true) Integer id)
    throws IOException
{
    if (id == 1)
        return createByteArray();
    throw new MyDataException();
}
在这段代码中,可能的响应类型实际上是2

  • 字节[](通过正常执行路径)
  • MyDataException(通过异常执行路径)
  • MyDataException稍后由异常处理程序处理,并转换为简单类。它可以转换为json响应

    首先,我认为如果我为
    @RequestMapping
    注释的
    products
    选项提供两种响应类型,消息转换器将根据实际返回对象转换这两种类型。但事实并非如此

    在spring类中,
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor
    writeWithMessageConverters()
    方法在选择响应类型时忽略实际返回的对象类型,如果存在
    products
    选项

    如何让Spring根据实际返回对象在运行时选择响应类型?

    自我回答

    • 删除
      生成
    • 将返回类型更改为
      ResponseEntity
    • 返回如下:

      HttpHeaders responseHeaders = new HttpHeaders();
      responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
      return new ResponseEntity<byte[]>(createByteArray(), responseHeaders, HttpStatus.OK);
      
      @RequestMapping(value = "/getRecord")
      public ResponseEntity<byte[]> getData(@RequestParam(value="id", required=true) Integer id)
          throws IOException
      {
          if (id == 1)
          {
              HttpHeaders responseHeaders = new HttpHeaders();
              responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
              return new ResponseEntity<byte[]>(createByteArray(), responseHeaders, HttpStatus.OK);
          }
          throw new MyDataException();
      }
      
      HttpHeaders responseHeaders=新的HttpHeaders();
      responseHeaders.setContentType(MediaType.APPLICATION\u OCTET\u STREAM);
      返回新的ResponseEntity(createByteArray(),responseHeaders,HttpStatus.OK);
      
    因此,关于该问题的代码转换如下:

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    return new ResponseEntity<byte[]>(createByteArray(), responseHeaders, HttpStatus.OK);
    
    @RequestMapping(value = "/getRecord")
    public ResponseEntity<byte[]> getData(@RequestParam(value="id", required=true) Integer id)
        throws IOException
    {
        if (id == 1)
        {
            HttpHeaders responseHeaders = new HttpHeaders();
            responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            return new ResponseEntity<byte[]>(createByteArray(), responseHeaders, HttpStatus.OK);
        }
        throw new MyDataException();
    }
    
    @RequestMapping(value=“/getRecord”)
    public ResponseEntity getData(@RequestParam(value=“id”,required=true)整数id)
    抛出IOException
    {
    如果(id==1)
    {
    HttpHeaders responseHeaders=新的HttpHeaders();
    responseHeaders.setContentType(MediaType.APPLICATION\u OCTET\u STREAM);
    返回新的ResponseEntity(createByteArray(),responseHeaders,HttpStatus.OK);
    }
    抛出新的MyDataException();
    }
    
    现在,响应类型如下所示:

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    return new ResponseEntity<byte[]>(createByteArray(), responseHeaders, HttpStatus.OK);
    
    @RequestMapping(value = "/getRecord")
    public ResponseEntity<byte[]> getData(@RequestParam(value="id", required=true) Integer id)
        throws IOException
    {
        if (id == 1)
        {
            HttpHeaders responseHeaders = new HttpHeaders();
            responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            return new ResponseEntity<byte[]>(createByteArray(), responseHeaders, HttpStatus.OK);
        }
        throw new MyDataException();
    }
    
    • 在正常执行路径上,appliaction/octet流
    • 在异常执行路径上,使用application/json
    我引用了一个答案 为了这个。另请参见Kumar Sambhav关于设置异常处理程序的回答


    如果几天后没有更好的答案发布,我将选择这个答案。

    我建议您在Spring MVC处理程序中使用注释来处理异常。这是分离错误处理关注点的非常优雅的方法(实际上有3种方法可以消除异常处理关注点),例如设置适当的HTTP响应代码(2xx以外的代码)和发送回错误消息/对象

    有一个很棒的博客

    示例(从Spring博客中借用):-

    在您的情况下,我建议采用@ControllerAdvice方法,如:-

    @ControllerAdvice
    class GlobalControllerExceptionHandler {
        @ResponseStatus(HttpStatus.CONFLICT)  // 409
        @ResponseBody
        @ExceptionHandler(MyDataException.class)
        public AnyReturnType handleConflict(Exception exception) {
             return exception.getDetails();
        }
    }
    
    处理程序的返回类型也可以是ModelAndView对象,它将错误对象的一部分传递给视图层


    有关更多详细信息,请参阅博客。

    另一种可能是所述两种解决方案之间的混合方法:

    @ControllerAdvice
    public class MyExceptionHandler extends ResponseEntityExceptionHandler {
    
    @ExceptionHandler({ MyDataException.class })
    protected ResponseEntity<Object> handleInvalidRequest(RuntimeException e, WebRequest request) {
        MyDataExceptionire = (MyDataException) e;
        List<FieldErrorResource> fieldErrorResources = new ArrayList<>();
    
        List<FieldError> fieldErrors = ire.getErrors().getFieldErrors();
        for (FieldError fieldError : fieldErrors) {
            FieldErrorResource fieldErrorResource = new FieldErrorResource();
            fieldErrorResource.setResource(fieldError.getObjectName());
            fieldErrorResource.setField(fieldError.getField());
            fieldErrorResource.setCode(fieldError.getCode());
            fieldErrorResource.setMessage(fieldError.getDefaultMessage());
            fieldErrorResources.add(fieldErrorResource);
        }
    
        ErrorResource error = new ErrorResource("MyDataException", ire.getMessage());
        error.setFieldErrors(fieldErrorResources);
    
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
    
        return handleExceptionInternal(e, error, headers, HttpStatus.UNPROCESSABLE_ENTITY, request);
    }}
    

    实际上,我对MyDataException使用了
    @ControllerAdvice
    。问题在于,当响应转换完成时,Spring不尊重异常的对象类型。它只是尝试将其转换为application/octet-stream(这是我为
    products
    指定的第一种类型),但失败了,而我希望将其转换为application/json。我假设您的是RESTful控制器(JSON响应,没有视图层)。如果是这种情况,那么使用@RestController而不是@Controller,这样就不必显式地提到“products”。将@ResponseBody注释放在handleConflict上应该可以工作。参考上面提到的博客以获得完整的图片。我的控制器是RESTful的。但在正常情况下,它返回application/octet流体。如果抛出异常,它应该通过
    @ControllerAdvice
    异常处理程序返回HTTP 400或500的application/json body(其中包含错误信息)。我已经使用@ResponseBody了。事实上,我的ExceptionHandler类的代码几乎与您的GlobalControllerExceptionHandler相同。(除了ResponseStatus是HttpStatus.BAD_请求)无论如何,我会读你提供的博客文章。谢谢。我想,这个博客实际上没有你提供的更多信息。我现在会坚持我的答案。有趣。这与我的回答相反。我对正常流使用ResponseEntity,而对异常流使用ResponseEntity。可以根据异常处理程序的数量和请求处理程序的数量来选择这两种方法。顺便说一句,在这个解决方案中,必须将
    products=“application/octet stream”
    选项添加到返回application/octet stream的请求处理程序中。@zeodtr是的,您是对的:),这个示例使用json作为返回类型,但您可以选择任何媒体类型。通常我喜欢在ControllerAdvice中包含这样的逻辑,而不是在控制器中捕获它。我选择这个答案是因为将特殊代码(与ResourceEntity相关的)放入异常处理程序比放入控制器更合适。但这个例子可能有点太复杂了。在这种情况下,可以引用我的自我回答来处理ResourceEntity。