Как правильно использовать 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);
}
Ответы
Ответ 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; }
}