私は春のチュートリアルをフォローしています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値を持つフィールド(hreflang
、media
など)はどこから来て、なぜ追加されるのですか?それらを取り除く方法はありますか?
すべてのユーザーリストへのリンクを作成する場合は表示されません。
@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);
}
他の誰かがこれに遭遇した場合の詳細な参照のために、私はそれを理解しました:application.properties
にエントリを追加しました。
spring.jackson.default-property-inclusion=NON_NULL
なぜこれがLink
オブジェクトに必要だったのに、User
には必要なかったのかわかりません(さらに深く掘り下げていません)。
による Spring HATEOAS#49 「トップレベルの構造は有効なHAL構造ではないため、HALシリアライザーをバイパスします。」
問題を解決するには、以下を使用します。
return new Resources<>(assembler.toResources(sourceList));
これは、toResources(Iterable <>)がリソースを返す、または SimpleResourceAssembler を使用するSpring HATEOASの将来のバージョンで解決される予定です。
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
リンクの追加属性を_"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
_を生成しましたが、正しくありませんが、後で変更するとクライアントが壊れます。
この依存関係を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"
}
}