Подтвердить что ты не робот

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

Я использую 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, как показано здесь: qaru.site/info/188597/...

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

Это не вызывает UserResourceAssembler, а вместо UserResource возвращается содержимое User.

Возврат одного ресурса работает:

@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 и нет Ресурс.

Итак, вопрос: как это работает?

EDIT:

Мой 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);
}
4b9b3361

Ответ 1

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

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

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

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

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

Ассемблеры ресурсов

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

Итак, PagedResourcesAssembler возьмет экземпляр Spring Data 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);

Outlook

По состоянию на предстоящий Spring Data Commons 1.7 RC1 (и Spring HATEOAS 0.9 транзитивно) ссылки prev и next будут сгенерированы как RFC6540, чтобы показать параметры запроса страницы, настроенные в HandlerMethodArgumentResolvers для Pageable и Sort.

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

Ответ 2

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

Другой способ - использовать HTTP-заголовок Range (подробнее читайте в RFC 7233). Вы можете определить HTTP-заголовок таким образом:

Range: resources=20-41

Это означает, что вы хотите получить ресурс от 20 до 41 (включая). Этот способ позволяет консулам API получать точно определенные ресурсы.

Это просто альтернативный путь. Диапазон часто используется с другими единицами (например, байтами и т.д.).

РЕКОМЕНДУЕМЫЙ ПУТЬ

Если вы хотите работать с разбиением на страницы и иметь действительно применимый API (hypermedia/HATEOAS включены), я рекомендую добавить страницу и страницу в ваш 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; }
}