云计算、AI、云原生、大数据等一站式技术学习平台

网站首页 > 教程文章 正文

惊呆了!Controller接口返回值支持17种逆天类型

jxf315 2025-05-27 15:21:39 教程文章 5 ℃

环境:SpringBoot3.4.2



1. 简介

在Spring Boot开发中,使用@RestController@Controller注解,开发者可以轻松定义Controller类,并借助@RequestMapping等注解将请求路径映射到具体的处理方法上。Controller接收请求参数,调用Service层处理业务逻辑,然后返回响应结果,如JSON数据或视图页面

但你知道Controller接口都能返回哪些具体的数据类型吗?本篇文章将为你详细的展开介绍多达17种的返回值类型。

2. 实战案例

2.1 @ResponseBody

你可以在方法上使用 @ResponseBody 注解,Spring MVC底层会通过 HttpMessageConverter 将返回序列化为响应体。如下示例:

@GetMapping("/{id}")
@ResponseBody
public Account query(@PathVariable Long id) {
  return new Account(id, "Spring全家桶实战案例源码") ;
}

类级别也支持 @ResponseBody,在这种情况下,所有控制器方法都会继承它。这就是 @RestController 的效果,它只不过是一个标有 @Controller@ResponseBody 的元注解。上面示例输出结果:

2.2 HttpEntity

指定完整响应(包括HTTP头和正文)的返回值将通过HttpMessageConverter实现进行转换,并写入到响应中。

@GetMapping("/httpentity")
public HttpEntity<Account> httpentity() {
  Account data = new Account(1L, "Pack") ;
  HttpHeaders headers = new HttpHeaders() ;
  headers.add("Content-Type", "application/xml") ;
  HttpEntity<Account> entity = new HttpEntity<Account>(data, headers) ;
  return entity ;
}

在该示例中,我们设置了响应的数据,以及响应的头信息。该示例输出结果:

如果你的接口无法输出xml内容,那么你还需要引入如下依赖:

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

2.3 ResponseEntity

该类型是上面2.2介绍的HttpEntity的子类。通常我们使用该类相对来说是比较多的。但是,HttpEntity不仅可以用来当作返回值,还可以用来接收请求参数;而ResponseEntity只能用来表示响应结果。

@GetMapping("/responseentity")
public ResponseEntity<Account> responseentity() {
  Account data = new Account(1L, "Pack/Spring全家桶实战案例源码") ;
  return ResponseEntity.ok()
    .header("Content-Type", "application/xml")
    .body(data) ;
}

你不仅仅可以设置header,body内容,还可以设置状态码,输出结果:

2.4 HttpHeaders

返回一个带有头信息但没有正文的响应。

@GetMapping("/httpheaders")
public HttpHeaders httpheaders() {
  HttpHeaders headers = new HttpHeaders() ;
  headers.add("x-token", UUID.randomUUID().toString().replace("-", "")) ;
  return headers ;
}

该接口中我们通过HttpHeaders只输出头数据x-token。输出结果:

2.5 ErrorResponse

若要渲染包含正文详细信息的RFC 9457错误响应,可以使用该接口。要详细了解RFC 9457请查看下面链接:

https://www.rfc-editor.org/rfc/rfc9457.html

@GetMapping("/errorresponse")
public ErrorResponse errorresponse() {
  URI uri = MvcUriComponentsBuilder
    .fromMethodName(ReturnValueController.class, "errorresponse")
    .build()
    .toUri() ;
  ErrorResponse errorResponse = ErrorResponse.builder(
      new RuntimeException("接口发生异常"), 
      HttpStatusCode.valueOf(500), "错误的参数信息")
    .title("API错误")
    .instance(uri)
    .build() ;
  return errorResponse ;
}

运行该接口输出结果如下:

我们可以通过全局异常进行处理器来统一规范输出错误数据。

2.6 ProblemDetail

与2.5介绍的ErrorResponse一样都是用来输出RFC 9457错误响应。

@GetMapping("/problemdetail")
public ProblemDetail problemdetail() {
  ProblemDetail detail = ProblemDetail.forStatus(500) ;
  detail.setDetail("请求接口的参数不正确") ;
  detail.setInstance(URI.create("http://localhost:8888/api/query")) ;
  detail.setTitle("API错误") ;
  return detail ;
}

输出结果

2.7 String

这里返回的是String是视图名称,也就是你的html或jsp页面名称。

@Controller
@RequestMapping("/view")
public class ViewController {
  @GetMapping("home")
  public String index() {
    return "index";
  }
}

下面我们还需要在classpath:/templates/ 下新建index.html页面

输出结果

2.8 View

直接返回View实例对象,会自动调用View#render方法输出内容到页面。

@GetMapping("/view")
public View view() {
  return new View() {
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
      response.setContentType("text/html;charset=utf-8") ;
      response.getWriter().println("<h1>《Spring全家桶实战案例源码》,作者:Pack</h1>") ;
    }
  };
}

输出结果

2.9 ModelAndView

通过该对象,我们可以定义返回视图名称、模型数据以及一个可选的响应状态码。

@GetMapping("modelandview")
public ModelAndView modelandview() {
  ModelAndView view = new ModelAndView() ;
  view.setViewName("user") ;
  view.setStatus(HttpStatusCode.valueOf(200)) ;
  view.addObject("data", "《Spring全家桶实战案例源码》,作者:Pack") ;
  return view ;
}

输出结果

2.10 @ModelAttribute

通过该注解我们可以像模型中添加数据,如下示例:

@ModelAttribute
public User queryUser() {
  // 查询User信息
  return new User("Pack", 22) ;
}

页面定义

<html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
  <meta charset="UTF-8">
  <title>ModelAndView</title>
 </head>
 <body>
   <h1 th:text="${data}"></h1>
   姓名:<b th:text="${user.name}"></b>
   年龄:<b th:text="${user.age}"></b>
 </body>
</html>

接下来,我们还是使用2.9中定义的接口;输出结果:

2.11 void

返回类型为 void(或返回值为 null)的方法,如果它还具有 ServletResponse 参数、OutputStream 参数,或者带有 @ResponseStatus 注解,则被视为已完全处理了响应。

@GetMapping("/void")
public void rvoid(HttpServletResponse response) throws Throwable {
  response.setContentType("text/html;charset=utf-8") ;
  response.getWriter().println("<h1>《Spring全家桶实战案例源码》,作者:Pack</h1>") ;
}

输出结果

也可以在方法上使用@ResponseStatus

@GetMapping("/void")
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public void rvoid() {}

输出结果

2.12 DeferredResult

从任意线程异步地生成上述任意一种返回值。

private DeferredResult<String> dr ;
@GetMapping("/deferredresult")
public DeferredResult<String> deferredresult() {
  dr = new DeferredResult<>();
  return dr ;
}
@GetMapping("/sendResult")
public void sendResult() {
  new Thread(() -> {
    try {
      TimeUnit.SECONDS.sleep(3) ;
    } catch (InterruptedException e) {}
    dr.setResult("复杂的运算执行完成...") ;
  }).start() ;
}

说明:

  • /deferredresult接口创建了DeferredResult对象返回,当我们请求该接口时该接口在没有得到数据前会一直等待。如下:

一直等待结果中。

  • /sendResult接口用来对/deferredresult接口创建的DeferredResult设置最终的结果数据,如下:

2.13 Callable

在 Spring MVC 管理的线程中异步地生成上述任意一种返回值。

@GetMapping("/callable")
public Callable<String> callable() {
  return () -> {
    TimeUnit.SECONDS.sleep(3) ;
    return "Callable 结果返回..." ;
  } ;
}

输出结果

等待3s后输出内容。

2.14 CompletableFuture & CompletionStage

这里还有一个返回值类型:ListenableFuture。他们都是DeferredResult的替代方案。

@GetMapping("/completablefuture")
public CompletableFuture<String> completablefuture() {
  return CompletableFuture.supplyAsync(() -> {
    try {
      TimeUnit.SECONDS.sleep(3) ;
    } catch (InterruptedException e) {}
    return "CompletableFuture 返回结果..." ;
  }) ;
}

等待3s输出结果

2.15 ResponseBodyEmitter & SseEmitter

以异步方式将对象流发送出去,并利用 HttpMessageConverter 实现将其写入响应中。同时,这种对象流也可以作为 ResponseEntity 的主体(body)来支持。

private ResponseBodyEmitter emitter ;
@GetMapping("/responsebodyemitter")
public ResponseBodyEmitter responsebodyemitter() {
  emitter = new ResponseBodyEmitter();
  return emitter;
}
@GetMapping("/stream/send")
public void streamSend() {
  new Thread(() -> {
    int i = 0 ;
    do {
      try {
        emitter.send(DateTimeFormatter.ofPattern("mm:ss").format(LocalDateTime.now()) + " | ") ;
        TimeUnit.SECONDS.sleep(1) ;
      }
    } while (i++ < 5) ;
    emitter.complete() ;
  }).start() ;
}

在异步线程中,我们多次调用send方法输出数据,当最终调用complete方法后会结束当前的业务处理。

注意:这里在调用complete后才会输出所有的内容。

SseEmitter示例

private SseEmitter sse ;
@GetMapping(path="/sseemitter", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter sse() {
  sse = new SseEmitter();
  return sse ;
}
// 异步线程发送数据接口我们还是使用上面的/stream/send

输出结果

注意:这里会逐条的输出,调用send方法就将结果输出。

2.16 StreamingResponseBody

以异步方式向响应的 OutputStream 写入数据。

@GetMapping("/stream/logs")
public ResponseEntity<StreamingResponseBody> streamLogs() {
  StreamingResponseBody responseBody = outputStream -> {
    for (int i = 0; i < 10; i++) {
      String log = i + " - " + DateTimeFormatter.ofPattern("mm:ss").format(LocalDateTime.now()) + "\n";
      outputStream.write(log.getBytes(StandardCharsets.UTF_8)) ;
      outputStream.flush() ;
      // 模拟延迟
      try {
        TimeUnit.SECONDS.sleep(1) ;
      } catch (InterruptedException e) {}
    }
  };
  return ResponseEntity.ok()
      .header("Content-Type", "text/plain;charset=utf-8")
      .body(responseBody) ;
}

注意:必须调用flush,否则不会实时输出。

输出结果

2.17 Flux & Mono

单一值类型(例如,Mono)可类比为返回 DeferredResult。多值类型(例如,Flux)可以根据请求的媒体类型(如 "text/event-stream"、"application/json+stream")被视为一个流,否则会被收集到一个 List 中并以单一值的形式进行渲染。

@GetMapping("/mono")
public Mono<String> mono() {
  return Mono.just("《Spring全家桶实战案例源码》") ;
}

返回单一值。

@GetMapping(value = "/flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> flux() {
  return Flux.fromStream(
      Stream.generate(() -> {
        return DateTimeFormatter.ofPattern("mm:ss").format(LocalDateTime.now());
      })
      .limit(5)
    ).delayElements(Duration.ofSeconds(1));
}

返回多值,同时设置了响应类型为text/event-stream,会每隔1s输出一个元素,并且最多五个。

最近发表
标签列表