Пружинный ботинок + ОТДЫХ + HATEOAS + HAL - PullRequest
0 голосов
/ 19 октября 2018

Я следовал учебникам по Spring.io Pivotal, чтобы получить REST API с базой данных MySQL, и все идет хорошо.Однако я обнаружил поведение, которое я не смог настроить или обойти.

Когда я использую встроенную функциональность для извлечения моих ресурсов из PagingAndSortingRepository, результирующий REST автоматически выгружается иинкапсулированы полезными ссылками HAL (_links, self, search, связанные ресурсы и т. д.).Я хочу использовать это.

Когда я реализовал свой контроллер для настройки поведения PostMapping и введения проверок работоспособности, проверки и т. Д., GetMapping прекратил работать.Поэтому я повторно реализовал GetMapping, который использовал мой уровень Service.

Это, к сожалению, нарушило HATEOAS, который был предоставлен ранее.

Я хотел бы иметь возможность настроить PostMapping, носохранить GetMapping точно так же, как по умолчанию.Если возможно, я бы не хотел писать это сам, поскольку знаю, что фреймворк может его предоставить.

Есть ли способ сделать это?

Контроллер:

@RestController
public class PartyMemberController {

    @Autowired
    PartyMemberService partyMemberService;

    @RequestMapping(method = RequestMethod.GET, value = "/partyMembers")
    public ResponseEntity<Iterable<PartyMember>> getAllPartyMembers() {
        Iterable<PartyMember> partyMemberList = partyMemberService.getAll();
        return new ResponseEntity<>(partyMemberList, HttpStatus.OK);
    }

    @RequestMapping(method = RequestMethod.POST, value = "/partyMembers")
    public ResponseEntity<PartyMember> addEmployee(@Valid @RequestBody PartyMember partyMember) {
        if (partyMemberService.exists(partyMember)) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        partyMember = partyMemberService.save(partyMember);
        return new ResponseEntity<PartyMember>(partyMember, HttpStatus.CREATED);
    }
}

Результирующий JSON

    [
      {
        "id": 2,
        "ward": {
          "id": 1,
          "name": "Mercier",
          "wardNumber": 42,
          "numberOfMembers": 3
        },
        "firstName": "Cindy",
        "lastName": "Tremblay",
        "partyMemberId": "12-1234-09876",
        "primaryPhone": "514-555-2323",
        "postalAddress": "1155 Robert-Bourassa, Montreal, Quebec, Canada, H3B3A7",
        "emailAddress": null,
        "secondaryPhone": null,
        "bestTimeToContact": null,
        "bestWayToContact": null,
        "membershipExpiry": null,
        "dateOfBirth": null,
        "donationEntries": [],
        "note": null
      },
      {
        "id": 3,
        "ward": {
          "id": 1,
          "name": "Mercier",
          "wardNumber": 42,
          "numberOfMembers": 3
        },
        "firstName": "Robert",
        "lastName": "Paulson",
        "partyMemberId": "12-1234-54321",
        "primaryPhone": "514-555-1212",
        "postalAddress": "440 Rue Saint-Pierre, App 5, Montreal, Quebec, Canada, H2Y2M5",
        "emailAddress": "rpaulson@papermillsoapcompany.com",
        "secondaryPhone": null,
        "bestTimeToContact": null,
        "bestWayToContact": null,
        "membershipExpiry": null,
        "dateOfBirth": null,
        "donationEntries": [],
        "note": null
      },
      {
        "id": 4,
        "ward": {
          "id": 1,
          "name": "Mercier",
          "wardNumber": 42,
          "numberOfMembers": 3
        },
        "firstName": "Richard",
        "lastName": "Schnobb",
        "partyMemberId": "12-4321-09876",
        "primaryPhone": "514-555-2323",
        "postalAddress": "440 Rue Saint-Pierre, App 5, Montreal, Quebec, Canada, H2Y2M5",
        "emailAddress": null,
        "secondaryPhone": null,
        "bestTimeToContact": null,
        "bestWayToContact": null,
        "membershipExpiry": null,
        "dateOfBirth": null,
        "donationEntries": [],
        "note": null
      }
    ]

JSON по умолчанию (обратите внимание на _embedded, _links и т. Д.).Это то, что я хочу получить в результате.

{
  "_embedded" : {
    "partyMembers" : [ {
      "firstName" : "Cindy",
      "lastName" : "Tremblay",
      "partyMemberId" : "12-1234-09876",
      "primaryPhone" : "514-555-2323",
      "postalAddress" : "1155 Robert-Bourassa, Montreal, Quebec, Canada, H3B3A7",
      "emailAddress" : null,
      "secondaryPhone" : null,
      "bestTimeToContact" : null,
      "bestWayToContact" : null,
      "membershipExpiry" : null,
      "dateOfBirth" : null,
      "donationEntries" : [ ],
      "note" : null,
      "_links" : {
        "self" : {
          "href" : "http://127.0.0.1:8080/partyMembers/2"
        },
        "partyMember" : {
          "href" : "http://127.0.0.1:8080/partyMembers/2"
        },
        "ward" : {
          "href" : "http://127.0.0.1:8080/partyMembers/2/ward"
        }
      }
    }, {
      "firstName" : "Robert",
      "lastName" : "Paulson",
      "partyMemberId" : "12-1234-54321",
      "primaryPhone" : "514-555-1212",
      "postalAddress" : "440 Rue Saint-Pierre, App 5, Montreal, Quebec, Canada, H2Y2M5",
      "emailAddress" : "rpaulson@papermillsoapcompany.com",
      "secondaryPhone" : null,
      "bestTimeToContact" : null,
      "bestWayToContact" : null,
      "membershipExpiry" : null,
      "dateOfBirth" : null,
      "donationEntries" : [ ],
      "note" : null,
      "_links" : {
        "self" : {
          "href" : "http://127.0.0.1:8080/partyMembers/3"
        },
        "partyMember" : {
          "href" : "http://127.0.0.1:8080/partyMembers/3"
        },
        "ward" : {
          "href" : "http://127.0.0.1:8080/partyMembers/3/ward"
        }
      }
    }, {
      "firstName" : "Richard",
      "lastName" : "Schnobb",
      "partyMemberId" : "12-4321-09876",
      "primaryPhone" : "514-555-2323",
      "postalAddress" : "440 Rue Saint-Pierre, App 5, Montreal, Quebec, Canada, H2Y2M5",
      "emailAddress" : null,
      "secondaryPhone" : null,
      "bestTimeToContact" : null,
      "bestWayToContact" : null,
      "membershipExpiry" : null,
      "dateOfBirth" : null,
      "donationEntries" : [ ],
      "note" : null,
      "_links" : {
        "self" : {
          "href" : "http://127.0.0.1:8080/partyMembers/4"
        },
        "partyMember" : {
          "href" : "http://127.0.0.1:8080/partyMembers/4"
        },
        "ward" : {
          "href" : "http://127.0.0.1:8080/partyMembers/4/ward"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://127.0.0.1:8080/partyMembers{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://127.0.0.1:8080/profile/partyMembers"
    },
    "search" : {
      "href" : "http://127.0.0.1:8080/partyMembers/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 3,
    "totalPages" : 1,
    "number" : 0
  }
}

Ответы [ 3 ]

0 голосов
/ 19 октября 2018

Чтобы Spring делал большую часть работы за вас, вы должны использовать аннотацию @RepositoryRestController.Дополнительные объяснения в документах :

Иногда вам может понадобиться написать собственный обработчик для конкретного ресурса.Чтобы воспользоваться преимуществами настроек Spring Data REST, конвертеров сообщений, обработки исключений и многого другого, используйте аннотацию @RepositoryRestController вместо стандартного Spring MVC @Controller или @RestController.Контроллеры, аннотированные @RepositoryRestController, обслуживаются из базового пути API, определенного в RepositoryRestConfiguration.setBasePath, который используется всеми другими конечными точками RESTful (например, /api).


, чтобы упроститьгенерируя ресурсы HATEOAS (с _links, который вы упомянули), вы можете воспользоваться ResourceAssemblerSupport.

Поскольку отображение от сущности к типу ресурса будет иметьдля использования в нескольких местах имеет смысл создать специальный класс, ответственный за это.Преобразование, конечно, будет содержать очень нестандартные шаги, но также и несколько стандартных.(...) Spring Hateoas теперь предоставляет базовый класс ResourceAssemblerSupport, который помогает сократить объем кода, который необходимо написать

0 голосов
/ 27 ноября 2018

Типы возврата на вашем контроллере не относятся к типу Spring HATEOAS ResourceSupport, поэтому сериализаторы HAL никогда не будут вызываться.

Вы должны вернуть либо Resource<PartyMember> (для одного элемента) или Resources<Resource<PartyMember>> (для списков).Структурируйте свой контроллер так, чтобы он возвращал их.

Внесите эти изменения, и тогда концепция многократного использования ResourceAssembler<PartyMember, Resource<PartyMember>> будет иметь смысл как способ преобразования PartyMember объектов, найденных в слое обслуживания, в Resource<PartyMember> объектыиспользуется для визуализации гипермедиа.

Чтобы увидеть эту эволюцию службы на основе REST, ознакомьтесь с этим руководством (которое я переписал всего несколько месяцев назад, чтобы правильно показать использование Spring HATEAOS) => https://spring.io/guides/tutorials/rest/

0 голосов
/ 19 октября 2018

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

Документы и примеры здесь: https://docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals.obtaining-links.entity-links

быстрый пример:

@Getter 
public class PersonResource extends ResourceSupport {
    private final Person person;
    public PersonResource(final Person person) {
        this.person = person;
        final long id = person.getId();
        add(linkTo(PersonController.class).withRel("people"));
        add(linkTo(methodOn(GymMembershipController.class).all(id)).withRel("memberships"));
        add(linkTo(methodOn(PersonController.class).get(id)).withSelfRel());
    }
}

Из этого самого превосходного слова: https://dzone.com/articles/applying-hateoas-to-a-rest-api-with-spring-boot

...