【问题标题】:How to correctly use PagedResourcesAssembler from Spring Data?如何正确使用 Spring Data 中的 PagedResourcesAssembler?
【发布时间】:2014-02-16 06:09:42
【问题描述】:

我正在使用 Spring 4.0.0.RELEASE、Spring Data Commons 1.7.0.M1、Spring Hateoas 0.8.0.RELEASE

我的资源是一个简单的 POJO:

public class UserResource extends ResourceSupport { ... }

我的资源组装器将 User 对象转换为 UserResource 对象:

@Component
public class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> { 
    public UserResourceAssembler() {
        super(UserController.class, UserResource.class);
    }

    @Override
    public UserResource toResource(User entity) {
        // map User to UserResource
    }
}

在我的 UserController 中,我想从我的服务中检索 Page&lt;User&gt;,然后使用 PagedResourcesAssembler 将其转换为 PagedResources&lt;UserResource&gt;,如下所示:https://stackoverflow.com/a/16794740/1321564

@RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(@PageableDefault Pageable p, PagedResourcesAssembler assembler) {
    Page<User> u = service.get(p)
    return assembler.toResource(u);
}

这不会调用UserResourceAssembler,只是返回User 的内容,而不是我的自定义UserResource

返回单个资源有效:

@Autowired
UserResourceAssembler assembler;

@RequestMapping(value="{id}", method=RequestMethod.GET)
UserResource getById(@PathVariable ObjectId id) throws NotFoundException {
    return assembler.toResource(service.getById(id));
}

PagedResourcesAssembler 需要一些通用参数,但是我不能使用 T toResource(T),因为我不想将我的 Page&lt;User&gt; 转换为 PagedResources&lt;User&gt;,尤其是因为 User 是 POJO 而不是资源。

所以问题是:它是如何工作的?

编辑:

我的 WebMvcConfigurationSupport:

@Configuration
@ComponentScan
@EnableHypermediaSupport
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(pageableResolver());
        argumentResolvers.add(sortResolver());
        argumentResolvers.add(pagedResourcesAssemblerArgumentResolver());
    }

    @Bean
    public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
        return new HateoasPageableHandlerMethodArgumentResolver(sortResolver());
    }

    @Bean
    public HateoasSortHandlerMethodArgumentResolver sortResolver() {
        return new HateoasSortHandlerMethodArgumentResolver();
    }

    @Bean
    public PagedResourcesAssembler<?> pagedResourcesAssembler() {
        return new PagedResourcesAssembler<Object>(pageableResolver(), null);
    }

    @Bean
    public PagedResourcesAssemblerArgumentResolver pagedResourcesAssemblerArgumentResolver() {
        return new PagedResourcesAssemblerArgumentResolver(pageableResolver(), null);
    }

    /* ... */
}

解决方案:

@Autowired
UserResourceAssembler assembler;

@RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(@PageableDefault Pageable p, PagedResourcesAssembler pagedAssembler) {
    Page<User> u = service.get(p)
    return pagedAssembler.toResource(u, assembler);
}

【问题讨论】:

    标签: spring spring-mvc pagination spring-data spring-hateoas


    【解决方案1】:

    您似乎已经找到了正确的使用方法,但我想在这里详细介绍一些细节,以便其他人也能找到。我在this answer 中详细介绍了PagedResourceAssembler

    表示模型

    Spring HATEOAS 附带了各种表示模型的基类,可以轻松创建配备链接的表示。提供了三种开箱即用的类:

    • Resource - 物品资源。有效地包裹一些 DTO 或实体,这些 DTO 或实体捕获一个单个项并通过链接丰富它。
    • Resources - 集合资源,可以是某些东西的集合,但通常是 Resource 实例的集合。
    • PagedResources - Resources 的扩展,可捕获额外的分页信息,例如总页数等。

    所有这些类都派生自ResourceSupport,这是Link 实例的基本容器。

    资源组装器

    ResourceAssembler 现在是将域对象或 DTO 转换为此类资源实例的缓解组件。这里重要的部分是,它将 one 源对象转换为 one 目标对象。

    因此,PagedResourcesAssembler 将采用 Spring Data Page 实例并将其转换为 PagedResources 实例,方法是评估 Page 并创建必要的 PageMetadata 以及 prevnext导航页面的链接。默认情况下 - 这可能是这里有趣的部分 - 它将使用普通的 SimplePagedResourceAssemblerPRA 的内部类)将页面的各个元素转换为嵌套的 Resource 实例。

    为了允许对此进行自定义,PRA 具有额外的 toResource(…) 方法,这些方法采用委托 ResourceAssembler 来处理各个项目。所以你最终会得到这样的结果:

     class UserResource extends ResourceSupport { … }
    
     class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> { … }
    

    客户端代码现在看起来像这样:

     PagedResourcesAssembler<User> parAssembler = … // obtain via DI
     UserResourceAssembler userResourceAssembler = … // obtain via DI
    
     Page<User> users = userRepository.findAll(new PageRequest(0, 10));
    
     // Tell PAR to use the user assembler for individual items.
     PagedResources<UserResource> pagedUserResource = parAssembler.toResource(
       users, userResourceAssembler);
    

    展望

    从即将发布的 Spring Data Commons 1.7 RC1(和 Spring HATEOAS 0.9 过渡)开始,prevnext 链接将生成为符合 RFC6540 的 URI 模板,以公开在 HandlerMethodArgumentResolvers 中配置的分页请求参数对于PageableSort

    您可以通过使用 @EnableSpringDataWebSupport 注释配置类来简化上面显示的配置,这样您就可以摆脱所有显式的 bean 声明。

    【讨论】:

    • 嗨,你能告诉我如何在PagedResourcesAssembler 中正确使用这些泛型吗?我只能将其用作原始类型。它说PagedResources&lt;R&gt; toResource(Page&lt;T&gt;, ResourceAssemblerSupport&lt;T, R&gt;)。 Eclipse 不会接受这个:PagedResources&lt;UserResource&gt; p = parAssembler.toResource(userPage, userResourceAssembler).
    • 我在上面的答案中添加了PagedResourcesAssembler 的泛型。如果您提供自定义UserResourceAssemblerResourceAssembler&lt;User, UserResource&gt;,则需要注入PagedResourcesAssembler&lt;User&gt; 以最终创建PagedResources&lt;UserResource&gt;。我刚刚在代码库中添加了一个test case,给出了一个工作示例。
    • 面对同样的问题,在 Spring 4.0.6.RELEASE 和 0.16.0.RELEASE 上,toResource 只接受一个参数,adminResourcePages,并且无法传入自定义的 adminResourceAssembler。这是方法签名: toResource(Page, Link) 我在这里使用了错误的 API 吗?
    • 我现在知道了: Page eventAdminPages = new PageImpl(searchedAdminsEvent.getEventAdmins(), pageable, searchedAdminsEvent.getTotalElements()); PagedResources adminPageResources = pagedResourcesAssembler.toResource(eventAdminPages, adminResourceAssembler);
    • 在这种新的资源分页方式中,重写 toResources 是否还有用处? (注意尾随的's')。干杯。
    【解决方案2】:

    我想将资源列表转换为页面。但是当给它 PagedResourcesAssembler 时,它正在吃掉内部链接。

    这将使您的列表分页。

     public class JobExecutionInfoResource extends ResourceSupport {
        private final JobExecutionInfo jobExecution;
    
        public JobExecutionInfoResource(final JobExecutionInfo jobExecution) {
            this.jobExecution = jobExecution;        
            add(ControllerLinkBuilder.linkTo(methodOn(JobsMonitorController.class).get(jobExecution.getId())).withSelfRel()); // add your own links.          
        }
    
        public JobExecutionInfo getJobExecution() {
            return jobExecution;
        }
    }
    

    分页资源提供 ResourceAssembler 告诉分页资源使用它,它什么也不做,只是简单地返回它,因为它已经是一个被传递的资源列表。

        private final PagedResourcesAssembler<JobExecutionInfoResource> jobExecutionInfoResourcePagedResourcesAssembler;
        public static final PageRequest DEFAULT_PAGE_REQUEST = new PageRequest(0, 20);
        public static final ResourceAssembler<JobExecutionInfoResource, JobExecutionInfoResource> SIMPLE_ASSEMBLER = entity -> entity;
    
    @GetMapping("/{clientCode}/{propertyCode}/summary")
        public PagedResources<JobExecutionInfoResource> getJobsSummary(@PathVariable String clientCode, @PathVariable String propertyCode,
                                                                       @RequestParam(required = false) String exitStatus,
                                                                       @RequestParam(required = false) String jobName,
                                                                       Pageable pageRequest) {
            List<JobExecutionInfoResource> listOfResources = // your code to generate the list of resource;
            int totalCount = 10// some code to get total count;
            Link selfLink = linkTo(methodOn(JobsMonitorController.class).getJobsSummary(clientCode, propertyCode, exitStatus, jobName, DEFAULT_PAGE_REQUEST)).withSelfRel();
            Page<JobExecutionInfoResource> page = new PageImpl<>(jobExecutions, pageRequest, totalCount);
            return jobExecutionInfoResourcePagedResourcesAssembler.toResource(page, SIMPLE_ASSEMBLER, selfLink);
        }
    

    【讨论】:

      【解决方案3】:

      替代方式

      另一种方法是使用 Range HTTP 标头(在RFC 7233 中阅读更多内容)。您可以这样定义 HTTP 标头:

      Range: resources=20-41
      

      这意味着,您想要从 20 到 41(包括)获取资源。这种方式允许 API 的消费者接收到精确定义的资源。

      这只是另一种方式。范围通常与另一个单位(如字节等)一起使用。

      推荐方式

      如果您想使用分页功能并拥有真正适用的 API(包括超媒体/HATEOAS),那么我建议您将 Page 和 PageSize 添加到您的 URL。示例:

      http://host.loc/articles?Page=1&PageSize=20
      

      然后,您可以在 BaseApiController 中读取此数据并在所有请求中创建一些 QueryFilter 对象:

      {
          var requestHelper = new RequestHelper(Request);
      
          int page = requestHelper.GetValueFromQueryString<int>("page");
          int pageSize = requestHelper.GetValueFromQueryString<int>("pagesize");
      
          var filter = new QueryFilter
          {
              Page = page != 0 ? page : DefaultPageNumber,
              PageSize = pageSize != 0 ? pageSize : DefaultPageSize
          };
      
          return filter;
      }
      

      您的 api 应该返回一些包含项目数量信息的特殊集合。

      public class ApiCollection<T>
      {
          public ApiCollection()
          {
              Data = new List<T>();
          }
      
          public ApiCollection(int? totalItems, int? totalPages)
          {
              Data = new List<T>();
              TotalItems = totalItems;
              TotalPages = totalPages;
          }
      
          public IEnumerable<T> Data { get; set; }
      
          public int? TotalItems { get; set; }
          public int? TotalPages { get; set; }
      }
      

      你的模型类可以继承一些支持分页的类:

      public abstract class ApiEntity
      {
          public List<ApiLink> Links { get; set; }
      }
      
      public class ApiLink
      {
          public ApiLink(string rel, string href)
          {
              Rel = rel;
              Href = href;
          }
      
          public string Href { get; set; }
      
          public string Rel { get; set; }
      }
      

      【讨论】:

      • 你的回答与SpringsPagedResourcesAssembler 无关。模型类不应该继承任何 API 的东西。 ... 关于Range:完全不推荐!生成的 URL 不是幂等的,但 GET 请求应该是。这意味着,我可以使用不同的 Range 标头调用相同的 URL 并获得不同的结果。 --- 你不能复制粘贴 URL 得到相同的结果,因为你总是会丢失Range 信息。
      • 1) 同意,我只是展示了另一种方式... Range 通常用于流式传输 api
      • 2) 同意,我的示例不包含任何数据模型类,只有 API 模型类
      猜你喜欢
      • 2018-01-15
      • 2015-05-26
      • 2013-08-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-23
      • 2020-02-09
      • 2016-08-04
      相关资源
      最近更新 更多