Всем привет, друзья! Это вторая статья про обертку ответов контроллеров. Сегодня хочу рассказать про то, как использовать BeanPostProcessor и для чего это может быть нужно - это основной посыл статьи. Сделаем мы это немного доработав проект из предыдущей статьи.  Под прошлой статьей пользователь с ником @maxzh83 указал на логическую недоработку проекта - невозможность реализовать несколько раз сервис IWrapperService, что происходит из-за того, что идет инжект только одной реализации сервиса. Сегодня посмотрим на то, как можно решить данную недоработку используя уже готовый функционал спринга - внедрение коллекций. И как можно немного доработать функционал спринга с помощью BeanPostProcessor для более удобного внедрения зависимостей для нашей конкретной ситуации. Предлагаю вспомнить, что мы сделали в прошлой статье. Код на GitHub на ветке master. Мы создали стартер, в котором присутствует 2 аннотации: @DisableResponseWrapper и @EnableResponseWrapper, а также 2 интерфейса: IWrapperModel и IWrapperService, используя которые мы можем обернуть все необходимые ответы контроллеров в новый класс. Для реализации такого функционала, в стартере мы создали класс, реализующий интерфейс ResponseBodyAdvice<Object> и аннотированный с помощью @ControllerAdvice(annotations = EnableResponseWrapper.class) Аннотация @ControllerAdvice(annotations = EnableResponseWrapper.class) указывает, что методы данного компонента будут использоваться сразу несколькими контроллерами. Также указываем, что наши методы будут обрабатывать только те контроллеры, которые помечены @EnableResponseWrapper. Класс реализует интерфейс ResponseBodyAdvice<>, который позволяет настраивать ответ, после его возвращения методом @ResponseBody или контроллером ResponseEntity, но до того, как тело будет записано с помощью HttpMessageConverter. В классе необходимо было реализовать 2 метода: - метод вызывается для каждой точки входа контроллера и является фильтром к дальнейшей обработке. В случае, если этот метод возвращает false - дальнейшая обработка точки входа контроллера не происходит. - метод обработки точки входа контроллера. Вернуть в методе необходимо объект, который будет возвращен api. Этот механизм мы положили в стартер и написали демо-проект под данный функционал. Представленный в статье механизм возможно использовать и для иных задач, связанных с обработкой ответов контроллеров. Подобный подход используется для обработки исключений в контроллерах. (Хорошая статья об обработки исключений https://habr.com/ru/post/528116/) С помощью использования ControllerAdvice+ResponseBodyAdvice и аннотаций вы можете более гибко настроить обработку любых ответов контроллеров с использованием большого количества информации о методе, контроллере, запросе и ответе контроллера. Мы хотим в результате получить возможность реализовывать для каждого класса-обертки свой сервис.  Самый простой и быстрый способ - воспользоваться тем, что уже умеет spring - внедрять коллекции зависимостей.  Создадим аннотацию для сервисов: Так как наша аннотация всегда висит над сервисом - то вешаем над нашей аннотацией @Service - это позволит использовать @WrapperService вместо @Service. В качестве аргумента будем принимать модель-обертку для дальнейшего получения по ней сервиса из списка. Сразу сделаем небольшие доработки по проекту для того, чтобы стартер был более гибким. Добавим generic-и. Интерфейс модели: Создаем функции-хелперы setBodyHelper и setDataHelper для того, чтобы иметь возможность работать с Wildcard. Подробнее про helper-методы и зачем они нужны можно прочесть в официальной документации. Аналогично делаем для интерфейса сервиса: Также для того, чтобы производить обертку на основе данных о запросе, создадим класс данных. Теперь все, что нам необходимо сделать - это написать инжект коллекции в обработчике контроллеров ResponseWrapperAdvice. Вместо: Пишем: Таким образом spring самостоятельно найдет и соберет в список все классы, реализующие IWrapperService с любыми Body и Data. Напишем метод получения из списка того сервиса, который относится к конкретно нашему классу-обертке. С помощью iWrapperService.getClass().getAnnotations() получаем все аннотации для каждого из сервисов и ищем среди них аннотацию @WrapperService. Из нее получаем класс-обертку, которую сравниваем с той, с которой работаем сейчас сами. Данный подход вполне оптимальный, но не лучший. Во-первых, метод getWrapperService будет вызываться каждый раз при запросе, в чем нет необходимости. Это можно поправить кешированием, например. Во-вторых, как мне кажется, куда логичнее инжектить не список сервисов и затем по нему искать нужный перебором, а сразу мапу, в которой будет класс-обертка против сервиса. Спринг умеет инжектить мапы, но не сможет (и не должен) сам догадаться брать класс-обертку из аннотации. Для таких случаев спринг предоставляет инструменты внедрения в процесс создания и настройки бинов (а также добавления их в контекст). Воспользуемся инструментом донастройки бинов - реализацией интерфейса BeanPostProcessor. Полный код проекта с реализацией через внедрение коллекций: GitHub В спринге присутствуют следующие этапы инициализации контекста, в каждый из которых, при желании, можно вклиниться самому: Парсирование кофигурации (XML, JavaConfig и тд). После парсирования конфигурации создается BeanDefenition - мета информация, описывающая будущий бин; Настройка уже созданных BeanDefinition - на этом этапе мы можем повлиять на то, какими будут наши бины еще до их создания; Создание кастомных FactoryBean - можно самостоятельно создать фабрику, которая будет создавать бины определенного типа; Создание экземпляров бинов - тут происходит создание классов или проксей. На этом этапе можно организовать инжект поля через конструктор; Настройка созданных бинов - BeanPostProcessor - именно то, что нам нужно. На этом этапе конструктор бина уже выполнился и бин создан, но бин еще не попал в контекст. Хорошая и подробная статья про этапы инициализации контекста. Класс, реализующий BeanPostProcessor - это структурный компонент - его бин должен быть декларирован одним из 4 способов. Интерфейс предоставляет 2 метода postProcessBeforeInitialization и postProcessAfterInitialization. Первый вызывается до PostConstruct, второй после. Каждый из методов должен вернуть бин. Во-первых, для удобства, нам понадобится новая аннотация, которая будет обозначать место для инжекта нашей мапы: Во-вторых, нам нужен сам класс, реализующий BeanPostProcessor: Для получения бинов в мапу нам понадобится заинжектить контекст. Так как мы реализуем архитектурный бин - то инжектить контекст имеем право. Из двух методов интерфейса нам понадобится только один. Выполнять наполнение полей будем до выполнения PostConstruct, тк метод, аннотированный PostConstruct считается инициализирующим, работющим тогда, когда все бины были подключены к классу. Метод получения мапы класса обертки против сервиса: applicationContext.getBeansOfType(IWrapperService.class) - получаем все бины из контекста, которые реализуют IWrapperService. t.getClass().getAnnotation(WrapperService.class).wrapperModel() - получаем класс-обертку для сервиса. Методы инжекта в метод и в переменную: Вначале получаем все методы/поля класса и фильтруем их по наличию аннотации @InjectWrapperServiceMap. Для методов вызываем выполнение метода, передавая в него мапу method.invoke(bean, getWrapperServiceMap()); Для поля обязательно устанавливаем доступность для того, чтобы могли в поле что-либо записывать и производим запись field.set(bean, getWrapperServiceMap()); В конфигурационном файле настройки бинов создаем новый бин: К бину ResponseWrapperAdvice необходимо добавить @DependsOn(value = "responseWrapperBeanPostProcessor") для того, чтобы бин конфигурировался после создания бина BPP. Для работы @DependsOn необходимо над классом конфигурации поставить аннотацию @ComponentScan("ru.emilnasyrov.lib.response.wrapper") Теперь аннотацию @InjectWrapperServiceMap для инжекта мапы сервисов можем использовать как в внутри нашего стартера, так и снаружи. В ResponseWrapperAdvice заинжектим мапу: Используем ее в методе generateResponseWrapper следующим образом: В коде остается нерешенный момент с использованием одного сервиса для разных оберток, что предлагаю, при необходимости, реализовать вам самим. Мне кажется, это довольно редкий кейс и нет необходимости его рассматривать в рамках данной статьи. А также инжект мапы через конструктор, который, думаю, я рассмотрю в последующих статьях. Вот и все. Остается только протестировать наш стартер. Код проекта с реализацией BeanPostProcessor: GitHub Добавим следующие классы в демо проект: WrapperServiceImpl остался из прошлой статьи с небольшими аналогичными изменениями Controller остался таким же, как и в прошлой статье В данной статье на примере стартера мы рассмотрели использование BeanPostProcessor и то, какие вещи с его помощью можно делать. Не всегда spring может дать нам то, чего мы хотим, но часто, если нас что-то не устраивает, то есть возможность дополнить spring. Ссылка на полный код проекта: GitHub Java Back-end