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
서로 다른 리소스를 넣을 수 있고 관계별로 자동으로 그룹화됩니다.를 위해 를해을다다을을 사용합니다.@Relation
org.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 |