При использовании ResponseEntity <T> и @RestController для приложений Spring RESTful

163

Я работаю с 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) не имеет значения, какой подход предлагается и почему.
Мануэль Джордан
источник

Ответы:

213

ResponseEntityпредназначен для представления всего ответа HTTP. Вы можете контролировать все, что входит в него: код состояния, заголовки и тело.

@ResponseBodyявляется маркером тела ответа HTTP и @ResponseStatusобъявляет код состояния ответа HTTP.

@ResponseStatusне очень гибкий Он отмечает весь метод, поэтому вы должны быть уверены, что ваш метод-обработчик всегда будет вести себя одинаково. И вы все еще не можете установить заголовки. Вам нужен HttpServletResponseили HttpHeadersпараметр.

В основном, ResponseEntityпозволяет вам сделать больше.

Сотириос Делиманолис
источник
6
Хороший вопрос о третьем наблюдении. Спасибо ... и я тоже об этом думал ResponseEntity, он более гибкий. Просто у меня были сомнения @RestController. Спасибо
Мануэль Джордан
55

Чтобы завершить ответ от Sotorios Delmanolis.

Это правда, что ResponseEntityдает вам больше гибкости, но в большинстве случаев вам это не понадобится, и в конечном итоге вы будете использовать их ResponseEntityповсюду в контроллере, что затруднит чтение и понимание.

Если вы хотите обрабатывать особые случаи, такие как ошибки (Not Found, Conflict и т. Д.), Вы можете добавить a HandlerExceptionResolverв конфигурацию Spring. Таким образом, в вашем коде вы просто выбрасываете конкретное исключение ( NotFoundExceptionнапример) и решаете, что делать в вашем обработчике (устанавливая статус HTTP на 404), делая код контроллера более понятным.

Matt
источник
5
Ваша точка зрения действительна при работе с (@) ExceptionHandler. Дело в том, что если вы хотите, чтобы все обрабатывались одним методом (Try / Catch), HttpEntity хорошо подходит, если вы хотите повторно использовать обработку исключений (@), ExceptionHandler для многих (@), то RequestMapping хорошо подходит. Мне нравится HttpEntity, потому что я тоже могу работать с HttpHeaders.
Мануэль Джордан
46

Согласно официальной документации: Создание контроллеров REST с аннотацией @RestController

@RestController - это аннотация стереотипа, которая объединяет @ResponseBody и @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);
Danail
источник
Что, если мы добавили @ResponseStatus (HttpStatus.OK) в метод, но метод вернул return new ResponseEntity <> (user, responseHeaders, HttpStatus.NOT_FOUND); Я просто думаю, что @ResponseStatus изменит код ответа дальше.
Пратапи Хемант Патель
4
@Hemant кажется, что @ResponseStatus(HttpStatus.OK)игнорируется, когда вы вернетесь ResponseEntity<>(user, responseHeaders, HttpStatus.NOT_FOUND). Ответ HTTP404
Danail
Из JavaDocs ResponseStatus. Код состояния применяется к HTTP-ответу, когда вызывается метод-обработчик, и переопределяет информацию о состоянии, заданную другими средствами, такими как {@code ResponseEntity} или {@code "redirect:"}.
вжемевко
14

Надлежащий REST API должен иметь в ответ следующие компоненты

  1. Код состояния
  2. Тело ответа
  3. Местоположение ресурса, который был изменен (например, если ресурс был создан, клиенту было бы интересно узнать URL этого местоположения)

Основной целью ResponseEntity было предоставление опции 3, остальные варианты могли быть достигнуты без ResponseEntity.

Поэтому, если вы хотите указать местоположение ресурса, лучше использовать 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);
}

Источник - Весна в действии

Гаутам Тадигоппула
источник