복붙노트

[SPRING] Spring HATEOAS 임베디드 리소스 지원

SPRING

Spring HATEOAS 임베디드 리소스 지원

내 REST API에 HAL 형식을 사용하여 포함 된 리소스를 포함하고 싶습니다. 내 API에 Spring HATEOAS를 사용하고 있습니다. Spring HATEOAS는 임베디드 리소스를 지원하는 것 같습니다. 그러나 이것을 사용하는 방법에 대한 문서 나 예제는 없습니다.

누군가가 스프링 HATEOAS를 사용하여 임베디드 리소스를 포함하는 방법에 대한 예제를 제공 할 수 있습니까?

해결법

  1. ==============================

    1.이 일을하는 공식적인 방법을 찾지 못했습니다 ... 우리가 한 일이 여기에 있습니다.

    이 일을하는 공식적인 방법을 찾지 못했습니다 ... 우리가 한 일이 여기에 있습니다.

    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로 확장했다.

  2. ==============================

    2.HATEOAS에 대한 Spring의 문서를 읽으십시오. 기본을 얻는 데 도움이됩니다.

    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에는 Resource가 여러 자원에 대한 응답을 반영하기 위해 사용하는 임베디드 지원이 함께 제공됩니다.

        @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가 @ chris-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" }
                        }
                    }]
                }
            }
        }
    }
    
  3. ==============================

    3.여기에 우리가 찾은 작은 예가 있습니다. 우선 우리는 봄 - hateoas-0.16을 사용합니다.

    여기에 우리가 찾은 작은 예가 있습니다. 우선 우리는 봄 - hateoas-0.16을 사용합니다.

    이미징에는 임베디드 이메일 목록이있는 사용자 프로필을 반환해야하는 GET / 프로필이 있습니다.

    우리는 이메일 자원을 가지고 있습니다.

    @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"));
    

    이러한 리소스가 포함되어 있음을 나타내려면 EmbeddedWrappers 인스턴스가 필요합니다.

    import org.springframework.hateoas.core.EmbeddedWrappers
    EmbeddedWrappers wrappers = new EmbeddedWrappers(true);
    

    래퍼의 도움으로 각 이메일에 대해 EmbeddedWrapper 인스턴스를 생성하고 목록에 넣을 수 있습니다.

    List<EmbeddedWrapper> embeddeds = Arrays.asList(wrappers.wrap(primary), wrappers.wrap(home))
    

    유일하게 남아있는 것은 이러한 임베디드로 프로파일 자원을 구성하는 것입니다. 아래 예제에서는 코드를 짧게하기 위해 lombok을 사용합니다.

    @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 임베디드 사용에 흥미로운 부분은 서로 다른 리소스를 넣을 수 있고 릴레이션별로 자동으로 그룹화한다는 점입니다. 이를 위해 org.springframework.hateoas.core 패키지의 주석 @Relation을 사용합니다.

    또한 HAL에 임베디드 리소스에 대한 좋은 기사가 있습니다.

  4. ==============================

    4.일반적으로 HATEOAS는 REST 출력을 나타내는 POJO를 생성하고 HATEOAS가 제공 한 ResourceSupport를 확장해야합니다. POJO를 추가로 만들지 않고이 작업을 수행 할 수 있으며 아래 코드와 같이 Resource, Resources 및 Link 클래스를 직접 사용할 수 있습니다.

    일반적으로 HATEOAS는 REST 출력을 나타내는 POJO를 생성하고 HATEOAS가 제공 한 ResourceSupport를 확장해야합니다. 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;
        }
    }
    
  5. ==============================

    5.위의 답변을 결합하여 훨씬 쉬운 접근 방식을 만들었습니다.

    위의 답변을 결합하여 훨씬 쉬운 접근 방식을 만들었습니다.

    return resWrapper(domainObj, embeddedRes(domainObj.getSettings(), "settings"))
    

    이것은 사용자 정의 유틸리티 클래스입니다 (아래 참조). 노트 :

    유틸리티 클래스를 만듭니다.

    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);
        }
    }
    

    이 패키지를 사용하려면 서비스 클래스에 정적 static package.ResourceWithEmbeddable. *을 가져와야합니다.

    JSON은 다음과 같습니다.

    {
        "myField1": "1field",
        "myField2": "2field",
        "_embedded": {
            "settings": [
                {
                    "settingName": "mySetting",
                    "value": "1337",
                    "description": "umh"
                },
                {
                    "settingName": "other",
                    "value": "1488",
                    "description": "a"
                },...
            ]
        }
    }
    
  6. from https://stackoverflow.com/questions/25858698/spring-hateoas-embedded-resource-support by cc-by-sa and MIT license