web-dev-qa-db-ja.com

Spring Hateoas ControllerLinkBuilderはnullフィールドを追加します

私は春のチュートリアルをフォローしていますREST、コントローラの結果にHATEOASリンクを追加しようとしています。

単純なUserクラスとそのためのCRUDコントローラーがあります。

class User {
    private int id;
    private String name;
    private LocalDate birthdate;
    // and getters/setters
}

サービス:

@Component
class UserService {
    private static List<User> users = new ArrayList<>();
    List<User> findAll() {
        return Collections.unmodifiableList(users);
    }
    public Optional<User> findById(int id) {
        return users.stream().filter(u -> u.getId() == id).findFirst();
    }
    // and add and delete methods of course, but not important here
}

私のコントローラーを除いてすべてがうまくいきます、私はすべてのユーザーのリストからシングルユーザーにリンクを追加したいと思います:

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/users")
    public List<Resource<User>> getAllUsers() {
        List<Resource<User>> userResources = userService.findAll().stream()
            .map(u -> new Resource<>(u, linkToSingleUser(u)))
            .collect(Collectors.toList());
        return userResources;
    }
    Link linkToSingleUser(User user) {
        return linkTo(methodOn(UserController.class)
                      .getById(user.getId()))
                      .withSelfRel();
    }

結果リストのすべてのユーザーについて、ユーザー自体へのリンクが追加されます。

リンク自体は問題なく作成されていますが、結果のJSONには余分なエントリがあります。

[
    {
        "id": 1,
        "name": "Adam",
        "birthdate": "2018-04-02",
        "links": [
            {
                "rel": "self",
                "href": "http://localhost:8080/users/1",
                "hreflang": null,
                "media": null,
                "title": null,
                "type": null,
                "deprecation": null
            }
        ]
    }
]

Null値を持つフィールド(hreflangmediaなど)はどこから来て、なぜ追加されるのですか?それらを取り除く方法はありますか?

すべてのユーザーリストへのリンクを作成する場合は表示されません。

@GetMapping("/users/{id}")
public Resource<User> getById(@PathVariable("id") int id) {
    final User user = userService.findById(id)
                                 .orElseThrow(() -> new UserNotFoundException(id));
    Link linkToAll = linkTo(methodOn(UserController.class)
                            .getAllUsers())
                            .withRel("all-users");
    return new Resource<User>(user, linkToAll);
}
12
daniu

他の誰かがこれに遭遇した場合の詳細な参照のために、私はそれを理解しました:application.propertiesにエントリを追加しました。

spring.jackson.default-property-inclusion=NON_NULL

なぜこれがLinkオブジェクトに必要だったのに、Userには必要なかったのかわかりません(さらに深く掘り下げていません)。

8
daniu

による Spring HATEOAS#49 「トップレベルの構造は有効なHAL構造ではないため、HALシリアライザーをバイパスします。」

問題を解決するには、以下を使用します。

return new Resources<>(assembler.toResources(sourceList));

これは、toResources(Iterable <>)がリソースを返す、または SimpleResourceAssembler を使用するSpring HATEOASの将来のバージョンで解決される予定です。

2
semiintel

Semiintelが言及したように、問題は、リストの戻りオブジェクトが有効なHALタイプではないことです。

public List<Resource<User>> getAllUsers()

これは、次のように戻り値の型をリソースに変更することで簡単に対処できます。

public Resources<Resource<User>> getAllUsers()

次に、returnステートメントを次のように変更するだけで、Resource <>リストをResources <>オブジェクトでラップできます。

return userResources;

に:

return new Resources<>(userResources)

次に、単一のオブジェクトの場合と同様に、適切なリンクのシリアル化を取得する必要があります。

この方法は、この問題に対処するための次の非常に役立つ記事のおかげです。

https://www.logicbig.com/tutorials/spring-framework/spring-hateoas/multiple-link-relations.html

2
java-addict301

リンクの追加属性を_"hreflang": null, "media": null, "title": null, ..._として回避するために、次のようなラッパークラスを作成しました。

_@JsonInclude(JsonInclude.Include.NON_NULL)
public class OurCustomDtoLink extends Link {
    public OurCustomDtoLink(Link link) {
        super(link.getHref(), link.getRel());
    }
}
_

次に、_org.springframework.hateoas.Resource_を拡張してadd(Link link)を上書きします。

_public class OurCustomDtoResource<T extends OurCustomDto> extends Resource<T> {
    @Override
    public void add(Link link) {
        super.add(new OurCustomDtoLink(link));
    }

}
_

次に、_org.springframework.data.web.PagedResourcesAssembler_を拡張して、ページングリンクを次のように修正しました。

_public class OurCustomDtoPagedResourceAssembler<T> extends PagedResourcesAssembler<T> {
    public OurCustomDtoPagedResourceAssembler(HateoasPageableHandlerMethodArgumentResolver resolver, UriComponents baseUri) {
        super(resolver, baseUri);
    }

    @Override
    public PagedResources<Resource<T>> toResource(Page<T> page, Link selfLink) {
        PagedResources<Resource<T>> resources = super.toResource(page, new OurCustomDtoLink(selfLink));
        exchangeLinks(resources);
        return resources;
    }

    @Override
    public <R extends ResourceSupport> PagedResources<R> toResource(Page<T> page, ResourceAssembler<T, R> assembler, Link link) {
        PagedResources<R> resources = super.toResource(page, assembler, new OurCustomDtoLink(link));
        exchangeLinks(resources);
        return resources;
    }

    @Override
    public PagedResources<?> toEmptyResource(Page<?> page, Class<?> type, Link link) {
        PagedResources<?> resources = super.toEmptyResource(page, type, new OurCustomDtoLink(link));
        exchangeLinks(resources);
        return resources;
    }

    private void exchangeLinks(PagedResources<?> resources) {
        List<Link> temp = new ArrayList<>(resources.getLinks()).stream()
            .map(OurCustomDtoLink::new)
            .collect(Collectors.toList());
        resources.removeLinks();
        resources.add(temp);
    }
}
_

しかし、より良い解決策は、正しいメディアタイプ_org.springframework.hateoas.MediaTypes.HAL_JSON_VALUE_を作成することです。私の場合、_org.springframework.http.MediaType.APPLICATION_JSON_VALUE_を生成しましたが、正しくありませんが、後で変更するとクライアントが壊れます。

1
Michael Hegner

この依存関係をpomに追加します。このリンクを確認してください: 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"
    }
}
0
aditya lath