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 ProvidermyPrototypeBeanProvider; 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 ObjectFactoryprototypeBeanObjectFactory; 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 FunctionbeanFactory; public PrototypeBean getPrototypeInstance(String name) { PrototypeBean bean = beanFactory.apply(name); return bean; } }
Наконец, мы должны определить компоненты factorybean, prototype и singleton в нашей конфигурации:
@Configuration public class AppConfig { @Bean public FunctionbeanFactory() { 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 .