что такое бин в спринге
Вышел релиз фреймворка Spring 3.0
Помимо этих основных изменений есть сотни улучшений в мелочах, которые вы, вероятно, оцените, когад будете апгрейдиться с Spring 2.5. Смотрите ченджлог и явадоки.
re: Спринг знает и умеет определять что создать первым а что потом.
> Чушь полная. Спринг знает и умеет определять что создать первым а что потом. А уже если ты попутал с циклическими ссылками то ты ССЗБ. Более того
Таки умеет? Удивительно.
ОК, допустим я не знаю спринг 🙂 Не соблаговолит ли уважаемый человек с 3мя годами опыта в нем, глянуть на вот это:
—
public class C <
public C(C other) <>
public C() <>
public void setOther(C other) <>
>
—
ApplicationContext context = new ClassPathXmlApplicationContext(«explosion.xml»);
context.getBean(«c1»);
—
У меня оно пишет на Spring 3.0.0 что «Requested bean is currently in creation».
Но весело работает, если сначала дергаю c2:
ApplicationContext context = new ClassPathXmlApplicationContext(«explosion.xml»);
context.getBean(«c2»); // вот оно, лекарство
context.getBean(«c1»);
аналогов aop:proxy ни в одном контейнере не существует! Благодаря чему в том же самом JSF многие вещи делаются через Ж. и все бины или искуственно держатся
Я не знаю, что за изврат такой этот aop:proxy и каким вообще боком тут AOP. В данном случае Spring не умеет делаеть элементарные вещи. Задача на автоматическое определение правильной последовательности инициализации решается даже школьниками без проблем, это не rocket science.
Я не желаю писать в конфигах «required». Контракт кода должен быть в коде, а не в каком-то конфиге, что служит для связки компонентов. Так что пусть мануалы к этой убогой кривоте курят другие.
Re: почему столько злобы?
> А что до любителей спринга, не знающих даже сколько бит в байте.
есть у меня одна альфочка. попробуйте угадать, сколько там бит в байте?)
>Таки умеет? Удивительно.
ОК, допустим я не знаю спринг 🙂 Не соблаговолит ли уважаемый человек с 3мя годами опыта в нем, глянуть на вот это:
Быдлокодерство. Но для даунов повторюсь:
А уже если ты попутал с циклическими ссылками то ты ССЗБ.
Я не знаю, что за изврат такой этот aop:proxy и каким вообще боком тут AOP. В данном случае Spring не умеет делаеть элементарные вещи. Задача на автоматическое определение правильной последовательности инициализации решается даже школьниками без проблем, это не rocket science.
Ликбез в соседней палате. Элементарные вещи спринг делает по всему миру милионы раз в сутки. Но найдется десяток человек с особо ровными руками для которых использовать спринг просто не судьба потому что даже элементарные вещи у них не работают.
Я не желаю писать в конфигах «required». Контракт кода должен быть в коде, а не в каком-то конфиге, что служит для связки компонентов. Так что пусть мануалы к этой убогой кривоте курят другие.
Ладно так уж и быть. контракт в коде: Устраивает?
public void afterPropertiesSet() throws Exception <
Spring Boot + BeanPostProcessor или как обернуть ответ контроллеров часть 2
Введение
Вспоминаем, что готово на данный момент
Предлагаю вспомнить, что мы сделали в прошлой статье. Код на GitHub на ветке master.
Немного про то, что уже было создано
Мы создали стартер, в котором присутствует 2 аннотации: @DisableResponseWrapper и @EnableResponseWrapper, а также 2 интерфейса: IWrapperModel и IWrapperService, используя которые мы можем обернуть все необходимые ответы контроллеров в новый класс.
Для реализации такого функционала, в стартере мы создали класс, реализующий интерфейс ResponseBodyAdvice и аннотированный с помощью @ControllerAdvice(annotations = EnableResponseWrapper.class)
Аннотация @ControllerAdvice(annotations = EnableResponseWrapper.class) указывает, что методы данного компонента будут использоваться сразу несколькими контроллерами. Также указываем, что наши методы будут обрабатывать только те контроллеры, которые помечены @EnableResponseWrapper.
Класс реализует интерфейс ResponseBodyAdvice<>, который позволяет настраивать ответ, после его возвращения методом @ResponseBody или контроллером ResponseEntity, но до того, как тело будет записано с помощью HttpMessageConverter.
В классе необходимо было реализовать 2 метода:
— метод обработки точки входа контроллера. Вернуть в методе необходимо объект, который будет возвращен api.
Этот механизм мы положили в стартер и написали демо-проект под данный функционал.
Представленный в статье механизм возможно использовать и для иных задач, связанных с обработкой ответов контроллеров. Подобный подход используется для обработки исключений в контроллерах. (Хорошая статья об обработки исключений https://habr.com/ru/post/528116/)
С помощью использования ControllerAdvice+ResponseBodyAdvice и аннотаций вы можете более гибко настроить обработку любых ответов контроллеров с использованием большого количества информации о методе, контроллере, запросе и ответе контроллера.
Внедрение коллекции
Мы хотим в результате получить возможность реализовывать для каждого класса-обертки свой сервис.
Создадим аннотацию для сервисов:
В качестве аргумента будем принимать модель-обертку для дальнейшего получения по ней сервиса из списка.
Небольшие доработки
Сразу сделаем небольшие доработки по проекту для того, чтобы стартер был более гибким.
Добавим generic-и. Интерфейс модели:
Создаем функции-хелперы setBodyHelper и setDataHelper для того, чтобы иметь возможность работать с Wildcard. Подробнее про helper-методы и зачем они нужны можно прочесть в официальной документации.
Аналогично делаем для интерфейса сервиса:
Также для того, чтобы производить обертку на основе данных о запросе, создадим класс данных.
Изменяем ResponseWrapperAdvice
Таким образом spring самостоятельно найдет и соберет в список все классы, реализующие IWrapperService с любыми Body и Data.
Напишем метод получения из списка того сервиса, который относится к конкретно нашему классу-обертке.
С помощью iWrapperService.getClass().getAnnotations() получаем все аннотации для каждого из сервисов и ищем среди них аннотацию @WrapperService. Из нее получаем класс-обертку, которую сравниваем с той, с которой работаем сейчас сами.
Данный подход вполне оптимальный, но не лучший. Во-первых, метод getWrapperService будет вызываться каждый раз при запросе, в чем нет необходимости. Это можно поправить кешированием, например.
Во-вторых, как мне кажется, куда логичнее инжектить не список сервисов и затем по нему искать нужный перебором, а сразу мапу, в которой будет класс-обертка против сервиса.
Полный код проекта с реализацией через внедрение коллекций: GitHub
Немного теории
В спринге присутствуют следующие этапы инициализации контекста, в каждый из которых, при желании, можно вклиниться самому:
Хорошая и подробная статья про этапы инициализации контекста.
Реализация задачи через BeanPostProcessor
Во-первых, для удобства, нам понадобится новая аннотация, которая будет обозначать место для инжекта нашей мапы:
Во-вторых, нам нужен сам класс, реализующий BeanPostProcessor:
Из двух методов интерфейса нам понадобится только один. Выполнять наполнение полей будем до выполнения PostConstruct, тк метод, аннотированный PostConstruct считается инициализирующим, работющим тогда, когда все бины были подключены к классу.
Метод получения мапы класса обертки против сервиса:
Методы инжекта в метод и в переменную:
Вначале получаем все методы/поля класса и фильтруем их по наличию аннотации @InjectWrapperServiceMap.
Для методов вызываем выполнение метода, передавая в него мапу method.invoke(bean, getWrapperServiceMap());
Для поля обязательно устанавливаем доступность для того, чтобы могли в поле что-либо записывать и производим запись field.set(bean, getWrapperServiceMap());
Полный код InjectWrapperServiceMapBeanPostProcessor
Регестрируем BeanPostProcessor
В конфигурационном файле настройки бинов создаем новый бин:
К бину ResponseWrapperAdvice необходимо добавить @DependsOn(value = «responseWrapperBeanPostProcessor») для того, чтобы бин конфигурировался после создания бина BPP.
Для работы @DependsOn необходимо над классом конфигурации поставить аннотацию @ComponentScan(«ru.emilnasyrov.lib.response.wrapper»)
Полный код ResponseWrapperAutoConfiguration
Теперь аннотацию @InjectWrapperServiceMap для инжекта мапы сервисов можем использовать как в внутри нашего стартера, так и снаружи.
Дополняем обработчик контроллеров
В ResponseWrapperAdvice заинжектим мапу:
Используем ее в методе generateResponseWrapper следующим образом:
Полный код ResponseWrapperAdvice
В коде остается нерешенный момент с использованием одного сервиса для разных оберток, что предлагаю, при необходимости, реализовать вам самим. Мне кажется, это довольно редкий кейс и нет необходимости его рассматривать в рамках данной статьи.
А также инжект мапы через конструктор, который, думаю, я рассмотрю в последующих статьях.
Вот и все. Остается только протестировать наш стартер.
Код проекта с реализацией BeanPostProcessor: GitHub
Добавим следующие классы в демо проект:
WrapperServiceImpl остался из прошлой статьи с небольшими аналогичными изменениями
Controller остался таким же, как и в прошлой статье
В данной статье на примере стартера мы рассмотрели использование BeanPostProcessor и то, какие вещи с его помощью можно делать.
Не всегда spring может дать нам то, чего мы хотим, но часто, если нас что-то не устраивает, то есть возможность дополнить spring.
Ссылка на полный код проекта: GitHub
Выбор бина для внедрения: @Qualifier, @Named, @Resource
В больших приложениях, основанных на Spring (или любом другом IoC фреймворке), может наступить такой день, когда при внедрении зависимостей образуется неоднозначность: одной зависимости удовлетворяет сразу несколько бинов, ведь выбор производится по совместимости типов внедряемого и запрашиваемого бинов. Что же тогда произойдёт?
Подготовка
Нам понадобится многомодульный пустой maven проект с Spring и JUnit:
В проекте создадим интерфейс и две его реализации:
Неоднозначные бины
Для начала я бы хотел посмотреть, что жё всё таки будет, если мы объявим конкурс «Два бина на одно место» 🙂 Поскольку Spring поддерживает несколько аннотаций для связывания зависимостей, я решил попробовать их все:
Код, который неоднозначен:
И тестовый класс к нему. Так как тесты отличаются только именем пакета и именем тестируемого класса, достаточно будет показать лишь один:
Результат исполнения немного предсказуем:
Неявный выбор бина по имени
Одно из самых простых решений, это намекнуть Spring’у, какой бин нам нужен, используя имена полей.
Каждый бин в Spring context имеет своё имя. Это име порождается либо из имени класса, либо явно задаётся в xml и grovvy конфигах, либо берётся из имени функции создания бина в java config.
Если мы назовём поле с неоднозначным типом бина по имени бина, Spring сможет самостоятельно сделать правильным выбор:
Неявное определение типа по имени работает со всеми аннотациями:
Явное указание бина по имени
Для тех, кто не любит ‘convention-over-configuration’, в Spring есть аннотация @Qualifier, позволяющая явно задать имя нужного бина:
Тесты остаются теми же самыми и точно так же проходят:
@Qualifier и задание бина по типу
Для использования этой аннотации вначале нужно определить собственную аннотацию, уникальную для каждого бина:
И применить эту аннотацию и к классу бина и к зависимости:
@Named как другой способ указания бина по имени
JSR-330 помимо нового @Qualifier принёс и аналог старого, который называется @Named и ведёт себя аналогичным образом:
@Resource и всё ещё указание бина по имени
Нужно…больше…бинов…
На случай, если нужны одновременно все бины заданного типа, например когда точное имя бина неизвестно, можно внедрить их в коллекцию:
Порядок внедрения бинов не определён. Внедрение коллекции бинов работает со всеми аннотациями.
Заключение и рекомендации
С другой стороны, выбор между специализацией бина по имени или по типу упирается в вопрос удобства и безопасности. Специализация по типу, с @Qualified из JSR-330 однозначно связывает требуемый бин с его реализацией, но требует достаточно много подготовительного кода. Специализация по имени не требует код вообще, но может привести к неожиданным результатам, если имя будет не то или не у того бина.
Код примера доступен на github. Тесты модуля fail специально оставлены проваливающимися.
Как вмешаться в частную жизнь Spring бина
В интернете полно картинок типа той, что слева, о жизненном цикле Spring бина и как им пользоваться. И почти к каждой такой картинке прилагается длинная статья, рассказывающая о этапах создания бина и как там всё устроено.
Мне кажется, что начинающему Spring разработчику гораздо полезнее знать, как пользоваться этими этапами создания, чем о их существовании.
Подготовка
Возьмём пустой maven проект с JUnit, Hamcrest, Spring и Grovvy для конфигурации контекста:
Создание бина
Начну с самого интересного: выполняем код после создания или перед уничтожением бина. Вообще с созданием может возникнуть непонимание, так как вроде бы есть конструктор, который и занимается созданием эксземпляра класса (=созданием бина) и вроде бы в нём и надо делать инициализацию, нет?
На самом деле нет. Конструктор будущего бина вызывается на ранних стадиях его жизненного пути, когда его зависимости ещё могут быть не установлены или не готовы. Для специализированного же метода инициализации гарантируется, что он будет вызван после того, как все зависимости бина готовы к употреблению.
Впрочем меньше слов, больше кода. Существует три возможности для указания метода инициализации бина, аннотация, реализация особого интерфейса, задание метода в конфигурации:
Для последнего случая никаких особенных дополнений в коде не требуется. В случае, если вы предпочитаете xml, а не groovy, задание init метода выглядит несколько иначе:
Результат во всех трёх случаях одинаков:
Уничтожение бина
Spring предоставляет возможность выполнения кода перед уничтожением бина. И, так же как и с finalize(), не гарантирует, что код будет вызван. Как и с инициализацией бина, есть три метода вызова кода при унчитожении бина:
Для последнего случая никаких особенных дополнений в коде не требуется. В случае, если вы предпочитаете xml, а не groovy, задание destroy метода выглядит несколько иначе:
В отличие от init() методов, с destroy() методами есть проблема с вызовом. Методы destroy() не вызываются автоматически при интеграционном тестировании с SpringJUnit4ClassRunner, они не вызываются автоматически, если контекст создаётся вручную. В последнем случае контекст надо и завершать тоже вручную:
Также destroy() методы не будут вызываны для prototype scope бинов.
Имя бина
Используя интерфейсы создания бина можно узнать, какое бину досталось имя:
*Aware интерфейсы
Spring определяет огромное количество *Aware интерфейсов, внедряющих в бины тот или иной объект. Большая часть этих объектов может быть так же внедрена и с использованием @Inject/@Autowired. Например вместо использования интерфейсов ApplicationContextAware и BeanFactoryAware можно просто написать:
Список *Aware интерфейсов весьма длинный и достаточно однообразный, поэтому вместо того, чтобы описывать каждый интерфейс с примером, я обойдусь сводным списком:
Код примера доступен на github.
Рекомендованное чтение
Spring в первую очередь известен как IoC контейнер, реализующий шаблон проектирования «Внедрение зависимостей». Не вдаваясь…
В примере Hello, Spring! контекст Spring создавался с использованием аннотаций, таких как @Service, и специального класса,…
Область основной боковой панели
Рекомендованное чтение
Spring в первую очередь известен как IoC контейнер, реализующий шаблон проектирования «Внедрение зависимостей». Не вдаваясь…
В примере Hello, Spring! контекст Spring создавался с использованием аннотаций, таких как @Service, и специального класса,…
Создание spring beans из обычных классов и юнит тесты
У нас и rich client, и сервер активно используют Spring. И очень быстро возникла проблема — как использовать спринг бины из обычных классов (которые сами — не бины).
Сначала возникли две идеи — передавать им нужные бины как аргументы в конструкторе или использовать какое то статическое поле для хранения Spring context.
Первая идея была признана порочной. Получается, что ныжные сервисы надо тянуть через длинную череду конструкторов.
Вторая идея тоже была признана порочной — возникает вопрос кто и когда будет инициализировать это поле и что будет происходить с юнит тестами.
Вскоре я нагуглил в интернетах такой красивый вариант:
И это работает отлично.
Однако же для юнит тестов пришлось это всё немного модифицировать.
У нас есть тесты, которые создают спринг контекст. Поэтому я добавил в этот класс такое метод:
Во вторых, если юнит тест не создаёт спринг котекст, но тестируемый класс использует StaticContextHolder, надо чтобы зависимости он получал из него.
Я сделал свой фиктивный контекст:
Теперь инициализация юнит теста выглядит так:
Теперь остаётся еще одна проблема: prototype beans, которые создаются через init method, например
Для этого заведём в FakeBeanfactory еще один Map:
и перепишем один метод:
и инициализируем этот Map статических методов его в инициализации юнит теста.
Буду рад, если кто то подскажет как лучше решать все эти проблемы.