Инжектиране на прототип на зърна в единичен екземпляр през пролетта

1. Общ преглед

В тази бърза статия ще покажем различни подходи за инжектиране на прототипни зърна в единичен екземпляр . Ще обсъдим случаите на използване и предимствата / недостатъците на всеки сценарий.

По подразбиране пролетните зърна са единични. Проблемът възниква, когато се опитваме да свържем фасул с различен обхват. Например, прототип на боб в единичен. Това е известно като проблем с инжектирането на боб с обхват .

За да научите повече за обхвата на зърната, това писане е добро начало.

2. Проблем с инжектиране на прототип на боб

За да опишем проблема, нека конфигурираме следните компоненти:

@Configuration public class AppConfig { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public PrototypeBean prototypeBean() { return new PrototypeBean(); } @Bean public SingletonBean singletonBean() { return new SingletonBean(); } }

Забележете, че първият боб има прототип, другият е единичен.

Сега, нека инжектираме боб с обхват от прототип в сингълтона - и след това изложим ако чрез метода 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 () бъде извикан, нов екземпляр на PrototypeBean ще се връща от ApplicationContext .

Този подход обаче има сериозни недостатъци. Това противоречи на принципа на обръщане на контрола, тъй като директно изискваме зависимостите от контейнера.

Също така извличаме прототипния боб от applicationContext в класа SingletonAppcontextBean . Това означава свързване на кода с Spring Framework .

4. Метод за инжектиране

Друг начин за решаване на проблема е инжектирането на метод с анотацията @Lookup :

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

Spring ще замени метода getPrototypeBean () , анотиран с @Lookup. След това регистрира боб в контекста на приложението. Винаги, когато поискаме метода getPrototypeBean () , той връща нов екземпляр PrototypeBean .

Той ще използва CGLIB, за да генерира байт код, отговорен за извличането на PrototypeBean от контекста на приложението.

5. API на javax.inject

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

Ето единичния боб:

public class SingletonProviderBean { @Autowired private Provider myPrototypeBeanProvider; public PrototypeBean getPrototypeInstance() { return myPrototypeBeanProvider.get(); } }

Използваме интерфейс на доставчика, за да инжектираме прототипа на боб. За всяко извикване на метода getPrototypeInstance () , myPrototypeBeanProvider. Методът 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(); } }

Нека да разгледаме метода getPrototypeInstance () ; getObject () връща чисто нов екземпляр на PrototypeBean за всяка заявка. Тук имаме повече контрол върху инициализирането на прототипа.

Също така ObjectFactory е част от рамката; това означава да се избягва допълнителна настройка, за да се използва тази опция.

8. Create a Bean at Runtime Using java.util.Function

Another option is to create the prototype bean instances at runtime, which also allows us to add parameters to the instances.

To see an example of this, let's add a name field to our PrototypeBean class:

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

Next, we'll inject a bean factory into our singleton bean by making use of the java.util.Function interface:

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

Finally, we have to define the factory bean, prototype and singleton beans in our configuration:

@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. Testing

Let's now write a simple JUnit test to exercise the case with 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); }

След успешно стартиране на теста можем да видим, че всеки път, когато методът getPrototypeInstance () е извикан, се създава нов прототип на екземпляр на боб.

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

В този кратък урок научихме няколко начина за инжектиране на прототипния боб в единичния екземпляр.

Както винаги, пълният код за този урок може да бъде намерен в проекта GitHub.