Обычно бины в приложении 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.
Отличная статья! Все очень понятно (что редкость)! Спасибо.