问题
I'm following a tutorial on Spring REST and am trying to add HATEOAS links to my Controller results.
I have a simple User class and a CRUD controller for it.
class User {
private int id;
private String name;
private LocalDate birthdate;
// and getters/setters
}
Service:
@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
}
Everything works fine except in my Controller, I want to add links from the all user list to the single users:
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();
}
so that for every User in the result list, a link to the user itself is added.
The link itself is created fine, but there are superfluous entries in the resulting 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
}
]
}
]
Where do the fields with null value (hreflang
, media
etc) come from and why are they added? Is there a way to get rid of them?
They do not appear when building a link to the all users list:
@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);
}
回答1:
For further reference in case anyone else stumbles upon this and I figured it out: I added an entry to application.properties
, namely
spring.jackson.default-property-inclusion=NON_NULL
Why this was necessary for the Link
objects, but not for the User
I don't know (and haven't diven in deeper).
回答2:
As per Spring HATEOAS #493 "As your top level structure is not a valid HAL structure hence bypassing the HAL serializers."
to resolve the issue use:
return new Resources<>(assembler.toResources(sourceList));
This will be resolved in a future versions of Spring HATEOAS where toResources(Iterable<>) returns Resources or by using the SimpleResourceAssembler.
回答3:
As semiintel alluded to, the problem is that your List return object is not a valid HAL type:
public List<Resource<User>> getAllUsers()
This can easily be addressed by changing the return type to Resources like this:
public Resources<Resource<User>> getAllUsers()
You can then simply wrap your Resource<> List in a Resources<> object by changing your return statement from:
return userResources;
to:
return new Resources<>(userResources)
Then you should get the proper links serialization like you do for your single object.
This method is courtesy of this very helpful article for addressing this issue:
https://www.logicbig.com/tutorials/spring-framework/spring-hateoas/multiple-link-relations.html
回答4:
To avoid the additional attributes in links as "hreflang": null, "media": null, "title": null, ...
, I built a wrapper class like:
@JsonInclude(JsonInclude.Include.NON_NULL)
public class OurCustomDtoLink extends Link {
public OurCustomDtoLink(Link link) {
super(link.getHref(), link.getRel());
}
}
Then I extended the org.springframework.hateoas.Resource
to overwrite add(Link link)
:
public class OurCustomDtoResource<T extends OurCustomDto> extends Resource<T> {
@Override
public void add(Link link) {
super.add(new OurCustomDtoLink(link));
}
}
Then I extended the org.springframework.data.web.PagedResourcesAssembler
to fix the paging links as following:
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);
}
}
But better solution would be producing correct media type org.springframework.hateoas.MediaTypes.HAL_JSON_VALUE
. In my case we produced org.springframework.http.MediaType.APPLICATION_JSON_VALUE
, what is not correct, but it would break the clients if we would change it afterwards.
回答5:
Add this dependency in your pom. Check this link: https://www.baeldung.com/spring-rest-hal
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
It will change your response like this.
"_links": {
"next": {
"href": "http://localhost:8082/mbill/user/listUser?extra=ok&page=11"
}
}
来源:https://stackoverflow.com/questions/49615358/spring-hateoas-controllerlinkbuilder-adds-null-fields