Как правильно использовать PagedResourcesAssembler из Spring Data?

Я использую 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> с помощью PagedResourcesAssembler, как показано здесь: /questions/31926866/spring-mvc-3-vernut-stranitsu-dannyih-spring-v-formate-json/31926877#31926877

@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<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);
}

2 ответа

Решение

Похоже, вы уже узнали о правильном способе использования, но я бы хотел немного подробнее остановиться здесь на некоторых деталях. Я вошел в аналогичную деталь о PagedResourceAssembler в этом ответе.

Модели представления

Spring HATEOAS поставляется с множеством базовых классов для моделей представлений, которые облегчают создание представлений, снабженных ссылками. Из коробки предусмотрено три типа классов:

  • Resource - ресурс предмета. Эффективно, чтобы обернуть вокруг некоторого DTO или объекта, который захватывает один элемент и обогащает его ссылками.
  • Resources - ресурс коллекции, который может быть коллекцией чего-то, но обычно является коллекцией Resource экземпляров.
  • PagedResources - расширение Resources которая захватывает дополнительную информацию о нумерации страниц, такую ​​как количество страниц и т. д.

Все эти классы происходят от ResourceSupport, который является основным контейнером для Link экземпляров.

Ресурс ассемблеров

ResourceAssembler теперь является смягчающим компонентом для преобразования ваших доменных объектов или DTO в такие экземпляры ресурсов. Важной частью здесь является то, что он превращает один исходный объект в один целевой объект.

Итак PagedResourcesAssembler возьму данные Spring Page экземпляр и превратить его в PagedResources Например, оценивая Page и создание необходимого PageMetadata так же хорошо как prev а также next ссылки для навигации по страницам. По умолчанию - и это, вероятно, самая интересная часть здесь - он будет использовать простой SimplePagedResourceAssembler (внутренний класс PRA) преобразовать отдельные элементы страницы во вложенные 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 транзитивно) prev а также next ссылки будут сгенерированы как RFC6540- совместимые шаблоны URI для предоставления параметров запроса разбивки на страницы, настроенных в HandlerMethodArgumentResolvers за Pageable а также Sort,

Конфигурацию, которую вы показали выше, можно упростить, пометив класс config с помощью @EnableSpringDataWebSupport что позволит вам избавиться от всех явных объявлений bean-компонентов.

Я хотел преобразовать список ресурсов на страницу. но когда он давал ему 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);
    }

АЛЬТЕРНАТИВНЫЙ ПУТЬ

Другой способ - использовать HTTP-заголовок Range (подробнее см. 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; }
}
Другие вопросы по тегам