Обычно бины в приложении Spring являтся синглтонами, и для внедрения зависимостей мы используем конструктор или сеттер.
Но бывает и другая ситуация: имеется бин Car — синглтон (singleton bean), и ему требуется каждый раз новый экземпляр бина Passenger. То есть Car – синглтон, а Passenger – так называемый прототипный бин (prototype bean). Жизненные циклы бинов разные. Бин Car создается контейнером только раз, а бин Passenger создается каждый раз новый – допустим, это происходит каждый раз при вызове какого-то метода бина Car. Вот здесь то и пригодится внедрение бина с помощью Lookup метода. Оно происходит не при инициализации контейнера, а позднее: каждый раз, когда вызывается метод.
Что такое Lookup method injection
Суть в том, что вы создаете метод-заглушку в бине Car и помечаете его специальным образом – аннотацией @Lookup. Этот метод должен возвращать бин Passenger, каждый раз новый. Контейнер Spring под капотом создаст подкласс и переопределит этот метод и будет вам выдавать новый экземпляр бина Passenger при каждом вызове аннотированного метода. Даже если в вашей заглушке он возвращает null (а так и надо делать, все равно этот метод будет переопределен).
Пример использования
Давайте рассмотрим пример. Создадим класс Car, синглтон, и сделаем его бином с помощью аннотации @Component:
@Component public class Car { @Lookup public Passenger createPassenger() { return null; }; public String drive(String name) { Passenger passenger = createPassenger(); passenger.setName(name); return "car with " + passenger.getName(); } }
Допустим, в бине есть метод drive(), и при каждом вызове метода drive() бину Car требуется новый экземпляр бина Passenger – сегодня пассажир Петя, завтра — Вася. То есть бин Passenger прототипный. Для получения этого бина надо написать метод-заглушку createPassenger() аннотировать его с помощью @Lookup:
@Lookup public Passenger createPassenger() { return null; };
Контейнер Spring переопределит этот метод-заглушку и будет выдавать при его вызове каждый раз новый экземпляр Passenger.
Осталось только определить бин Passenger как прототипный:
@Component @Scope("prototype") public class Passenger { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
И все, теперь при вызове метода drive() мы можем везти каждый раз нового пассажира. Имя его передается в аргументе метода drive(), и затем задается сеттером во вновь созданном экземпляре пассажира.
Убедимся, что все работает:
@Test public void givenAnnotationConfig_whenLookupMethodCalled_ThenNewPassengerCreated() { ApplicationContext javaConfigContext = new AnnotationConfigApplicationContext(LookupTest.class); Car car = javaConfigContext.getBean(Car.class); assertEquals(car.drive("John"), "car with John"); assertEquals(car.drive("Michel"), "car with Michel"); }
При каждом вызове метода drive() создается новый пассажир с новым именем, как и требуется.
XML-конфигурация.
То же самое можно сконфигурировать с помощью XML:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="passenger" class="ru.javalang.lookup.Passenger"> </bean> <bean class="ru.javalang.lookup.Car"> <lookup-method name="createPassenger" bean="passenger" /> </bean> </beans>
Проверим, что все работает:
@Test public void givenXmlConfig_whenLookupMethodCalled_ThenNewPassengerCreated() { ApplicationContext xmlConfigContext = new ClassPathXmlApplicationContext("lookup.xml"); Car car = xmlConfigContext.getBean(Car.class); assertEquals(car.drive("John"), "car with John"); assertEquals(car.drive("Michel"), "car with Michel"); }
Вывод
Внедрение зависимости с помощью Lookup метода в коде встречается нечасто, но это хороший способ внедрить прототипный бин в бин-синглтон.
Полный код примеров доступен на GitHub.
Отличная статья! Все очень понятно (что редкость)! Спасибо.