Решатели аргументов метода - отличный инструмент для поддержания чистоты вашего Spring приложения. Реализуя интерфейс HandlerMethodArgumentResolver, вы можете извлечь логику, необходимую для создания экземпляра настраиваемого аргумента для компонента и поддерживать чистоту ваших контроллеров.

Но в некоторых ситуациях использование преобразователей аргументов метода может привести к неидеальной производительности.

О чем я говорю

Я покажу вам на примере:

  • Допустим, у нас есть приложение, которое получает настраиваемый заголовок с JSON-представлением объекта контекста.
  • Мы будем разрешать различные аргументы, используя этот объект контекста.
  • В какой-то момент мы обнаружим некоторые вещи, которые нужно выполнить более одного раза.

Мы можем начать пример кода с написания преобразователя для самого ContextInformation:

Этот компонент разрешает ContextInformation из заголовка с именем X-App-Context. Если этот преобразователь правильно зарегистрирован (подробнее о регистрации преобразователей см. В моей статье о преобразователях аргументов), мы можем написать контроллер для тестирования преобразователя:

Мы можем протестировать наш контроллер и резольвер с помощью httpie:

Хорошо, все выглядит хорошо, если предположить, что наш ContextInformation выглядит примерно так:

Наш аргумент разрешен правильно, и наш контроллер может использовать настраиваемый аргумент, не загрязняя свой код созданием экземпляра ContextInformation.

Все идет нормально.

Эта проблема

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

На данный момент мы выполняем почти одну и ту же задачу дважды. Конечно, мы можем провести рефакторинг обоих преобразователей, чтобы избежать повторения кода, но десериализация JSON, содержащегося в заголовке, будет выполняться несколько раз, если оба преобразователя будут выполнены. Давайте напишем метод контроллера, чтобы это произошло:

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

Решение проблемы с фильтрами

Мы можем использовать фильтры и атрибуты запроса, чтобы избежать этой проблемы: мы обрабатываем заголовок в фильтре, а затем, после десериализации JSON, мы сохраняем десериализованный объект как атрибут запроса.

Этот метод является настолько старомодным, насколько это возможно, поддержка атрибутов запроса присутствует в API сервлета с момента первой спецификации сервлета.

Напишем наш фильтр:

В этом случае мы используем OncePerRequestFilter, который представляет собой реализацию фильтра с поддержкой Spring, которая срабатывает только один раз за запрос. OncePerRequestFilter предоставляет шаблонный метод doFilterInternal, который мы можем переопределить, чтобы реализовать поведение фильтра.

В этом примере мы игнорируем обработку ошибок и предполагаем, что заголовок всегда присутствует и правильно сформирован.

Учитывая это предположение, мы знаем, что у нас будет атрибут, содержащий должным образом instantiatedContextInformation для каждого запроса, который достигает нашего сервера.

Мы можем изменить наши резолверы, чтобы воспользоваться этим фактом:

Решатель для ContextInformation теперь сведен к одной значащей строке кода. То же самое и с UserInformationtResolver:

Немного рефакторинга

Теперь решение работает правильно, но мы можем сделать немного лучше: оба преобразователя должны знать имя атрибута, выполнять приведение и т. Д.

Мы можем написать служебный компонент:

С этим RequestUtils наш фильтр теперь может быть реализован следующим образом:

И наши финальные резолверы выглядят чище:

Я загрузил пример кода в свою учетную запись Github, чтобы упростить отслеживание и запуск примеров.

Подведение итогов

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

О подобной технике для АОП мы поговорим в ближайшее время.

Будьте на связи!