Spring HATEOAS 내장 리소스 지원
REST API에 HAL 포맷을 사용하여 임베디드 리소스를 포함하고 싶습니다.API에 Spring HATEOAS를 사용하고 있는데 Spring HATEOAS가 내장된 리소스를 지원하는 것 같습니다. 그러나 이를 사용하는 방법에 대한 설명서나 예시는 없습니다.
Spring HATEOAS를 사용하여 내장된 리소스를 포함하는 방법을 예로 들어 줄 수 있는 사람이 있습니까?
HATEOAS에 대한 Spring의 설명서를 꼭 읽어보세요. 기본 사항을 이해하는 데 도움이 됩니다.
이 답변에서 핵심 개발자는 다음의 개념을 지적합니다.Resource,Resources그리고.PagedResources, 문서에서 다루지 않는 필수적인 것.
작동 방식을 이해하는 데 시간이 좀 걸렸는데, 몇 가지 예를 들어 확실하게 설명해 보겠습니다.
단일 리소스 반환
자원
import org.springframework.hateoas.ResourceSupport;
public class ProductResource extends ResourceSupport{
final String name;
public ProductResource(String name) {
this.name = name;
}
}
조종자
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("products/{id}", method = RequestMethod.GET)
ResponseEntity<Resource<ProductResource>> get(@PathVariable Long id) {
ProductResource productResource = new ProductResource("Apfelstrudel");
Resource<ProductResource> resource = new Resource<>(productResource, new Link("http://example.com/products/1"));
return ResponseEntity.ok(resource);
}
}
반응, 반응
{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}
}
여러 리소스 반환
Spring HATEOAS에는 내장 서포트가 포함되어 있습니다. 이 서포트는 에 의해 사용됩니다.Resources여러 리소스를 사용하여 응답을 반영합니다.
@RequestMapping("products/", method = RequestMethod.GET)
ResponseEntity<Resources<Resource<ProductResource>>> getAll() {
ProductResource p1 = new ProductResource("Apfelstrudel");
ProductResource p2 = new ProductResource("Schnitzel");
Resource<ProductResource> r1 = new Resource<>(p1, new Link("http://example.com/products/1"));
Resource<ProductResource> r2 = new Resource<>(p2, new Link("http://example.com/products/2"));
Link link = new Link("http://example.com/products/");
Resources<Resource<ProductResource>> resources = new Resources<>(Arrays.asList(r1, r2), link);
return ResponseEntity.ok(resources);
}
반응, 반응
{
"_links": {
"self": { "href": "http://example.com/products/" }
},
"_embedded": {
"productResources": [{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}, {
"name": "Schnitzel",
"_links": {
"self": { "href": "http://example.com/products/2" }
}
}]
}
}
키를 변경하려면productResources리소스에 주석을 달아야 합니다.
@Relation(collectionRelation = "items")
class ProductResource ...
내장된 리소스로 리소스 반환
이때가 바로 포주를 시작해야 할 때입니다. 스프링.HALResource@yonhap-damour가 완벽하게 또 다른 답안지에서 소개했습니다.
public class OrderResource extends HalResource {
final float totalPrice;
public OrderResource(float totalPrice) {
this.totalPrice = totalPrice;
}
}
조종자
@RequestMapping(name = "orders/{id}", method = RequestMethod.GET)
ResponseEntity<OrderResource> getOrder(@PathVariable Long id) {
ProductResource p1 = new ProductResource("Apfelstrudel");
ProductResource p2 = new ProductResource("Schnitzel");
Resource<ProductResource> r1 = new Resource<>(p1, new Link("http://example.com/products/1"));
Resource<ProductResource> r2 = new Resource<>(p2, new Link("http://example.com/products/2"));
Link link = new Link("http://example.com/order/1/products/");
OrderResource resource = new OrderResource(12.34f);
resource.add(new Link("http://example.com/orders/1"));
resource.embed("products", new Resources<>(Arrays.asList(r1, r2), link));
return ResponseEntity.ok(resource);
}
반응, 반응
{
"_links": {
"self": { "href": "http://example.com/products/1" }
},
"totalPrice": 12.34,
"_embedded": {
"products": {
"_links": {
"self": { "href": "http://example.com/orders/1/products/" }
},
"_embedded": {
"items": [{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}, {
"name": "Schnitzel",
"_links": {
"self": { "href": "http://example.com/products/2" }
}
}]
}
}
}
}
HATEOAS 1.0.0M1: 공식적인 방법을 찾을 수 없었습니다.우리가 한 일은 이렇습니다.
public abstract class HALResource extends ResourceSupport {
private final Map<String, ResourceSupport> embedded = new HashMap<String, ResourceSupport>();
@JsonInclude(Include.NON_EMPTY)
@JsonProperty("_embedded")
public Map<String, ResourceSupport> getEmbeddedResources() {
return embedded;
}
public void embedResource(String relationship, ResourceSupport resource) {
embedded.put(relationship, resource);
}
}
그리고 HALRESource를 확장할 수 있도록 했습니다.
업데이트: HATEOAS 1.0.0M1에서 EntityModel(및 실제로 RepresentationalModel을 확장하는 모든 것)은 내장된 리소스가 getContent를 통해 노출되는 한(또는 jackson이 콘텐츠 속성을 직렬화하는 방식으로) 이제 기본적으로 지원됩니다.예:
public class Result extends RepresentationalModel<Result> {
private final List<Object> content;
public Result(
List<Object> content
){
this.content = content;
}
public List<Object> getContent() {
return content;
}
};
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
List<Object> elements = new ArrayList<>();
elements.add(wrappers.wrap(new Product("Product1a"), LinkRelation.of("all")));
elements.add(wrappers.wrap(new Product("Product2a"), LinkRelation.of("purchased")));
elements.add(wrappers.wrap(new Product("Product1b"), LinkRelation.of("all")));
return new Result(elements);
알게 될 것입니다
{
_embedded: {
purchased: {
name: "Product2a"
},
all: [
{
name: "Product1a"
},
{
name: "Product1b"
}
]
}
}
여기 우리가 발견한 작은 예가 있습니다.우선 스프링-헤이트오아스-0.16을 사용합니다.
우리가 가지고 있는 영상GET /profile그것은 내장된 이메일 목록과 함께 사용자 프로파일을 반환해야 합니다.
이메일 자료가 있습니다.
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Relation(value = "email", collectionRelation = "emails")
public class EmailResource {
private final String email;
private final String type;
}
프로파일 응답에 포함시키고 싶은 두 개의 이메일
Resource primary = new Resource(new Email("neo@matrix.net", "primary"));
Resource home = new Resource(new Email("t.anderson@matrix.net", "home"));
이러한 리소스가 내장되어 있음을 나타내려면 EmbeddedWrapper의 인스턴스가 필요합니다.
import org.springframework.hateoas.core.EmbeddedWrappers
EmbeddedWrappers wrappers = new EmbeddedWrappers(true);
의 도움으로wrappers우리는 창조할 수 있습니다.EmbeddedWrapper각 e-메일에 대한 인스턴스(instance)를 제공하고 목록에 입력합니다.
List<EmbeddedWrapper> embeddeds = Arrays.asList(wrappers.wrap(primary), wrappers.wrap(home))
이제 남은 일은 이러한 임베디드로 프로필 리소스를 구성하는 것입니다.아래 예제에서는 롬복을 사용하여 코드를 단축합니다.
@Data
@Relation(value = "profile")
public class ProfileResource {
private final String firstName;
private final String lastName;
@JsonUnwrapped
private final Resources<EmbeddedWrapper> embeddeds;
}
하기 @JsonUnwrapped된.
그리고 우리는 컨트롤러로부터 이 모든 것을 돌려받을 준비가 되어 있습니다.
...
Resources<EmbeddedWrapper> embeddedEmails = new Resources(embeddeds, linkTo(EmailAddressController.class).withSelfRel());
return ResponseEntity.ok(new Resource(new ProfileResource("Thomas", "Anderson", embeddedEmails), linkTo(ProfileController.class).withSelfRel()));
}
이제 우리는 그에 대한 응답할 것입니다.
{
"firstName": "Thomas",
"lastName": "Anderson",
"_links": {
"self": {
"href": "http://localhost:8080/profile"
}
},
"_embedded": {
"emails": [
{
"email": "neo@matrix.net",
"type": "primary"
},
{
"email": "t.anderson@matrix.net",
"type": "home"
}
]
}
}
사용할때 흥미로운 부분Resources<EmbeddedWrapper> embeddeds서로 다른 리소스를 넣을 수 있고 관계별로 자동으로 그룹화됩니다.를 위해 를해을다다을을 사용합니다.@Relationorg.springframework.hateoas.core
또한 HAL에 내장된 자원에 대한 좋은 기사가 있습니다.
일반적으로 HATEOAS는 REST 출력을 나타내는 POJO를 생성하고 리소스 지원을 제공하는 HATEOAS를 확장해야 합니다.추가 POJO를 생성하지 않고 아래 코드와 같이 Resource, Resources 및 Link 클래스를 직접 사용할 수 있습니다.
@RestController
class CustomerController {
List<Customer> customers;
public CustomerController() {
customers = new LinkedList<>();
customers.add(new Customer(1, "Peter", "Test"));
customers.add(new Customer(2, "Peter", "Test2"));
}
@RequestMapping(value = "/customers", method = RequestMethod.GET, produces = "application/hal+json")
public Resources<Resource> getCustomers() {
List<Link> links = new LinkedList<>();
links.add(linkTo(methodOn(CustomerController.class).getCustomers()).withSelfRel());
List<Resource> resources = customerToResource(customers.toArray(new Customer[0]));
return new Resources<>(resources, links);
}
@RequestMapping(value = "/customer/{id}", method = RequestMethod.GET, produces = "application/hal+json")
public Resources<Resource> getCustomer(@PathVariable int id) {
Link link = linkTo(methodOn(CustomerController.class).getCustomer(id)).withSelfRel();
Optional<Customer> customer = customers.stream().filter(customer1 -> customer1.getId() == id).findFirst();
List<Resource> resources = customerToResource(customer.get());
return new Resources<Resource>(resources, link);
}
private List<Resource> customerToResource(Customer... customers) {
List<Resource> resources = new ArrayList<>(customers.length);
for (Customer customer : customers) {
Link selfLink = linkTo(methodOn(CustomerController.class).getCustomer(customer.getId())).withSelfRel();
resources.add(new Resource<Customer>(customer, selfLink));
}
return resources;
}
}
위의 답변들을 종합하면 훨씬 더 쉬운 접근법을 만들 수 있었습니다.
return resWrapper(domainObj, embeddedRes(domainObj.getSettings(), "settings"))
사용자 지정 유틸리티 클래스입니다(아래 참조).참고:
- 의 의 두 번째
resWrapper하다를...embeddedRes호출 - inside 를 한 를 를 한
resWrapper. - 의 의 첫 번째
embeddedRes이다 ㅇObject, 은 를 할 할 은 를ResourceSupport - 식의 결과는 확장된 형식입니다.
Resource<DomainObjClass>REST에 됩니다. ① Spring Data REST에 의해 처리됩니다.ResourceProcessor<Resource<DomainObjClass>>의 모음을 도 있고, 랩을 할 수도 . 을 , 을new Resources<>().
유틸리티 클래스 만들기:
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import java.util.Arrays;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.core.EmbeddedWrapper;
import org.springframework.hateoas.core.EmbeddedWrappers;
public class ResourceWithEmbeddable<T> extends Resource<T> {
@SuppressWarnings("FieldCanBeLocal")
@JsonUnwrapped
private Resources<EmbeddedWrapper> wrappers;
private ResourceWithEmbeddable(final T content, final Iterable<EmbeddedWrapper> wrappers, final Link... links) {
super(content, links);
this.wrappers = new Resources<>(wrappers);
}
public static <T> ResourceWithEmbeddable<T> resWrapper(final T content,
final EmbeddedWrapper... wrappers) {
return new ResourceWithEmbeddable<>(content, Arrays.asList(wrappers));
}
public static EmbeddedWrapper embeddedRes(final Object source, final String rel) {
return new EmbeddedWrappers(false).wrap(source, rel);
}
}
만 하면 만 포함하면 됩니다.import static package.ResourceWithEmbeddable.*사용할 수 있는 서비스 클래스를 제공합니다.
JSON은 다음과 같습니다.
{
"myField1": "1field",
"myField2": "2field",
"_embedded": {
"settings": [
{
"settingName": "mySetting",
"value": "1337",
"description": "umh"
},
{
"settingName": "other",
"value": "1488",
"description": "a"
},...
]
}
}
Spring은 Builder https://github.com/spring-projects/spring-hateoas/issues/864 를 제공합니다.
스프링부트-스타터-헤이트오 2.1.1을 가진 json을 이렇게 만들었습니다.
{
"total": 2,
"count": 2,
"_embedded": {
"contacts": [
{
"id": "1-1CW-303",
"role": "ASP",
"_links": {
"self": {
"href": "http://localhost:8080/accounts/2700098669/contacts/1-1CW-303"
}
}
},
{
"id": "1-1D0-267",
"role": "HSP",
"_links": {
"self": {
"href": "http://localhost:8080/accounts/2700098669/contacts/1-1D0-267"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/accounts/2700098669/contacts?limit=2&page=1"
},
"first": {
"href": "http://localhost:8080/accounts/2700098669/contacts?limit=2&page=1"
},
"last": {
"href": "http://localhost:8080/accounts/2700098669/contacts?limit=2&page=1"
}
}
}
이 모든 필드를 캡슐화하는 기본 클래스는
public class ContactsResource extends ResourceSupport{
private long count;
private long total;
private final Resources<Resource<SimpleContact>> contacts;
public long getTotal() {
return total;
}
public ContactsResource(long total, long count, Resources<Resource<SimpleContact>> contacts){
this.contacts = contacts;
this.total = total;
this.count = count;
}
public long getCount() {
return count;
}
@JsonUnwrapped
public Resources<Resource<SimpleContact>> getContacts() {
return contacts;
}
}
SimpleContact는 싱글 컨택에 대한 정보를 가지고 있으며 단지 pojo일 뿐입니다.
@Relation(value = "contact", collectionRelation = "contacts")
public class SimpleContact {
private String id;
private String role;
public String getId() {
return id;
}
public SimpleContact id(String id) {
this.id = id;
return this;
}
public String getRole() {
return role;
}
public SimpleContact role(String role) {
this.role = role;
return this;
}
}
연락처 리소스 만들기:
public class ContactsResourceConverter {
public static ContactsResource toResources(Page<SimpleContact> simpleContacts, Long accountId){
List<Resource<SimpleContact>> embeddeds = simpleContacts.stream().map(contact -> {
Link self = linkTo(methodOn(AccountController.class).getContactById(accountId, contact.getId())).
withSelfRel();
return new Resource<>(contact, self);
}
).collect(Collectors.toList());
List<Link> listOfLinks = new ArrayList<>();
//self link
Link selfLink = linkTo(methodOn(AccountController.class).getContactsForAccount(
accountId,
simpleContacts.getPageable().getPageSize(),
simpleContacts.getPageable().getPageNumber() + 1)) // +1 because of 0 first index
.withSelfRel();
listOfLinks.add(selfLink);
... another links
Resources<Resource<SimpleContact>> resources = new Resources<>(embeddeds);
ContactsResource contactsResource = new ContactsResource(simpleContacts.getTotalElements(), simpleContacts.getNumberOfElements(), resources);
contactsResource.add(listOfLinks);
return contactsResource;
}
}
저는 단지 통제관이 이런 식으로 부르는 것입니다.
return new ResponseEntity<>(ContactsResourceConverter.toResources(simpleContacts, accountId), HttpStatus.OK);
이 의존성을 폼에 추가합니다.다음 링크 확인: https://www.baeldung.com/spring-rest-hal
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
이렇게 반응이 바뀝니다.
"_links": {
"next": {
"href": "http://localhost:8082/mbill/user/listUser?extra=ok&page=11"
}
}
언급URL : https://stackoverflow.com/questions/25858698/spring-hateoas-embedded-resource-support
'programing' 카테고리의 다른 글
| 텍스트 파일 및 콘솔에 오류 및 출력 쓰기 (0) | 2023.09.13 |
|---|---|
| Jquery: 요소에 특정 CSS 클래스/스타일이 있는지 확인하는 방법 (0) | 2023.09.13 |
| Context.startForegroundService()가 Service.startForeground()를 호출하지 않았습니다. (0) | 2023.09.13 |
| Evenly spacing views using ConstraintLayout (0) | 2023.09.13 |
| 컴파일된 .apk를 장치에 설치하려고 하면 INSTALL_FAILED_UPDATE_INCOBLATE (0) | 2023.09.13 |