44

我正在使用 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<User>从我的服务中检索,然后将其转换为PagedResources<UserResource>using PagedResourcesAssembler,如下所示: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返回的内容而不是我的 custom 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<User>PagedResources<User>特别是因为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);
}
4

3 回答 3

79

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

表示模型

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

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

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

资源汇编器

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

因此,PagedResourcesAssembler将获取一个 Spring Data实例,并通过评估和创建必要的链接以及导航页面的链接Page将其转换为实例。默认情况下——这可能是这里有趣的部分——它将使用一个普通的(内部类)将页面的各个元素转换为嵌套实例。PagedResourcesPagePageMetadataprevnextSimplePagedResourceAssemblerPRAResource

为了允许对此进行自定义,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 模板,以公开在HandlerMethodArgumentResolversforPageableSort.

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

于 2014-01-26T10:29:11.727 回答
0

我想将资源列表转换为页面。但是当给它 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;
    }
}

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

    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);
    }
于 2019-03-08T10:37:07.033 回答
-7

替代方式

另一种方法是使用 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; }
}
于 2014-10-21T08:46:04.727 回答