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