Quando usar ResponseEntity <T> e @RestController para aplicativos Spring RESTful

163

Estou trabalhando com o Spring Framework 4.0.7, junto com o MVC e o Rest

Eu posso trabalhar em paz com:

  • @Controller
  • ResponseEntity<T>

Por exemplo:

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

Com o método (apenas para criar)

@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);
}

para retornar algo

@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);
}

Funciona bem

Eu posso fazer o mesmo com :

  • @RestController(Eu sei que é o mesmo que @Controller+ @ResponseBody)
  • @ResponseStatus

Por exemplo:

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

Com o método (apenas para criar)

@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());
}

para retornar algo

@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;
}

Minhas perguntas são:

  1. quando, por um motivo sólido ou cenário específico, uma opção deve ser usada obrigatoriamente sobre a outra
  2. Se (1) não importa, qual abordagem é sugerida e por quê.
Manuel Jordan
fonte

Respostas:

213

ResponseEntitydeve representar a resposta HTTP inteira. Você pode controlar tudo o que for necessário: código de status, cabeçalhos e corpo.

@ResponseBodyé um marcador para o corpo da resposta HTTP e @ResponseStatusdeclara o código de status da resposta HTTP.

@ResponseStatusnão é muito flexível. Ele marca todo o método, assim você deve ter certeza de que seu método manipulador sempre se comportará da mesma maneira. E você ainda não pode definir os cabeçalhos. Você precisaria do HttpServletResponseou de um HttpHeadersparâmetro.

Basicamente, ResponseEntitypermite fazer mais.

Sotirios Delimanolis
fonte
6
Bom ponto sobre a terceira observação. Obrigado ... e pensei o mesmo ResponseEntity, é mais flexível. Só estava com a dúvida @RestController. Obrigado
Manuel Jordan
55

Para completar a resposta de Sotorios Delimanolis.

É verdade que ResponseEntitylhe dá mais flexibilidade, mas na maioria dos casos você não precisará dela e acabará com elas ResponseEntityem todos os lugares do seu controlador, dificultando a leitura e a compreensão.

Se você quiser lidar com casos especiais, como erros (não encontrado, conflito, etc.), poderá adicionar um HandlerExceptionResolvera sua configuração do Spring. Portanto, em seu código, basta lançar uma exceção específica ( NotFoundExceptionpor exemplo) e decidir o que fazer em seu manipulador (configurando o status HTTP para 404), tornando o código do controlador mais claro.

Matt
fonte
5
Seu ponto de vista é válido ao trabalhar com (@) ExceptionHandler. O ponto é: se você deseja que tudo seja tratado em um método (Try / Catch) HttpEntity se encaixa bem, se você deseja reutilizar o tratamento de exceções (@) ExceptionHandler para muitos (@) RequestMapping se encaixa bem. Eu gosto do HttpEntity porque também posso trabalhar com o HttpHeaders.
Manuel Jordan
46

De acordo com a documentação oficial: Criando controladores REST com a anotação @RestController

@RestController é uma anotação de estereótipo que combina @ResponseBody e @Controller. Mais do que isso, ele dá mais significado ao seu Controller e também pode transportar semânticas adicionais em versões futuras da estrutura.

Parece que é melhor usar @RestControllerpara maior clareza, mas você também pode combiná- lo com ResponseEntityflexibilidade quando necessário (de acordo com o tutorial oficial e o código aqui e minha pergunta para confirmar isso ).

Por exemplo:

@RestController
public class MyController {

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

        return user;
    }

}

é o mesmo que:

@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);
    }

}

Dessa forma, você pode definir ResponseEntityapenas quando necessário.

Atualizar

Você pode usar isto:

    return ResponseEntity.ok().headers(responseHeaders).body(user);
Danail
fonte
E se tivermos adicionado @ResponseStatus (HttpStatus.OK) no método, mas o método retorna retornar new ResponseEntity <> (usuário, responseHeaders, HttpStatus.NOT_FOUND); Estou apenas pensando que se @ResponseStatus irá modificar ainda mais o código de resposta.
Pratapi Hemant Patel
4
@ Hemant parece que @ResponseStatus(HttpStatus.OK)é ignorado quando você retorna ResponseEntity<>(user, responseHeaders, HttpStatus.NOT_FOUND). A resposta HTTP é404
Danail
De JavaDocs do ResponseStatus. O código de status é aplicado à resposta HTTP quando o método manipulador é chamado e substitui as informações de status definidas por outros meios, como {@code ResponseEntity} ou {@code "redirect:"}.
21418 vzhemevko
14

Uma API REST adequada deve ter componentes abaixo em resposta

  1. Código de status
  2. Corpo de resposta
  3. Local para o recurso que foi alterado (por exemplo, se um recurso foi criado, o cliente estaria interessado em saber o URL desse local)

O principal objetivo do ResponseEntity era fornecer a opção 3, opções de descanso poderiam ser alcançadas sem o ResponseEntity.

Portanto, se você deseja fornecer a localização do recurso, usar o ResponseEntity seria melhor, caso contrário, isso pode ser evitado.

Considere um exemplo em que uma API é modificada para fornecer todas as opções mencionadas

// 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);
}

Fonte - Primavera em Ação

Gautam Tadigoppula
fonte