Как использовать аннотацию @Lookup

Обычно бины в приложении 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.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *