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 .