Spring in Action Covers Spring 5-1--11 — страница 26 из 63

List findByUserOrderByPlacedAtDesc(User user);

Обратите внимание, что этот метод хранилища (репозитория) имеет в названии OrderByPlacedAtDesc. Часть OrderBy указывает свойство, по которому будут упорядочены результаты - в данном случае, свойство placeAt. Desc в конце заставляет упорядочение быть в порядке убывания. Таким образом, список возвращаемых заказов будет отсортирован от самых последних до наименее последних.

Этот метод контроллера может быть полезен после того, как пользователь разместил несколько заказов. Но это может стать немного громоздким для самых заядлых ценителей тако. Несколько заказов, отображаемых в браузере, полезны; бесконечный список из сотен заказов - это просто шум. Допустим, вы хотите ограничить количество отображаемых заказов самыми последними 20 заказами. Вы можете изменить ordersForUser()

@GetMapping

public String ordersForUser(

 @AuthenticationPrincipal User user, Model model) {

   Pageable pageable = PageRequest.of(0, 20);

 model.addAttribute("orders",

     orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));

   return "orderList";

}

вместе с соответствующими изменениями в OrderRepository:

List findByUserOrderByPlacedAtDesc(User user, Pageable pageable);

Вы изменили сигнатуру метода findByUserOrderByPlacedAtDesc(), чтобы принимать на вход Pageable в качестве параметра. Pageable - это способ Spring Data выбрать некоторое подмножество результатов по номеру страницы и размеру страницы. В методе контроллера ordersForUser() вы создали объект PageRequest, который реализовал Pageable для запроса первой страницы (нулевой страницы) с размером страницы 20, чтобы получить до 20 самых последних размещенных заказов для пользователя.

Хотя это работает фантастически, мне немного неловко, что вы жестко закодировали размер страницы. Что, если позже вы решите, что 20 - это слишком много заказов, и вы решите изменить его на 10? Поскольку он жестко запрограммирован, вам придется пересобрать и повторно развернуть приложение.

Вместо того, чтобы жестко задавать размер страницы, вы можете установить его с помощью пользовательского свойства конфигурации. Сначала вам нужно добавить новое свойство с именем pageSize в OrderController, а затем аннотировать OrderController с помощью @ConfigurationProperties, как показано в следующем листинге.

Листинг 5.1 Включение свойств конфигурации в OrderController

@Controller

@RequestMapping("/orders")

@SessionAttributes("order")

@ConfigurationProperties(prefix="taco.orders")

public class OrderController {

 private int pageSize = 20;

 public void setPageSize(int pageSize) {

   this.pageSize = pageSize;

 }

 ...

 @GetMapping

 public String ordersForUser(

      @AuthenticationPrincipal User user, Model model) {

    Pageable pageable = PageRequest.of(0, pageSize);

    model.addAttribute("orders",

    orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));

    return "orderList";

 }

}

Наиболее значительным изменением, внесенным в листинг 5.1, является добавление аннотации @ConfigurationProperties. Его префиксный атрибут имеет значение taco.orders, что означает, что при установке свойства pageSize необходимо использовать свойство конфигурации с именем taco.orders.pageSize.

Новое свойство pageSize по умолчанию равно 20. Но вы можете легко изменить его на любое желаемое значение, установив свойство taco.orders.pageSize. Например, вы можете установить это свойство в application.yml следующим образом:

taco:

 orders:

   pageSize: 10

Или, если вам нужно сделать быстрые изменения во время работы, вы можете сделать это без необходимости rebuild и redeploy приложения, задав свойство taco.orders.pageSize в качестве переменной среды:

$ export TACO_ORDERS_PAGESIZE=10

Любое средство, с помощью которого можно установить свойство конфигурации, можно использовать для настройки размера страницы последних заказов. Далее мы рассмотрим, как устанавливать данные конфигурации в хранилищах свойств (property holders).

5.2.1 Определение хранилища свойств конфигурации (configuration properties holders)

Ничто не указывает на то, что @ConfigurationProperties должен быть установлен на контроллере или любом другом конкретном компоненте. @ConfigurationProperties на самом деле часто размещаются на bean-компонентах, единственная цель которых в приложении - хранить данные конфигурации. Это исключает детали конфигурации из контроллеров и других классов приложений. Это также упрощает совместное использование общих свойств конфигурации несколькими компонентами, которые могут использовать эту информацию.

В случае свойства pageSize в OrderController вы можете перенести его в отдельный класс. Следующий листинг демонстрирует такой класс OrderProps.

Листинг 5.2. Извлечение pageSize в класс хранилища свойств

package tacos.web;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.stereotype.Component;

import lombok.Data;

@Component

@ConfigurationProperties(prefix="taco.orders")

@Data

public class OrderProps {

 private int pageSize = 20;

}

Как и в случае с OrderController, для свойства pageSize по умолчанию установлено значение 20, а для OrderProps добавлен @ConfigurationProperties с префиксом taco.orders. Он также помечен @Component, так что сканирование компонентов Spring автоматически обнаружит его и создаст как компонент в контексте приложения Spring. Это важно, так как следующим шагом является внедрение bean-компонента OrderProps в OrderController.

В хранилище конфигурации нет ничего особенного. Это bean, чьи свойства поступают из среды Spring. Они могут быть введены в любой другой компонент, которому нужны эти свойства. Для OrderController это означает удаление свойства pageSize из OrderController и вместо этого внедрение и использование компонента OrderProps:

@Controller

@RequestMapping("/orders")

@SessionAttributes("order")

public class OrderController {

 private OrderRepository orderRepo;

 private OrderProps props;

 public OrderController(OrderRepository orderRepo,

     OrderProps props) {

   this.orderRepo = orderRepo;

   this.props = props;

 }

 ...

 @GetMapping

 public String ordersForUser(

     @AuthenticationPrincipal User user, Model model) {

   Pageable pageable = PageRequest.of(0, props.getPageSize());

   model.addAttribute("orders",

     orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));

   return "orderList";

 }

 ...

}

Теперь OrderController больше не отвечает за обработку своих собственных свойств конфигурации. Это делает код OrderController немного аккуратнее и позволяет повторно использовать свойства из OrderProps в любом другом компоненте, который может в них нуждаться. Кроме того, вы группируете свойства конфигурации, которые относятся к заказам в одном месте: класс OrderProps. Если вам нужно добавить, удалить, переименовать или иным образом изменить содержащиеся в нем свойства, вам нужно только произвести эти изменения в OrderProps.

Например, давайте представим, что вы используете свойство pageSize в нескольких других bean-компонентах, когда вдруг решили, что было бы лучше применить некоторую проверку к этому свойству, чтобы ограничить его значения не менее чем 5 и не более 25. Без отдельного bean-компонента вам нужно будет применить аннотации проверки к OrderController, свойству pageSize и во всех других классах, использующих это свойство. Но поскольку вы создали pageSize в OrderProps, вам нужно только внести изменения в OrderProps:

package tacos.web;

import javax.validation.constraints.Max;

import javax.validation.constraints.Min;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.stereotype.Component;

import org.springframework.validation.annotation.Validated;

import lombok.Data;

@Component

@ConfigurationProperties(prefix="taco.orders")

@Data

@Validated

public class OrderProps {

   @Min(value=5, message="must be between 5 and 25")

   @Max(value=25, message="must be between 5 and 25")

   private int pageSize = 20;

}

//end::validated[]

Хотя вы могли бы так же легко применить аннотации @Validated, @Min и @Max к OrderController (и любым другим bean-компонентам, которые могут быть внедрены с помощью OrderProps), это просто намного больше загромождает OrderController. С помощью компонента-хранилища свойства конфигурации вы собрали спецификацию свойств конфигурации в одном месте, оставив классы, которым эти свойства нужны, относительно чистыми.

5.2.2 Объявление метаданных свойства конфигурации

В зависимости от вашей среды IDE вы, возможно, заметили, что запись taco.orders.pageSize в application.yml (или application.properties) имеет предупреждение о чем-то вроде неизвестного свойства «taco». Это предупреждение появляется из-за отсутствия метаданных, касающихся только что созданного свойства конфигурации. На рисунке 5.2 показано, как это выглядит, когда я наведу курсор мыши на тако-часть свойства в Spring Tool Suite.

Рисунок 5.2 Предупреждение о отсутствующих метаданных свойства конфигурации

Метаданные свойств конфигурации не являются обязательными и не мешают работе свойств конфигурации. Но метаданные могут быть полезны для предоставления некоторой минимальной документации по свойствам конфигурации, особенно в IDE.

Например, при наведении курсора на свойство security.user.password я вижу то, что показано на рисунке 5.3. Хотя помощь при наведении мыши минимальна, ее может быть достаточно, чтобы понять, для чего используется свойство и как его использовать.