Gdy używasz ResponseEntity <T> i @RestController dla aplikacji Spring RESTful

163

Pracuję na Spring Framework 4.0.7, razem z MVC i Rest

Mogę pracować w zgodzie z:

  • @Controller
  • ResponseEntity<T>

Na przykład:

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

Metodą (tylko do tworzenia)

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

zwrócić coś

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

Działa w porządku

Mogę zrobić to samo z :

  • @RestController(Wiem, że to to samo niż @Controller+ @ResponseBody)
  • @ResponseStatus

Na przykład:

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

Metodą (tylko do tworzenia)

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

zwrócić coś

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

Moje pytania to:

  1. kiedy z solidnego powodu lub z konkretnego scenariusza jedna opcja musi być obowiązkowo stosowana nad drugą
  2. Jeśli (1) nie ma znaczenia, jakie podejście jest sugerowane i dlaczego.
Manuel Jordan
źródło

Odpowiedzi:

213

ResponseEntityma reprezentować całą odpowiedź HTTP. Możesz kontrolować wszystko, co się w nim znajduje: kod stanu, nagłówki i treść.

@ResponseBodyjest znacznikiem treści odpowiedzi HTTP i @ResponseStatusdeklaruje kod statusu odpowiedzi HTTP.

@ResponseStatusnie jest zbyt elastyczny. Oznacza całą metodę, więc musisz mieć pewność, że twoja metoda obsługi zawsze będzie zachowywać się w ten sam sposób. I nadal nie możesz ustawić nagłówków. Potrzebowałbyś parametru HttpServletResponselub HttpHeaders.

Zasadniczo ResponseEntitypozwala zrobić więcej.

Sotirios Delimanolis
źródło
6
Słuszna uwaga dotycząca trzeciej obserwacji. Dziękuję… i tak samo myślałem ResponseEntity, jest bardziej elastyczny. Po prostu miałem wątpliwości @RestController. Dziękuję
Manuel Jordan
55

Aby uzupełnić odpowiedź od Sotorios Delimanolis.

To prawda, że ResponseEntityzapewnia to większą elastyczność, ale w większości przypadków nie będziesz jej potrzebować i skończysz z nimi ResponseEntitywszędzie w kontrolerze, co utrudni czytanie i zrozumienie.

Jeśli chcesz zająć się przypadkami specjalnymi, takimi jak błędy (nie znaleziono, konflikt itp.), Możesz dodać HandlerExceptionResolverdo swojej konfiguracji Springa. Tak więc w swoim kodzie po prostu rzucasz określony wyjątek ( NotFoundExceptionna przykład) i decydujesz, co zrobić w swoim programie obsługi (ustawiając stan HTTP na 404), dzięki czemu kod kontrolera jest bardziej przejrzysty.

Matt
źródło
5
Twój punkt widzenia jest ważny podczas pracy z (@) ExceptionHandler. Chodzi o to, że jeśli chcesz, aby wszystko było obsługiwane w jednej metodzie (Try / Catch), HttpEntity pasuje dobrze, jeśli chcesz ponownie użyć obsługi wyjątków (@) ExceptionHandler dla wielu (@) RequestMapping pasuje dobrze. Lubię HttpEntity, ponieważ mogę też pracować z HttpHeaders.
Manuel Jordan
46

Zgodnie z oficjalną dokumentacją: Tworzenie kontrolerów REST z adnotacją @RestController

@RestController to stereotypowa adnotacja, która łączy @ResponseBody i @Controller. Co więcej, nadaje większe znaczenie Twojemu kontrolerowi, a także może mieć dodatkową semantykę w przyszłych wydaniach frameworka.

Wydaje się, że najlepiej jest używać go @RestControllerdla jasności, ale można go również połączyć z ResponseEntityelastycznością w razie potrzeby ( zgodnie z oficjalnym samouczkiem i kodem tutaj i moim pytaniem, aby to potwierdzić ).

Na przykład:

@RestController
public class MyController {

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

        return user;
    }

}

jest taki sam jak:

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

}

W ten sposób możesz zdefiniować ResponseEntitytylko wtedy, gdy jest to potrzebne.

Aktualizacja

Możesz użyć tego:

    return ResponseEntity.ok().headers(responseHeaders).body(user);
Danail
źródło
A co jeśli dodaliśmy @ResponseStatus (HttpStatus.OK) do metody, ale metoda zwraca zwraca new ResponseEntity <> (user, responseHeaders, HttpStatus.NOT_FOUND); Myślę tylko, że czy @ResponseStatus będzie dalej modyfikować kod odpowiedzi.
Pratapi Hemant Patel
4
@Hemant wydaje się, że @ResponseStatus(HttpStatus.OK)jest ignorowany po powrocie ResponseEntity<>(user, responseHeaders, HttpStatus.NOT_FOUND). Odpowiedź HTTP to404
Danail
Z JavaDocs w ResponseStatus. Kod stanu jest stosowany do odpowiedzi HTTP, gdy wywoływana jest metoda obsługi, i zastępuje informacje o stanie ustawione w inny sposób, na przykład {@code ResponseEntity} lub {@code "redirect:"}.
vzhemevko
14

Odpowiedni interfejs API REST powinien zawierać poniższe komponenty w odpowiedzi

  1. Kod statusu
  2. Treść odpowiedzi
  3. Lokalizacja zasobu, który został zmieniony (na przykład, jeśli zasób został utworzony, klient byłby zainteresowany poznaniem adresu URL tej lokalizacji)

Głównym celem ResponseEntity było zapewnienie opcji 3, pozostałe opcje można było osiągnąć bez ResponseEntity.

Jeśli więc chcesz podać lokalizację zasobu, lepiej byłoby użyć ResponseEntity, w przeciwnym razie można by tego uniknąć.

Rozważmy przykład, w którym interfejs API został zmodyfikowany w celu udostępnienia wszystkich wymienionych opcji

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

Źródło - Wiosna w akcji

Gautam Tadigoppula
źródło