在Spring RESTful应用中,何时使用ResponseEntity<T>和@RestController?

219

我正在使用Spring Framework 4.0.7,结合MVC和Rest进行工作。

我可以与以下内容和谐地工作:

  • @Controller
  • ResponseEntity<T>

例如:

@Controller
@RequestMapping("/person")
@Profile("responseentity")
public class PersonRestResponseEntityController {

使用这种方法(只是创建)

@RequestMapping(value="/", method=RequestMethod.POST)
public ResponseEntity<Void> createPerson(@RequestBody Person person, UriComponentsBuilder ucb){
    logger.info("PersonRestResponseEntityController  - createPerson");
    if(person==null)
        logger.error("person is null!!!");
    else
        logger.info("{}", person.toString());

    personMapRepository.savePerson(person);
    HttpHeaders headers = new HttpHeaders();
    headers.add("1", "uno");
    //http://localhost:8080/spring-utility/person/1
    headers.setLocation(ucb.path("/person/{id}").buildAndExpand(person.getId()).toUri());

    return new ResponseEntity<>(headers, HttpStatus.CREATED);
}

归还某物

@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<Person> getPerson(@PathVariable Integer id){
    logger.info("PersonRestResponseEntityController  - getPerson - id: {}", id);
    Person person = personMapRepository.findPerson(id);
    return new ResponseEntity<>(person, HttpStatus.FOUND);
}

运行正常

我也可以使用:

  • @RestController(我知道它与@Controller+@ResponseBody是一样的)
  • @ResponseStatus

例如:

@RestController
@RequestMapping("/person")
@Profile("restcontroller")
public class PersonRestController {

使用这种方法(只是为了创建)

@RequestMapping(value="/", method=RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public void createPerson(@RequestBody Person person, HttpServletRequest request, HttpServletResponse response){
    logger.info("PersonRestController  - createPerson");
    if(person==null)
        logger.error("person is null!!!");
    else
        logger.info("{}", person.toString());

    personMapRepository.savePerson(person);
    response.setHeader("1", "uno");

    //http://localhost:8080/spring-utility/person/1
    response.setHeader("Location", request.getRequestURL().append(person.getId()).toString());
}
返回某物。
@RequestMapping(value="/{id}", method=RequestMethod.GET)
@ResponseStatus(HttpStatus.FOUND)
public Person getPerson(@PathVariable Integer id){
    logger.info("PersonRestController  - getPerson - id: {}", id);
    Person person = personMapRepository.findPerson(id);
    return person;
}

我的问题是:

  1. 当“有充分的理由”或“特定情境”要求必须强制使用一种选项时
  2. 如果(1)不重要,建议采用什么方法以及为什么。
4个回答

284

ResponseEntity代表整个HTTP响应。您可以控制其中的任何内容:状态码、头部和正文。

@ResponseBody是HTTP响应正文的标记,@ResponseStatus声明了HTTP响应的状态码。

@ResponseStatus不太灵活。它标记整个方法,因此必须确保处理程序方法始终以相同的方式运行。并且仍然无法设置头部。您需要使用HttpServletResponse

基本上,ResponseEntity让您做更多的事情。


8
第三个观察点提出得很好。谢谢你...我也认为ResponseEntity更灵活,只是我对@RestController还有疑问。谢谢。 - Manuel Jordan

80

补充Sotorios Delimanolis的回答。

虽然ResponseEntity提供更多的灵活性,但在大多数情况下不需要它。如果在控制器中到处使用ResponseEntity,会使代码难以阅读和理解。

如果你想处理特殊情况(例如未找到、冲突等错误),可以将HandlerExceptionResolver添加到Spring配置中。所以在你的代码中,只需抛出指定的异常(例如NotFoundException),并在Handler中决定如何处理(将HTTP状态设置为404),从而使控制器代码更清晰。


5
你的观点在使用ExceptionHandler方面是正确的。关键是:如果你想在一个方法(Try/Catch)中处理所有内容,HttpEntity非常适合;如果你想重复使用ExceptionHandler来处理多个RequestMapping,则@RequestMapping非常适合。我喜欢HttpEntity,因为我也可以使用HttpHeaders。 - Manuel Jordan

61

根据官方文档:使用 @RestController 注释创建 REST 控制器

@RestController 是一种元注释,将 @ResponseBody 和 @Controller 结合起来。此外,它还为您的 Controller 赋予更多含义,并在框架的未来版本中可能携带其他语义。

看起来使用@RestController最清晰明了,但当需要灵活性时,也可以与ResponseEntity结合使用(根据官方教程,以及这里的代码我的问题供确认)。

例如:

@RestController
public class MyController {

    @GetMapping(path = "/test")
    @ResponseStatus(HttpStatus.OK)
    public User test() {
        User user = new User();
        user.setName("Name 1");

        return user;
    }

}

即相当于:

@RestController
public class MyController {

    @GetMapping(path = "/test")
    public ResponseEntity<User> test() {
        User user = new User();
        user.setName("Name 1");

        HttpHeaders responseHeaders = new HttpHeaders();
        // ...
        return new ResponseEntity<>(user, responseHeaders, HttpStatus.OK);
    }

}

这样做可以在需要时定义ResponseEntity

更新

您可以使用这个:

    return ResponseEntity.ok().headers(responseHeaders).body(user);

如果我们在方法上添加了@ResponseStatus(HttpStatus.OK),但是方法返回 return new ResponseEntity<>(user, responseHeaders, HttpStatus.NOT_FOUND); 我在想,@ResponseStatus是否会进一步修改响应代码。 - Pratapi Hemant Patel
4
ه½“و‚¨è؟”ه›‍ResponseEntity<>(user, responseHeaders, HttpStatus.NOT_FOUND)و—¶ï¼Œن¼¼ن¹ژ@ResponseStatus(HttpStatus.OK)被ه؟½ç•¥ن؛†م€‚HTTPه“چه؛”وک¯404م€‚ - Danail
从 ResponseStatus 的 JavaDocs 中。当处理程序方法被调用时,状态码将应用于 HTTP 响应,并覆盖其他方式设置的状态信息,如 ResponseEntity 或 "redirect:"。 - vzhemevko

28

一个适当的REST API应该在响应中包含以下组件:

  1. 状态码
  2. 响应主体
  3. 被修改资源的位置(例如,如果创建了一个资源,则客户端希望知道该位置的URL)

ResponseEntity的主要目的是提供选项3,但是不使用ResponseEntity也可以实现REST选项。

因此,如果您想提供资源的位置,则使用ResponseEntity会更好,否则可以避免使用它。

考虑一个例子,修改API以提供所有上述选项。

// Step 1 - Without any options provided
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public @ResponseBody Spittle spittleById(@PathVariable long id) {
  return spittleRepository.findOne(id);
}

// Step 2- We need to handle exception scenarios, as step 1 only caters happy path.
@ExceptionHandler(SpittleNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Error spittleNotFound(SpittleNotFoundException e) {
  long spittleId = e.getSpittleId();
  return new Error(4, "Spittle [" + spittleId + "] not found");
}

// Step 3 - Now we will alter the service method, **if you want to provide location**
@RequestMapping(
    method=RequestMethod.POST
    consumes="application/json")
public ResponseEntity<Spittle> saveSpittle(
    @RequestBody Spittle spittle,
    UriComponentsBuilder ucb) {

  Spittle spittle = spittleRepository.save(spittle);
  HttpHeaders headers = new HttpHeaders();
  URI locationUri =
  ucb.path("/spittles/")
      .path(String.valueOf(spittle.getId()))
      .build()
      .toUri();
  headers.setLocation(locationUri);
  ResponseEntity<Spittle> responseEntity =
      new ResponseEntity<Spittle>(
          spittle, headers, HttpStatus.CREATED)
  return responseEntity;
}

// Step4 - If you are not interested to provide the url location, you can omit ResponseEntity and go with
@RequestMapping(
    method=RequestMethod.POST
    consumes="application/json")
@ResponseStatus(HttpStatus.CREATED)
public Spittle saveSpittle(@RequestBody Spittle spittle) {
  return spittleRepository.save(spittle);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接