Cuando use ResponseEntity <T> y @RestController para aplicaciones Spring RESTful

163

Estoy trabajando con Spring Framework 4.0.7, junto con MVC y Rest

Puedo trabajar en paz con:

  • @Controller
  • ResponseEntity<T>

Por ejemplo:

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

Con el método (solo para crear)

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

devolver 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 bien

Puedo hacer lo mismo con :

  • @RestController(Sé que es lo mismo que @Controller+ @ResponseBody)
  • @ResponseStatus

Por ejemplo:

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

Con el método (solo para crear)

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

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

Mis preguntas son:

  1. cuando por una razón sólida o escenario específico, una opción debe usarse obligatoriamente sobre la otra
  2. Si (1) no importa, qué enfoque se sugiere y por qué.
Manuel Jordan
fuente

Respuestas:

213

ResponseEntityestá destinado a representar la respuesta HTTP completa. Puede controlar todo lo que entra: código de estado, encabezados y cuerpo.

@ResponseBodyes un marcador para el cuerpo de respuesta HTTP y @ResponseStatusdeclara el código de estado de la respuesta HTTP.

@ResponseStatusNo es muy flexible. Marca todo el método, por lo que debe asegurarse de que su método de controlador siempre se comportará de la misma manera. Y todavía no puedes configurar los encabezados. Necesitarías el HttpServletResponseo un HttpHeadersparámetro.

Básicamente, ResponseEntityte permite hacer más.

Sotirios Delimanolis
fuente
66
Buen punto sobre la tercera observación. Gracias ... y pensé lo mismo ResponseEntity, es más flexible. Solo estaba con la duda sobre @RestController. Gracias
Manuel Jordan
55

Para completar la respuesta de Sotorios Delimanolis.

Es cierto que ResponseEntityle brinda más flexibilidad, pero en la mayoría de los casos no la necesitará y terminará con estos ResponseEntityen todas partes en su controlador, lo que dificulta su lectura y comprensión.

Si desea manejar casos especiales como errores (No encontrado, Conflicto, etc.), puede agregar un HandlerExceptionResolvera su configuración de Spring. Entonces, en su código, simplemente lanza una excepción específica ( NotFoundExceptionpor ejemplo) y decide qué hacer en su controlador (configurando el estado HTTP en 404), haciendo que el código del controlador sea más claro.

Mate
fuente
55
Su punto de vista es válido trabajando con (@) ExceptionHandler. El punto es: si desea que todo se maneje en un método (Try / Catch), HttpEntity se ajusta bien, si desea reutilizar el manejo de excepciones (@) ExceptionHandler para muchos (@) RequestMapping se ajusta bien. Me gusta HttpEntity porque también puedo trabajar con HttpHeaders.
Manuel Jordan
46

Según la documentación oficial: Creación de controladores REST con la anotación @RestController

@RestController es una anotación de estereotipo que combina @ResponseBody y @Controller. Más que eso, le da más significado a su Controlador y también puede llevar una semántica adicional en futuras versiones del marco.

Parece que es mejor usarlo @RestControllerpara mayor claridad, pero también puede combinarlo con ResponseEntityflexibilidad cuando sea necesario ( según el tutorial oficial y el código aquí y mi pregunta para confirmarlo ).

Por ejemplo:

@RestController
public class MyController {

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

        return user;
    }

}

es lo mismo 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);
    }

}

De esta manera, puede definir ResponseEntitysolo cuando sea necesario.

Actualizar

Puedes usar esto:

    return ResponseEntity.ok().headers(responseHeaders).body(user);
Danail
fuente
¿Qué pasa si hemos agregado @ResponseStatus (HttpStatus.OK) en el método, pero el método devuelve return new ResponseEntity <> (user, responseHeaders, HttpStatus.NOT_FOUND); Solo estoy pensando si @ResponseStatus modificará aún más el código de respuesta.
Pratapi Hemant Patel
44
@Hemant parece que @ResponseStatus(HttpStatus.OK)se ignora cuando regresas ResponseEntity<>(user, responseHeaders, HttpStatus.NOT_FOUND). La respuesta HTTP es404
Danail, el
De JavaDocs de ResponseStatus. El código de estado se aplica a la respuesta HTTP cuando se invoca el método del controlador y anula la información de estado establecida por otros medios, como {@code ResponseEntity} o {@code "redirect:"}.
vzhemevko
14

Una API REST adecuada debe tener los siguientes componentes en respuesta

  1. Código de estado
  2. Cuerpo de respuesta
  3. Ubicación del recurso que se modificó (por ejemplo, si se creó un recurso, el cliente estaría interesado en conocer la URL de esa ubicación)

El objetivo principal de ResponseEntity era proporcionar la opción 3, las opciones de descanso se podían lograr sin ResponseEntity.

Entonces, si desea proporcionar la ubicación del recurso, usar ResponseEntity sería mejor, de lo contrario, se puede evitar.

Considere un ejemplo en el que se modifica una API para proporcionar todas las opciones 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);
}

Fuente - Primavera en acción

Gautam Tadigoppula
fuente