Injizieren von Prototyp-Bohnen in eine Singleton-Instanz im Frühjahr

1. Übersicht

In diesem kurzen Artikel werden verschiedene Ansätze zum Injizieren von Prototyp-Beans in eine Singleton-Instanz gezeigt . Wir werden die Anwendungsfälle und die Vor- und Nachteile jedes Szenarios diskutieren.

Standardmäßig sind Spring Beans Singletons. Das Problem tritt auf, wenn wir versuchen, Bohnen mit unterschiedlichen Anwendungsbereichen zu verdrahten. Zum Beispiel eine Prototyp-Bean in einen Singleton. Dies ist als das Problem der Bohneninjektion mit Umfang bekannt .

Um mehr über Bean Scopes zu erfahren, ist dieser Artikel ein guter Anfang.

2. Prototyp Bean Injection Problem

Um das Problem zu beschreiben, konfigurieren wir die folgenden Beans:

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

Beachten Sie, dass die erste Bean einen Prototypbereich hat, die andere ein Singleton.

Lassen Sie uns nun die Bean mit Prototypenbereich in den Singleton injizieren - und dann über die Methode getPrototypeBean () verfügbar machen :

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; } }

Laden Sie dann den ApplicationContext und holen Sie sich die Singleton-Bean zweimal:

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"); }

Hier ist die Ausgabe von der Konsole:

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

Beide Beans wurden beim Start des Anwendungskontexts nur einmal initialisiert .

3. Injizieren von ApplicationContext

Wir können den ApplicationContext auch direkt in eine Bean einfügen.

Verwenden Sie dazu entweder die Annotation @Autowire oder implementieren Sie die ApplicationContextAware- Schnittstelle:

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; } }

Jedes Mal, wenn die Methode getPrototypeBean () aufgerufen wird, wird eine neue Instanz von PrototypeBean aus dem ApplicationContext zurückgegeben .

Dieser Ansatz hat jedoch schwerwiegende Nachteile. Dies widerspricht dem Prinzip der Umkehrung der Kontrolle, da wir die Abhängigkeiten direkt vom Container anfordern.

Außerdem rufen wir die Prototyp-Bean aus dem applicationContext in der SingletonAppcontextBean- Klasse ab. Dies bedeutet, dass der Code an das Spring Framework gekoppelt wird .

4. Methodeninjektion

Eine andere Möglichkeit, das Problem zu lösen, ist die Methodeninjektion mit der Annotation @Lookup :

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

Spring überschreibt die mit @Lookup kommentierte Methode getPrototypeBean () . Anschließend wird die Bean im Anwendungskontext registriert. Immer wenn wir die Methode getPrototypeBean () anfordern , wird eine neue PrototypeBean- Instanz zurückgegeben.

Es wird CGLIB verwenden, um den Bytecode zu generieren, der für das Abrufen der PrototypeBean aus dem Anwendungskontext verantwortlich ist.

5. javax.inject API

Das Setup sowie die erforderlichen Abhängigkeiten werden in diesem Artikel zur Federverdrahtung beschrieben.

Hier ist die Singleton Bean:

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

Wir verwenden die Provider- Schnittstelle , um die Prototyp-Bean zu injizieren. Für jeden Methodenaufruf getPrototypeInstance () den myPrototypeBeanProvider. Die Methode g et () gibt eine neue Instanz von PrototypeBean zurück .

6. Proxy mit Gültigkeitsbereich

Standardmäßig enthält Spring einen Verweis auf das reale Objekt, um die Injektion durchzuführen. Hier erstellen wir ein Proxy-Objekt, um das reale Objekt mit dem abhängigen zu verbinden.

Bei jedem Aufruf der Methode für das Proxy-Objekt entscheidet der Proxy selbst, ob eine neue Instanz des realen Objekts erstellt oder die vorhandene wiederverwendet werden soll.

Um dies einzurichten, ändern wir die Appconfig- Klasse, um eine neue @ Scope- Annotation hinzuzufügen :

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

Standardmäßig verwendet Spring die CGLIB-Bibliothek, um die Objekte direkt zu unterklassifizieren. Um die Verwendung von CGLIB zu vermeiden, können wir den Proxy-Modus mit ScopedProxyMode konfigurieren. SCHNITTSTELLEN, um stattdessen den dynamischen JDK-Proxy zu verwenden.

7. ObjectFactory- Schnittstelle

Spring bietet die ObjectFactory-Schnittstelle zum Erstellen von On-Demand-Objekten des angegebenen Typs:

public class SingletonObjectFactoryBean { @Autowired private ObjectFactory prototypeBeanObjectFactory; public PrototypeBean getPrototypeInstance() { return prototypeBeanObjectFactory.getObject(); } }

Werfen wir einen Blick auf die Methode getPrototypeInstance () . getObject () gibt für jede Anforderung eine brandneue Instanz von PrototypeBean zurück. Hier haben wir mehr Kontrolle über die Initialisierung des Prototyps.

Außerdem ist die ObjectFactory Teil des Frameworks. Dies bedeutet, dass zusätzliche Einstellungen vermieden werden müssen, um diese Option zu verwenden.

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); }

Nach erfolgreichem Start des Tests können wir feststellen, dass jedes Mal, wenn die Methode getPrototypeInstance () aufgerufen wird, eine neue Prototyp-Bean-Instanz erstellt wird.

10. Schlussfolgerung

In diesem kurzen Tutorial haben wir verschiedene Möglichkeiten kennengelernt, um die Prototyp-Bean in die Singleton-Instanz zu injizieren.

Wie immer finden Sie den vollständigen Code für dieses Tutorial im GitHub-Projekt.