1. Обзор
В этой быстрой статье мы собираемся показать различные подходы внедрения прототипов bean-компонентов в одноэлементный экземпляр . Мы обсудим варианты использования и преимущества/недостатки каждого сценария.
По умолчанию бобы Spring являются синглетонами. Проблема возникает, когда мы пытаемся соединить бобы разных областей. Например, прототип bean в синглтоне. Это известно как проблема с инъекцией в боб .
Чтобы узнать больше о сферах применения бобов, перейдите по ссылке:/spring-bean-scopes[эта статья - хорошее место для начала].
2. Проблема внедрения боба-прототипа
Чтобы описать проблему, давайте настроим следующие компоненты:
@Configuration
public class AppConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE__PROTOTYPE)
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
@Bean
public SingletonBean singletonBean() {
return new SingletonBean();
}
}
Обратите внимание, что у первого компонента есть область действия прототипа, а у другого - синглтон.
Теперь давайте вставим bean-объект с прототипом в синглтон, а затем выставим 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;
}
}
Каждый раз, когда вызывается метод getPrototypeBean () , из ApplicationContext будет возвращаться новый экземпляр PrototypeBean .
-
Однако у этого подхода есть серьезные недостатки. ** Он противоречит принципу инверсии управления, поскольку мы запрашиваем зависимости непосредственно из контейнера.
Кроме того, мы получаем прототип bean-компонента из applicationContext в классе SingletonAppcontextBean . Это означает связывание кода с Spring Framework .
4. Метод инъекции
Другим способом решения проблемы является внедрение метода с аннотацией @ Lookup :
@Component
public class SingletonLookupBean {
@Lookup
public PrototypeBean getPrototypeBean() {
return null;
}
}
Spring переопределит метод getPrototypeBean () , аннотированный @Lookup. Затем он зарегистрирует компонент в контексте приложения.
Всякий раз, когда мы запрашиваем метод getPrototypeBean () , он возвращает новый экземпляр PrototypeBean .
-
Он будет использовать CGLIB для генерации байт-кода ** , ответственного за выбор PrototypeBean из контекста приложения.
5. javax.inject API
Настройка вместе с необходимыми зависимостями описана в этой ссылке:/spring-annotations-resource-inject-autowire[Spring wiring]article.
Вот синглтон бин:
public class SingletonProviderBean {
@Autowired
private Provider<PrototypeBean> myPrototypeBeanProvider;
public PrototypeBean getPrototypeInstance() {
return myPrototypeBeanProvider.get();
}
}
Мы используем Provider interface для внедрения прототипа bean-компонента. Для каждого вызова метода getPrototypeInstance () метод _myPrototypeBeanProvider . g et () возвращает новый экземпляр PrototypeBean_ .
6. Scoped Proxy
По умолчанию Spring содержит ссылку на реальный объект для выполнения инъекции. Здесь мы создаем прокси-объект, чтобы связать реальный объект с зависимым.
Каждый раз, когда вызывается метод прокси-объекта, прокси сам решает, создавать ли новый экземпляр реального объекта или повторно использовать существующий.
Чтобы настроить это, мы изменим класс Appconfig , добавив новую аннотацию @ Scope :
@Scope(
value = ConfigurableBeanFactory.SCOPE__PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET__CLASS)
По умолчанию Spring использует библиотеку CGLIB для непосредственного подкласса объектов.
Чтобы избежать использования CGLIB, мы можем настроить режим прокси с _ScopedProxyMode . _ INTERFACES, чтобы использовать вместо него динамический прокси JDK.
7. ObjectFactory Interface
Spring предоставляет интерфейс ObjectFactory <T> для создания объектов по требованию данного типа:
public class SingletonObjectFactoryBean {
@Autowired
private ObjectFactory<PrototypeBean> prototypeBeanObjectFactory;
public PrototypeBean getPrototypeInstance() {
return prototypeBeanObjectFactory.getObject();
}
}
Давайте посмотрим на метод getPrototypeInstance () ; getObject () возвращает новый экземпляр PrototypeBean для каждого запроса. Здесь у нас больше контроля над инициализацией прототипа.
Кроме того, ObjectFactory является частью структуры; это означает, что нужно избегать дополнительной настройки, чтобы использовать эту опцию.
8. Создайте Бин во время выполнения, используя java.util.Function
Другой вариант - создать экземпляры прототипа bean-компонента во время выполнения, что также позволяет нам добавлять параметры к экземплярам.
Чтобы увидеть пример этого, давайте добавим поле имени в наш класс PrototypeBean :
public class PrototypeBean {
private String name;
public PrototypeBean(String name) {
this.name = name;
logger.info("Prototype instance " + name + " created");
}
//...
}
Затем мы добавим фабрику bean-компонентов в наш синглтон-компонент, используя интерфейс java.util.Function :
public class SingletonFunctionBean {
@Autowired
private Function<String, PrototypeBean> beanFactory;
public PrototypeBean getPrototypeInstance(String name) {
PrototypeBean bean = beanFactory.apply(name);
return bean;
}
}
Наконец, мы должны определить фабричный bean-компонент, прототип и синглтон-бины в нашей конфигурации:
@Configuration
public class AppConfig {
@Bean
public Function<String, PrototypeBean> 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 :
@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);
}
После успешного запуска теста мы видим, что каждый раз, когда вызывается метод getPrototypeInstance () , создается новый экземпляр компонента-прототипа.
10. Заключение
В этом коротком уроке мы узнали несколько способов внедрить прототип bean в экземпляр singleton
Как всегда, полный код этого руководства можно найти по адресу GitHub project .