Spring Boot 2.1.1 HAL自链接缺失分页参数问题咨询
首先要明确:你遇到的self链接模板缺失参数的情况,是Spring Data REST 2.1.x版本里的默认行为,不算严格意义上的Bug,但确实不符合客户端复用链接的需求。
当时的设计逻辑是:如果请求中已经明确传入了某个分页参数(比如你这里的size=10),生成self链接时会把这个参数从模板中移除,只保留未被指定的参数(也就是sort)。但这种设计会破坏客户端的跳转流程——比如用户想修改page页码或者调整size时,没法直接基于返回的self模板做修改,必须手动拼接参数,确实很不方便。
针对Spring Boot 2.1.1(对应Spring Data REST 2.1.x),这里有两种可行的解决方式:
方案1:自定义分页资源组装器(PagedResourcesAssembler)
我们可以重写默认的PagedResourcesAssembler,手动控制self链接的生成逻辑,让它保留所有分页参数的模板(page、size、sort),不管请求中是否指定了这些参数。
步骤如下:
- 创建一个自定义的
PagedResourcesAssembler实现类,注册为Spring Bean,替换默认的实现:
import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.rest.webmvc.support.PagedResourcesAssembler; import org.springframework.hateoas.Link; import org.springframework.hateoas.Resources; import org.springframework.hateoas.UriTemplate; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import java.util.stream.Collectors; @Component public class CustomPagedResourcesAssembler extends PagedResourcesAssembler<Object> { public CustomPagedResourcesAssembler(HateoasPageableHandlerMethodArgumentResolver resolver, UriComponentsBuilder baseUri) { super(resolver, baseUri); } @Override protected void addPaginationLinks(Resources<?> resources, Page<?> page, UriComponentsBuilder builder) { // 构建包含所有分页参数的self模板链接 String selfUri = builder.build().toUriString() + "{?page,size,sort}"; resources.add(new Link(new UriTemplate(selfUri), Link.REL_SELF)); // 手动添加其他分页链接(上一页、下一页、首页、末页) if (page.hasPrevious()) { resources.add(buildPageLink(page.previousPageable(), Link.REL_PREVIOUS)); } if (page.hasNext()) { resources.add(buildPageLink(page.nextPageable(), Link.REL_NEXT)); } resources.add(buildPageLink(page.firstPageable(), Link.REL_FIRST)); resources.add(buildPageLink(page.lastPageable(), Link.REL_LAST)); } private Link buildPageLink(Pageable pageable, String rel) { UriComponentsBuilder builder = getUriComponentsBuilder().cloneBuilder(); builder.queryParam("page", pageable.getPageNumber()); builder.queryParam("size", pageable.getPageSize()); // 处理排序参数 if (pageable.getSort().isSorted()) { builder.queryParam("sort", pageable.getSort().stream() .map(order -> order.getProperty() + "," + order.getDirection()) .collect(Collectors.toList())); } return new Link(builder.build().toUriString(), rel); } }
这个自定义类会强制self链接保留page、size、sort三个参数的模板,不管请求中是否传入了其中的某个参数,完全符合你的预期。
方案2:升级Spring Data REST版本
这个问题在Spring Data REST 2.2.x及以后的版本中已经被优化,链接生成逻辑做了调整:当请求中指定了部分分页参数时,self链接会保留当前参数的具体值,同时保留未指定参数的模板。比如你访问http://localhost:8080/admin/levels?size=10后,返回的self链接会变成:
"self": { "href": "http://localhost:8080/admin/levels?page=0&size=10{&sort}", "templated": true }
这正好是你想要的效果。如果你的项目允许升级依赖版本,这是最省心的解决方案——只需要把Spring Boot版本升级到2.2.x及以上(对应Spring Data REST 2.2.x+),就能自动修复这个问题,不需要写任何自定义代码。
补充说明
你之前找到的旧解决方案无效,是因为那些方案针对的是更早的Spring Data REST版本,而2.1.x的链接生成逻辑已经做了调整,所以旧方案不适用。如果暂时没法升级或者自定义组件,客户端只能临时拼接链接,但确实不如服务端返回正确的模板便捷。
内容的提问来源于stack exchange,提问作者gillesB

