Spring Hateoas ControllerLinkBuilder添加了空字段

问题描述 投票:9回答:5

我正在关注Spring REST的教程,并试图将HATEOAS链接添加到我的Controller结果中。

我有一个简单的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
            }
        ]
    }
]

具有空值的字段(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);
}
java spring spring-boot spring-hateoas
5个回答
8
投票

为了进一步参考,以防其他人偶然发现这一点,我想出来了:我在application.properties上添加了一个条目,即

spring.jackson.default-property-inclusion=NON_NULL

为什么这对于Link物体是必要的,但不是我不知道的User(并且没有更深入地倾斜)。


2
投票

根据Spring HATEOAS #493“由于您的顶级结构不是有效的HAL结构,因此绕过了HAL序列化器。”

解决问题的用法:

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

这将在Spring HATEOAS的未来版本中得到解决,其中toResources(Iterable <>)返回Resources或使用SimpleResourceAssembler


2
投票

正如semiintel所提到的,问题是List返回对象不是有效的HAL类型:

public List<Resource<User>> getAllUsers()

这可以通过将返回类型更改为Resources来轻松解决,如下所示:

public Resources<Resource<User>> getAllUsers()

然后,您可以通过更改return语句,将Resource <> List简单地包装在Resources <>对象中:

return userResources;

至:

return new Resources<>(userResources)

然后,您应该像对待单个对象一样获得正确的链接序列化。

这个方法是对这个非常有用的解决这个问题的文章的礼貌:

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


1
投票

为了避免链接中的其他属性如"hreflang": null, "media": null, "title": null, ...,我构建了一个包装类,如:

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

然后我扩展了org.springframework.hateoas.Resource以覆盖add(Link link)

public class WsDtoResource<T extends WsDto> extends Resource<T> {
    @Override
    public void add(Link link) {
        super.add(new WsDtoLink(link));
    }

}

然后我扩展了org.springframework.data.web.PagedResourcesAssembler以修复分页链接,如下所示:

public class WsDtoPagedResourceAssembler<T> extends PagedResourcesAssembler<T> {
    public WsDtoPagedResourceAssembler(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 WsDtoLink(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 WsDtoLink(link));
        exchangeLinks(resources);
        return resources;
    }

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

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

但更好的解决方案是生产正确的媒体类型org.springframework.hateoas.MediaTypes.HAL_JSON_VALUE。在我的情况下,我们生产org.springframework.http.MediaType.APPLICATION_JSON_VALUE,这是不正确的,但如果我们之后改变它会打破客户。


0
投票

在您的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"
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.