【问题标题】:Spring Hateoas ControllerLinkBuilder adds null fieldsSpring Hateoas ControllerLinkBuilder 添加空字段
【发布时间】:2018-04-02 16:57:18
【问题描述】:

我正在学习有关 Spring 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
            }
        ]
    }
]

具有空值的字段(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


    【解决方案1】:

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

    spring.jackson.default-property-inclusion=NON_NULL
    

    为什么这对于 Link 对象是必要的,而不是对于 User 我不知道(也没有深入研究)。

    【讨论】:

    • 谢谢。这也对我有用。但是,我有一个 application.yml 的解决方案是这样的:spring: jackson: default-property-inclusion: NON_NULL
    • 这里有些歧义,我尝试添加此属性无济于事,您查看过其他任何解决方案吗?
    • 这不是一个好的解决方案 IMO。当您可能希望它们在其他 json 结构中时,这将全面禁用空值的序列化。这相当于试图通过焚烧田地来去除一些死去的玉米秆。
    • @java-addict301 是的,它并不完美,但我还没有遇到完美的东西。你的观点是正确的,但只要你知道这个变通办法的效果,你也可以解决这个问题(你不能用“总是序列化”来注释类字段吗?)
    • 我也想知道替代解决方案。这会影响整个项目,看起来并不理想。
    【解决方案2】:

    正如 semiintel 所暗示的,问题在于您的 List 返回对象不是有效的 HAL 类型:

    public List<Resource<User>> getAllUsers()
    

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

    public Resources<Resource<User>> getAllUsers()
    

    然后,您可以简单地将您的 Resource 列表包装在 Resources 对象中,方法是更改​​您的 return 语句:

    return userResources;
    

    到:

    return new Resources<>(userResources)
    

    然后您应该像为单个对象所做的那样获得正确的链接序列化。

    此方法由这篇对解决此问题非常有用的文章提供:

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

    【讨论】:

      【解决方案3】:

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

      解决问题使用:

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

      这将在 Spring HATEOAS 的未来版本中解决,其中 toResources(Iterable) 返回资源或使用SimpleResourceAssembler

      【讨论】:

      • 那么对于sourceList,您是指服务返回的List&lt;User&gt; 还是包含我为每个用户创建的链接的List&lt;Resource&lt;User&gt;&gt;?我假设是前者,但我确实想要单独的链接......而且在提供SimpleResourceAssembler 之前,我不知道如何添加它们。不过还是谢谢。
      • 对于那些不想处理创建汇编程序的人,我在下面发布了一个答案,该答案还返回了一个 Resources 对象(在我看来以更简单的方式)。
      【解决方案4】:

      为了避免链接中的附加属性为"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,这是不正确的,但如果我们之后更改它会破坏客户端。

      【讨论】:

      • 似乎在任何地方都找不到 WsDto。这是你自己的类型吗?
      • 嗨,詹姆斯,感谢您的留言。也许不清楚,但 WsDto 只是任何自定义 dto 类型。我现在试图在我的回答中说得更清楚。
      【解决方案5】:

      在你的 pom.xml 中添加这个依赖。 检查此链接: 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"
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-08-02
        • 2019-10-22
        • 2015-04-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多