覆盖 Spring Data REST 中的相关链接

问题描述 投票:0回答:2

我正在使用 Spring Boot 2、Spring Data REST、Spring HATEOAS。

假设我有一个模型:

@EntityListeners({ContactListener.class})
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Contact extends AbstractEntity {

    @NotNull
    @Enumerated(EnumType.STRING)
    @Column(nullable = false, columnDefinition = "VARCHAR(30) DEFAULT 'CUSTOMER'")
    private ContactType type = ContactType.CUSTOMER;

    @NotNull
    @Enumerated(EnumType.STRING)
    @Column(nullable = false, columnDefinition = "VARCHAR(30) DEFAULT 'NATURAL_PERSON'")
    private PersonType personType = PersonType.NATURAL_PERSON;

    private String firstName;

    private String lastName;

    private String companyName;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "store_id", updatable = false)
    private Store store;

和商店:

@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Store extends AbstractEntity {

    @NotBlank
    @Column(nullable = false)
    private String name;

    @Username
    @NotBlank
    @Length(max = 16)
    @Column(nullable = false/*, unique = true*/)
    @ColumnTransformer(write = "UPPER(?)")
    private String code;

    private String address;

    private String zipCode;

    private String city;

    private String district;

当我收到联系信息时,回复如下所示:

{
    "sid": "962732c2-68a8-413b-9762-f676d42046b4",
    "createdBy": "1ccf2329-4aa3-4d55-8878-25517edf1522",
    "createdDate": "2019-05-28T14:06:07.011Z",
    "lastModifiedDate": "2019-06-04T08:46:02.591Z",
    "lastModifiedBy": "system",
    "createdByName": "Rossi Mario",
    "lastModifiedByName": null,
    "type": "CUSTOMER",
    "personType": "NATURAL_PERSON",
    "firstName": "Mario",
    "lastName": "Rossi",
    "companyName": null,
    "fullName": "Rossi Mario",
    "gender": "MALE",
    "birthDate": "2019-05-21T00:00:00Z",
    "birthCity": null,
    "job": null,
    "billingAddress": "Via 123",
    "billingZipCode": "14018",
    "billingCity": "Roatto",
    "billingDistrict": "AT",
    "billingCountry": "IT",
    "shippingAddress": "Via 123",
    "shippingZipCode": "14018",
    "shippingCity": "Roatto",
    "shippingDistrict": "AT",
    "shippingCountry": "IT",
    "taxCode": "XXXX",
    "vatNumber": null,
    "landlinePhone": null,
    "mobilePhone": null,
    "fax": null,
    "email": "[email protected]",
    "certifiedEmail": null,
    "survey": null,
    "iban": null,
    "swift": null,
    "publicAdministration": false,
    "sdiAccountId": "0000000",
    "preset": false,
    "_links": {
        "self": {
            "href": "http://localhost:8082/api/v1/contacts/1"
        },
        "contact": {
            "href": "http://localhost:8082/api/v1/contacts/1{?projection}",
            "templated": true
        },
        "store": {
            "href": "http://localhost:8082/api/v1/contacts/1/store{?projection}",
            "templated": true
        }
    }
}

正如您所看到的商店链接,它不是资源商店的self链接。 我想覆盖该链接设置自身资源。所以我创建了这个处理器:

@Component
public class DocumentRowProcessor implements ResourceProcessor<Resource<Contact>> {

    @Autowired
    private BasePathAwareLinks service;

    @Autowired
    private EntityLinks entityLinks;

    @Override
    public Resource<Contact> process(Resource<Contact> resource) {

        Store store = resource.getContent().getStore();
        if(store != null){
           resource.add(entityLinks.linkToSingleResource(store.getClass(), store.getId()).withRel("store"));
        }

        return resource;
    }
}

不幸的是,该链接现已被覆盖,但我在“商店”内找到了 2 个链接。调试我看到资源内部仅存在自我链接。我的猜测是在以下步骤中添加了相关链接。

如何干净利落地完成我的目标?

spring spring-data-rest spring-hateoas
2个回答
1
投票
  1. hateoas 链接在序列化期间添加到结果中(使用特定的 JSON 序列化程序),因此您无法使用 ResourceProcessor 删除它。
  2. 结果中的 hatoas 链接是该资源的
  3. proper 链接。 http://localhost:8082/api/v1/contacts/1/store
     是端点,您可以在其中检查哪个商店链接到该内容,或者您可以删除/修改这两个对象之间的关联。
但是,在某些用例中,您需要自链接才能执行进一步操作,并且您不希望从客户端发送额外的请求。 请执行下列操作: 1. 为内容创建投影。 2. 包括您需要的所有房产以及商店。 3. 如果您不需要此处商店的任何属性(仅需要自链接),则为商店实体创建一个“空投影”,并将该投影作为商店属性包含到联系人属性中。

当您获得联系人的投影时,结果将包含商店

内部商店属性的自链接。因此,主要的 _links

 集合仍然是常规的 hatos 链接集合,但会有一个 
store._links.self.href
 属性,其中包含关联商店的自链接。


0
投票
从 Spring Data REST 3.6.0 开始,您可以实现新的

LinkCollector

。以下内容有明显的缺陷,我无法测试 Java 版本(我的 Kotlin 版本工作正常),但它使用规范 URL 生成链接:

import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.rest.core.mapping.ResourceMapping; import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.core.support.SelfLinkProvider; import org.springframework.data.rest.webmvc.mapping.Associations; import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Links; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; // https://github.com/spring-projects/spring-data-rest/issues/2042#issuecomment-1913914052 public class AbsoluteLinkCollector implements LinkCollector { private final PersistentEntities entities; private final Associations associationLinks; private final SelfLinkProvider links; public AbsoluteLinkCollector(LinkCollector oldCollector) throws NoSuchFieldException, IllegalAccessException { Field entitiesField = oldCollector.getClass().getDeclaredField("entities"); entitiesField.setAccessible(true); this.entities = (PersistentEntities) entitiesField.get(oldCollector); Field associationLinksField = oldCollector.getClass().getDeclaredField("associationLinks"); associationLinksField.setAccessible(true); this.associationLinks = (Associations) associationLinksField.get(oldCollector); Field linksField = oldCollector.getClass().getDeclaredField("links"); linksField.setAccessible(true); this.links = (SelfLinkProvider) linksField.get(oldCollector); } @Override public Links getLinksFor(Object obj) { return this.getLinksFor(obj, Links.NONE); } @Override public Links getLinksFor(Object obj, Links existing) { Map<String, Link> linkMap = new HashMap<>(); for (Link l : existing) { linkMap.put(l.getRel().value(), l); } linkMap.put(IanaLinkRelations.SELF.value(), links.createSelfLinkFor(obj).withSelfRel()); PersistentEntity<?, ? extends PersistentProperty<?>> entity = entities.getRequiredPersistentEntity(obj.getClass()); entity.doWithAssociations((Association<?> assoc) -> { try { if (!associationLinks.isLinkableAssociation(assoc)) { return; } ResourceMetadata ownerMetadata = associationLinks.getMetadataFor(assoc.getInverse().getOwner().getType()); ResourceMapping propertyMapping = ownerMetadata.getMappingFor(assoc.getInverse()); LinkRelation rel = propertyMapping.getRel(); // if it is a collection, we can't get the value if (assoc.getInverse().isCollectionLike()) { return; } Field fieldValueField = obj.getClass().getDeclaredField(assoc.getInverse().getName()); fieldValueField.setAccessible(true); Object fieldValue = fieldValueField.get(obj); if (fieldValue == null) return; linkMap.put(rel.value(), links.createSelfLinkFor(fieldValue).withRel(rel)); } catch (Exception e) { // without this, we get a useless and confusing "Failed to write request" // error response. System.out.println("Error getting links for " + obj); e.printStackTrace(); throw new RuntimeException(e); } }); return Links.of(linkMap.values()); } @Override public Links getLinksForNested(Object obj, Links existing) { return this.getLinksFor(obj, existing); } }
它可以通过覆盖 

customizeLinkCollector

 中的 
RepositoryRestConfigurer
 来启用:

@Configuration public class RestConfiguration extends RepositoryRestConfigurerAdapter { @Override public LinkCollector customizeLinkCollector(LinkCollector collector) { return new AbsoluteLinkCollector(collector); } }
    
© www.soinside.com 2019 - 2024. All rights reserved.