Рубрики
Без рубрики

Инъекция прототипных бобов в одноэлементный экземпляр весной

Узнайте, как внедрить компоненты прототипа в экземпляр компонента с одноэлементной областью.

Автор оригинала: baeldung.

1. Обзор

В этой краткой статье мы покажем различные подходы к введению компонентов прототипа в одноэлементный экземпляр . Мы обсудим варианты использования и преимущества/недостатки каждого сценария.

По умолчанию весенние бобы являются синглетами. Проблема возникает, когда мы пытаемся связать бобы разных областей. Например, прототип боба в синглтон. Это известно как проблема инъекции бобов с областью действия .

Чтобы узнать больше о областях применения bean, эта статья-хорошее место для начала .

2. Проблема инъекции Прототипа Боба

Чтобы описать проблему, давайте настроим следующие компоненты:

@Configuration
public class AppConfig {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public PrototypeBean prototypeBean() {
        return new PrototypeBean();
    }

    @Bean
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }
}

Обратите внимание, что первый компонент имеет область прототипа, а другой – одноэлементный.

Теперь давайте введем компонент с областью действия прототипа в синглтон, а затем выставим if с помощью метода getPrototypeBean() :

public class SingletonBean {

    // ..

    @Autowired
    private PrototypeBean prototypeBean;

    public SingletonBean() {
        logger.info("Singleton instance created");
    }

    public PrototypeBean getPrototypeBean() {
        logger.info(String.valueOf(LocalTime.now()));
        return prototypeBean;
    }
}

Затем давайте загрузим ApplicationContext и получим одноэлементный компонент дважды:

public static void main(String[] args) throws InterruptedException {
    AnnotationConfigApplicationContext context 
      = new AnnotationConfigApplicationContext(AppConfig.class);
    
    SingletonBean firstSingleton = context.getBean(SingletonBean.class);
    PrototypeBean firstPrototype = firstSingleton.getPrototypeBean();
    
    // get singleton bean instance one more time
    SingletonBean secondSingleton = context.getBean(SingletonBean.class);
    PrototypeBean secondPrototype = secondSingleton.getPrototypeBean();

    isTrue(firstPrototype.equals(secondPrototype), "The same instance should be returned");
}

Вот вывод с консоли:

Singleton Bean created
Prototype Bean created
11:06:57.894
// should create another prototype bean instance here
11:06:58.895

Оба компонента были инициализированы только один раз, при запуске контекста приложения.

3. Инъекция ApplicationContext

Мы также можем ввести ApplicationContext непосредственно в компонент.

Для достижения этой цели либо используйте @Autowire аннотацию или реализовать ApplicationContextAware интерфейс:

public class SingletonAppContextBean implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public PrototypeBean getPrototypeBean() {
        return applicationContext.getBean(PrototypeBean.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) 
      throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Каждый раз, когда вызывается метод get Prototype Bean () , из ApplicationContext возвращается новый экземпляр Prototype Bean .

Однако такой подход имеет серьезные недостатки. Это противоречит принципу инверсии управления, поскольку мы запрашиваем зависимости непосредственно из контейнера.

Кроме того, мы извлекаем компонент-прототип из ApplicationContext в классе SingletonAppcontextBean . Это означает связывание кода с каркасом Spring .

4. Способ Инъекции

Другим способом решения этой проблемы является инъекция метода с помощью @Lookup аннотации :

@Component
public class SingletonLookupBean {

    @Lookup
    public PrototypeBean getPrototypeBean() {
        return null;
    }
}

Spring переопределит метод get Prototype Bean() с аннотацией @Lookup. Затем он регистрирует компонент в контексте приложения. Всякий раз, когда мы запрашиваем метод get Prototype Bean () , он возвращает новый экземпляр Prototype Bean .

Он будет использовать CGLIB для генерации байт-кода , ответственного за извлечение компонента Prototype из applicationcontext.

5. javax.inject API

Настройка вместе с необходимыми зависимостями описана в этой статье о весенней проводке.

Вот однолетний боб:

public class SingletonProviderBean {

    @Autowired
    private Provider myPrototypeBeanProvider;

    public PrototypeBean getPrototypeInstance() {
        return myPrototypeBeanProvider.get();
    }
}

Мы используем Provider |/interface для внедрения компонента прототипа. Для каждого вызова метода get Prototype Instance() поставщик компонентов my Prototype. g et() метод возвращает новый экземпляр PrototypeBean .

6. Прокси-сервер с областью действия

По умолчанию Spring содержит ссылку на реальный объект для выполнения инъекции. Здесь мы создаем прокси-объект, чтобы связать реальный объект с зависимым.

Каждый раз, когда вызывается метод прокси-объекта, прокси сам решает, создавать ли новый экземпляр реального объекта или повторно использовать существующий.

Чтобы настроить это, мы изменяем класс Appconfig , чтобы добавить новую аннотацию @Scope :

@Scope(
  value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
  proxyMode = ScopedProxyMode.TARGET_CLASS)

По умолчанию Spring использует библиотеку CGLIB для прямого подкласса объектов. Чтобы избежать использования CGLIB, мы можем настроить режим прокси с помощью ScopedProxyMode. ИНТЕРФЕЙСЫ, чтобы вместо этого использовать динамический прокси-сервер JDK.

7. Интерфейс ObjectFactory

Spring предоставляет интерфейс ObjectFactory для создания по требованию объектов данного типа:

public class SingletonObjectFactoryBean {

    @Autowired
    private ObjectFactory prototypeBeanObjectFactory;

    public PrototypeBean getPrototypeInstance() {
        return prototypeBeanObjectFactory.getObject();
    }
}

Давайте посмотрим на get Prototype Instance() метод; GetObject() возвращает совершенно новый экземпляр Prototype Bean для каждого запроса. Здесь у нас больше контроля над инициализацией прототипа.

Кроме того, ObjectFactory является частью фреймворка; это означает, что следует избегать дополнительной настройки для использования этой опции.

8. Создайте компонент во время выполнения с помощью java.util.Функция

Другой вариант-создать экземпляры прототипа компонента во время выполнения, что также позволяет нам добавлять параметры к экземплярам.

Чтобы увидеть пример этого, давайте добавим поле имени в наш класс Prototype Bean :

public class PrototypeBean {
    private String name;
    
    public PrototypeBean(String name) {
        this.name = name;
        logger.info("Prototype instance " + name + " created");
    }

    //...   
}

Затем мы введем фабрику бобов в наш одноэлементный боб, используя файл java.util.Функция интерфейс:

public class SingletonFunctionBean {
    
    @Autowired
    private Function beanFactory;
    
    public PrototypeBean getPrototypeInstance(String name) {
        PrototypeBean bean = beanFactory.apply(name);
        return bean;
    }

}

Наконец, мы должны определить компоненты factorybean, prototype и singleton в нашей конфигурации:

@Configuration
public class AppConfig {
    @Bean
    public Function beanFactory() {
        return name -> prototypeBeanWithParam(name);
    } 

    @Bean
    @Scope(value = "prototype")
    public PrototypeBean prototypeBeanWithParam(String name) {
       return new PrototypeBean(name);
    }
    
    @Bean
    public SingletonFunctionBean singletonFunctionBean() {
        return new SingletonFunctionBean();
    }
    //...
}

9. Тестирование

Давайте теперь напишем простой тест JUnit, чтобы проверить случай с ObjectFactory interface:

@Test
public void givenPrototypeInjection_WhenObjectFactory_ThenNewInstanceReturn() {

    AbstractApplicationContext context
     = new AnnotationConfigApplicationContext(AppConfig.class);

    SingletonObjectFactoryBean firstContext
     = context.getBean(SingletonObjectFactoryBean.class);
    SingletonObjectFactoryBean secondContext
     = context.getBean(SingletonObjectFactoryBean.class);

    PrototypeBean firstInstance = firstContext.getPrototypeInstance();
    PrototypeBean secondInstance = secondContext.getPrototypeInstance();

    assertTrue("New instance expected", firstInstance != secondInstance);
}

После успешного запуска теста мы видим, что каждый раз, когда вызывается метод get Prototype Instance () , создается новый экземпляр компонента-прототипа.

10. Заключение

В этом коротком уроке мы узнали несколько способов внедрения компонента-прототипа в одноэлементный экземпляр.

Как всегда, полный код этого учебника можно найти на GitHub project .