Prototyp-Beans im Frühjahr in eine Singleton-Instanz injizieren

1. Überblick

In diesem kurzen Artikel werden verschiedene Ansätze gezeigt, wie Prototyp-Beans in eine Singleton-Instanz injiziert werden . Wir besprechen die Anwendungsfälle und die Vor- und Nachteile jedes Szenarios.

Spring Beans sind standardmäßig Singletons. Das Problem entsteht, wenn wir versuchen, Bohnen verschiedener Bereiche zu verdrahten. Zum Beispiel ein Prototyp-Bean in ein Singleton. Dies ist bekannt als das - Problem der Bean-Injektion von Bohnen .

Um mehr über Bohnenscopes zu erfahren, verlinke:/spring-bean-scopes[diese Beschreibung ist ein guter Anfangspunkt].

2. Prototyp Bean Beanspruchung Problem

Um das Problem zu beschreiben, lassen Sie uns die folgenden Beans konfigurieren:

@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 das erste Bean einen Prototypbereich hat, der andere ist ein Singleton.

Lassen Sie uns nun die Bean des Prototyps in den Singleton einfügen - und dann mit der 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;
    }
}

Dann laden wir den ApplicationContext und holen uns zweimal die Singleton-Bean:

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. ApplicationContext einspritzen

Wir können den ApplicationContext auch direkt in eine Bean injizieren.

  • Um dies zu erreichen, verwenden Sie entweder die Annotation @ Autowire oder implementieren Sie die Schnittstelle 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;
    }
}

Bei jedem Aufruf der Methode getPrototypeBean () wird eine neue Instanz von PrototypeBean vom ApplicationContext zurückgegeben.

  • Dieser Ansatz hat jedoch gravierende Nachteile. ** Er widerspricht dem Prinzip der Umkehrung der Kontrolle, da wir die Abhängigkeiten direkt vom Container anfordern.

Außerdem holen wir die Prototyp-Bean aus der Klasse applicationContext innerhalb der Klasse SingletonAppcontextBean . Dies bedeutet Koppeln des Codes an das Spring Framework .

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 getPrototypeBean () -Methode, die mit @Lookup. kommentiert wurde. Sie registriert dann die Bean im Anwendungskontext.

Immer wenn wir die Methode getPrototypeBean () anfordern, wird eine neue Instanz von PrototypeBean zurückgegeben.

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

5. javax.inject API

Das Setup und die erforderlichen Abhängigkeiten werden in diesem Link beschrieben:/Spring-Annotations-Resource-Inject-Autowire

Hier ist die Singleton-Bean:

public class SingletonProviderBean {

    @Autowired
    private Provider<PrototypeBean> myPrototypeBeanProvider;

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

Wir verwenden Provider interface , um die Prototyp-Bean einzufügen. Für jeden getPrototypeInstance () -Methodenaufruf gibt die _myPrototypeBeanProvider . g et () -Methode eine neue Instanz von PrototypeBean_ zurück.

6. Bereichs-Proxy

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 Objekt zu verbinden.

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

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 zum direkten Unterklassen der Objekte.

Um die Verwendung von CGLIB zu vermeiden, können wir den Proxy-Modus mit _ScopedProxyMode . _ INTERFACES konfigurieren, um stattdessen den dynamischen JDK-Proxy zu verwenden.

7. ObjectFactory Schnittstelle

Spring stellt die Schnittstelle ObjectFactory <T> zur Verfügung, um bei Bedarf Objekte zu erzeugen des gegebenen Typs:

public class SingletonObjectFactoryBean {

    @Autowired
    private ObjectFactory<PrototypeBean> prototypeBeanObjectFactory;

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

Schauen wir uns die getPrototypeInstance () -Methode an. getObject () gibt für jede Anforderung eine brandneue Instanz von PrototypeBean zurück. Hier haben wir mehr Kontrolle über die Initialisierung des Prototyps.

Die ObjectFactory ist auch Teil des Frameworks; Dies bedeutet, dass keine zusätzlichen Einstellungen erforderlich sind, um diese Option zu verwenden.

8. Bean zur Laufzeit mit java.util.Function erstellen

Eine weitere Option ist das Erstellen der Prototyp-Bean-Instanzen zur Laufzeit, sodass wir den Instanzen außerdem Parameter hinzufügen können.

Um ein Beispiel dafür zu sehen, fügen wir unserer PrototypeBean -Klasse ein Namensfeld hinzu:

public class PrototypeBean {
    private String name;

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

   //...
}

Als nächstes injizieren wir eine Bean-Factory in unsere Singleton-Bean, indem wir die java.util.Function -Schnittstelle verwenden:

public class SingletonFunctionBean {

    @Autowired
    private Function<String, PrototypeBean> beanFactory;

    public PrototypeBean getPrototypeInstance(String name) {
        PrototypeBean bean = beanFactory.apply(name);
        return bean;
    }

}

Schließlich müssen wir die Factory-Bean, den Prototyp und die Singleton-Beans in unserer Konfiguration definieren:

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

Lassen Sie uns nun einen einfachen JUnit-Test schreiben, um den Fall mit der ObjectFactory -Schnittstelle auszuüben:

@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 dem erfolgreichen Starten des Tests können wir feststellen, dass bei jedem Aufruf der getPrototypeInstance () -Methode eine neue Prototyp-Bean-Instanz erstellt wird.

10. Fazit

In diesem kurzen Lernprogramm haben wir verschiedene Möglichkeiten zum Einfügen der Prototyp-Bean in die Singleton-Instanz gelernt.

Den vollständigen Code für dieses Lernprogramm finden Sie wie immer unter GitHub[ .