Lorsque vous utilisez ResponseEntity <T> et @RestController pour les applications Spring RESTful

163

Je travaille avec Spring Framework 4.0.7, avec MVC et Rest

Je peux travailler en paix avec:

  • @Controller
  • ResponseEntity<T>

Par exemple:

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

Avec la méthode (juste pour créer)

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

retourner quelque chose

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

Fonctionne bien

Je peux faire la même chose avec :

  • @RestController(Je sais que c'est la même chose que @Controller+ @ResponseBody)
  • @ResponseStatus

Par exemple:

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

Avec la méthode (juste pour créer)

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

retourner quelque chose

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

Mes questions sont:

  1. lorsque, pour une raison solide ou un scénario spécifique, une option doit être obligatoirement utilisée par rapport à l'autre
  2. Si (1) n'a pas d'importance, quelle approche est suggérée et pourquoi.
Manuel Jordan
la source

Réponses:

213

ResponseEntityest censé représenter la réponse HTTP entière. Vous pouvez contrôler tout ce qui y entre: code d'état, en-têtes et corps.

@ResponseBodyest un marqueur pour le corps de la réponse HTTP et @ResponseStatusdéclare le code d'état de la réponse HTTP.

@ResponseStatusn'est pas très flexible. Il marque la méthode entière, vous devez donc être sûr que votre méthode de gestionnaire se comportera toujours de la même manière. Et vous ne pouvez toujours pas définir les en-têtes. Vous auriez besoin du paramètre HttpServletResponseou HttpHeaders.

Fondamentalement, ResponseEntityvous permet d'en faire plus.

Sotirios Delimanolis
la source
6
Bon point sur la troisième observation. Merci… et j'ai pensé la même chose ResponseEntity, c'est plus flexible. J'étais juste avec le doute @RestController. Merci
Manuel Jordan
55

Pour compléter la réponse de Sotorios Delimanolis.

C'est vrai que cela ResponseEntityvous donne plus de flexibilité, mais dans la plupart des cas, vous n'en aurez pas besoin et vous vous retrouverez avec ceux-ci ResponseEntitypartout dans votre contrôleur, ce qui rendra difficile la lecture et la compréhension.

Si vous souhaitez gérer des cas particuliers tels que des erreurs (non trouvé, conflit, etc.), vous pouvez ajouter un HandlerExceptionResolverà votre configuration Spring. Donc, dans votre code, vous lancez simplement une exception spécifique ( NotFoundExceptionpar exemple) et décidez quoi faire dans votre gestionnaire (en définissant le statut HTTP sur 404), ce qui rend le code du contrôleur plus clair.

Mat
la source
5
Votre point de vue est valide en travaillant avec (@) ExceptionHandler. Le point est: si vous voulez que tout soit géré dans une seule méthode (Try / Catch) HttpEntity convient bien, si vous voulez réutiliser la gestion des exceptions (@) ExceptionHandler pour beaucoup (@) RequestMapping convient bien. J'aime HttpEntity parce que je peux aussi travailler avec HttpHeaders.
Manuel Jordan
46

Selon la documentation officielle: Création de contrôleurs REST avec l'annotation @RestController

@RestController est une annotation stéréotypée qui combine @ResponseBody et @Controller. De plus, cela donne plus de sens à votre contrôleur et peut également contenir une sémantique supplémentaire dans les futures versions du framework.

Il semble qu'il soit préférable d'utiliser @RestControllerpour plus de clarté, mais vous pouvez également le combiner avec ResponseEntitypour plus de flexibilité si nécessaire ( selon le tutoriel officiel et le code ici et ma question pour le confirmer ).

Par exemple:

@RestController
public class MyController {

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

        return user;
    }

}

est le même 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 cette façon, vous ne pouvez définir ResponseEntityque lorsque cela est nécessaire.

Mettre à jour

Vous pouvez utiliser ceci:

    return ResponseEntity.ok().headers(responseHeaders).body(user);
Danail
la source
Que faire si nous avons ajouté @ResponseStatus (HttpStatus.OK) sur la méthode, mais que la méthode retourne return new ResponseEntity <> (user, responseHeaders, HttpStatus.NOT_FOUND); Je pense simplement que @ResponseStatus modifiera davantage le code de réponse.
Pratapi Hemant Patel
4
@Hemant semble @ResponseStatus(HttpStatus.OK)ignoré lorsque vous revenez ResponseEntity<>(user, responseHeaders, HttpStatus.NOT_FOUND). La réponse HTTP est404
Danail
À partir de JavaDocs du ResponseStatus. Le code d'état est appliqué à la réponse HTTP lorsque la méthode du gestionnaire est appelée et remplace les informations d'état définies par d'autres moyens, comme {@code ResponseEntity} ou {@code "redirect:"}.
vzhemevko
14

Une API REST appropriée doit avoir les composants ci-dessous en réponse

  1. Code d'état
  2. Corps de réponse
  3. Emplacement de la ressource qui a été modifiée (par exemple, si une ressource a été créée, le client serait intéressé de connaître l'URL de cet emplacement)

Le principal objectif de ResponseEntity était de fournir l'option 3, les options de repos pouvaient être obtenues sans ResponseEntity.

Donc, si vous souhaitez indiquer l'emplacement de la ressource, il serait préférable d'utiliser ResponseEntity, sinon cela peut être évité.

Prenons un exemple où une API est modifiée pour fournir toutes les options mentionnées

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

Source - Le printemps en action

Gautam Tadigoppula
la source