programing

Spring HATEOAS 내장 리소스 지원

goodsources 2023. 9. 13. 22:34
반응형

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

반응형