HATEOAS — это принцип архитектуры REST, в котором гипермедиа используется для изменения состояния приложения. Чтобы изменить состояние, возвращаемое представление ресурса содержит ссылки, тем самым «ограничивая» клиента на дальнейших действиях.
Проект Spring-HATEOAS призван помочь тем, кто пишет код Spring MVC, создавать такие ссылки и собирать ресурсы, возвращаемые клиентам.
В приведенном ниже примере будет описан простой сценарий, показывающий, как создаются и возвращаются ссылки для ресурса Bet . Каждая операция на ресурсе описана ниже:
- createBet — эта операция POST создаст ставку.
- updateBet — эта операция PUT обновит ставку.
- getBet — эта операция GET будет получать ставку.
- cancelBet — эта операция УДАЛИТЬ отменит ставку.
@Controller @RequestMapping("/bets") public class BetController { private BetService betService; private BetResourceAssembler betResourceAssembler; public BetController(BetService betService, BetResourceAssembler betResourceAssembler) { this.betService = betService; this.betResourceAssembler = betResourceAssembler; } @RequestMapping(method = RequestMethod.POST) ResponseEntity<BetResource> createBet(@RequestBody Bet body) { Bet bet = betService.createBet(body.getMarketId(), body.getSelectionId(), body.getPrice(), body.getStake(), body.getType()); BetResource resource = betResourceAssembler.toResource(bet); return new ResponseEntity<BetResource>(resource, HttpStatus.CREATED); } @RequestMapping(method = RequestMethod.PUT, value = "/{betId}") ResponseEntity<BetResource> updateBet(@PathVariable Long betId, @RequestBody Bet body) throws BetNotFoundException, BetNotUnmatchedException { Bet bet = betService.updateBet(betId, body); BetResource resource = betResourceAssembler.toResource(bet); return new ResponseEntity<BetResource>(resource, HttpStatus.OK); } @RequestMapping(method = RequestMethod.GET, value = "/{betId}") ResponseEntity<BetResource> getBet(@PathVariable Long betId) throws BetNotFoundException { Bet bet = betService.getBet(betId); BetResource resource = betResourceAssembler.toResource(bet); if (bet.getStatus() == BetStatus.UNMATCHED) { resource.add(linkTo(BetController.class).slash(bet.getId()).withRel("cancel")); } return new ResponseEntity<BetResource>(resource, HttpStatus.OK); } @RequestMapping(method = RequestMethod.GET) ResponseEntity<List<BetResource>> getBets() { List<Bet> betList = betService.getAllBets(); List<BetResource> resourceList = betResourceAssembler.toResources(betList); return new ResponseEntity<List<BetResource>>(resourceList, HttpStatus.OK); } @RequestMapping(method = RequestMethod.DELETE, value = "/{betId}") ResponseEntity<BetResource> cancelBet(@PathVariable Long betId) { Bet bet = betService.cancelBet(betId); BetResource resource = betResourceAssembler.toResource(bet); return new ResponseEntity<BetResource>(resource, HttpStatus.OK); } @ExceptionHandler ResponseEntity handleExceptions(Exception ex) { ResponseEntity responseEntity = null; if (ex instanceof BetNotFoundException) { responseEntity = new ResponseEntity(HttpStatus.NOT_FOUND); } else if (ex instanceof BetNotUnmatchedException) { responseEntity = new ResponseEntity(HttpStatus.CONFLICT); } else { responseEntity = new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } return responseEntity; } }
Все операции создадут
BetResource для возврата клиенту. Это делается путем вызова
toResource на
классе BetResourceAssembler :
public class BetResourceAssembler extends ResourceAssemblerSupport<Bet, BetResource> { public BetResourceAssembler() { super(BetController.class, BetResource.class); } public BetResource toResource(Bet bet) { BetResource resource = instantiateResource(bet); resource.bet = bet; resource.add(linkTo(BetController.class).slash(bet.getId()).withSelfRel()); return resource; } }
Этот класс расширяет
ResourceAssemblerSupport, который требует реализации
метода toResource, поскольку он реализует
интерфейс ResourceAssembler . Вот где делается сопоставление между
Bet и
BetResource . В этом случае
BetResource — это просто оболочка для
Bet, поэтому это просто случай установки
атрибута ставки . Метод
instantiateResource возвращает
BetResource без каких-либо ссылок, поэтому ссылки могут быть добавлены на этом этапе, если это необходимо. В этом примере добавлена ссылка на себя. Альтернативный подход будет использовать
createResourceWithId, который будет возвращать
BetResource с собственной ссылкой.
public class BetResource extends ResourceSupport { public Bet bet; }
Также в этом примере ссылки добавляются к
BetResource в
классе BetController, чтобы обеспечить применение ограничения HATEOAS. Если служба REST получает запрос GET, выполняется проверка статуса
ставки . Если
ставка является
НЕПРЕВЗОЙДЕННОЙ , то ссылка , чтобы отменить
ставки может быть добавлена к
BetResource . Это делается аналогично собственной ссылке, но с именем атрибута отношения отмены.
Альтернативный подход к этому заключается в создании ссылки на метод, а не на создание URI.
resource.add(linkTo(methodOn(BetController.class).cancelBet(betId)) .withRel("cancel"));
BetController класса и в результате возвращаемого типа
cancelBet метода должен быть способны проксированием. Поэтому в этом примере возвращаемый тип
метода cancelBet будет
HttpEntity <Bet>, а не
ResponseEntity <Bet>. Если последнее, то вероятное исключение из сервера будет:
[org.springframework.http.ResponseEntitycom.city81.hateoas.rest.BetResource> com.city81.hateoas.controller.BetController.getBet (java.lang.Long) выбрасывает com.city81.hateoas.BetNotFoundExceptionfra.srafra.sprf..sg: org .framework.AopConfigException: Не удалось сгенерировать подкласс CGLIB класса [class org.springframework.http.ResponseEntity]: общие причины этой проблемы включают использование конечного класса или невидимого класса; Вложенное исключение — java.lang.IllegalArgumentException: у Суперкласса нет нулевых конструкторов, но никакие аргументы не были предоставлены
Вернемся к запросу GET, и возвращенный JSON для запроса
ресурса Bet, который имеет статус
UNMATC HED, показан ниже:
{
«links»: [
{«rel»: «self», «href»:
http: // localhost : 8080 / hateoas-1-SNAPSHOT / bets / 0 },
{«rel»: «cancel», «href»:
http: // localhost: 8080 / hateoas-1-SNAPSHOT / bets / 0 }],
«bet» : { «ID»: 0, «marketId»: 1, «selectionId»: 22, «цена»: 4,0, «застолбить»: 2,0 «Тип»: «НАЗАД», «статус»: «НЕПРЕВЗОЙДЕННАЯ»
}
}
Поэтому клиент может использовать собственную ссылку для получения и обновления
ставки , а также ссылку отмены для ее эффективного удаления.
В этом посте описываются лишь некоторые функциональные возможности проекта Spring-HATEOAS, который постоянно развивается. Чтобы получить актуальное и более подробное объяснение, посетите
страницы GitHub .