Iniezione di prototipi di bean in un'istanza Singleton in primavera

1. Panoramica

In questo rapido articolo, mostreremo diversi approcci per l' iniezione di bean prototipo in un'istanza singleton . Discuteremo i casi d'uso e i vantaggi / svantaggi di ogni scenario.

Per impostazione predefinita, i fagioli di primavera sono singoli. Il problema sorge quando proviamo a collegare bean di scopi diversi. Ad esempio, un prototipo di bean in un singleton. Questo è noto come problema di iniezione di bean con ambito .

Per saperne di più sugli ambiti dei bean, questo articolo è un buon punto di partenza.

2. Problema di iniezione di fagioli prototipo

Per descrivere il problema, configuriamo i seguenti bean:

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

Si noti che il primo bean ha uno scope prototipo, l'altro è un singleton.

Ora, iniettiamo il bean con ambito prototipo nel singleton e quindi esponiamo if tramite il metodo 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; } }

Quindi, cariciamo ApplicationContext e otteniamo il bean singleton due volte:

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

Ecco l'output dalla console:

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

Entrambi i bean sono stati inizializzati solo una volta, all'avvio del contesto dell'applicazione.

3. Inserimento di ApplicationContext

Possiamo anche iniettare ApplicationContext direttamente in un bean.

Per ottenere ciò, utilizzare l' annotazione @Autowire o implementare l' interfaccia 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; } }

Ogni volta che viene chiamato il metodo getPrototypeBean () , verrà restituita una nuova istanza di PrototypeBean da ApplicationContext .

Tuttavia, questo approccio presenta gravi svantaggi. È in contraddizione con il principio di inversione del controllo, poiché richiediamo direttamente le dipendenze dal contenitore.

Inoltre, recuperiamo il prototipo di bean da applicationContext all'interno della classe SingletonAppcontextBean . Ciò significa accoppiare il codice a Spring Framework .

4. Metodo di iniezione

Un altro modo per risolvere il problema è l' inserimento del metodo con l' annotazione @Lookup :

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

Spring sovrascriverà il metodo getPrototypeBean () annotato con @Lookup. Quindi registra il bean nel contesto dell'applicazione. Ogni volta che viene richiesto il metodo getPrototypeBean () , restituisce una nuova istanza di PrototypeBean .

Utilizzerà CGLIB per generare il bytecode responsabile del recupero di PrototypeBean dal contesto dell'applicazione.

5. API javax.inject

La configurazione e le dipendenze richieste sono descritte in questo articolo sul cablaggio di Spring.

Ecco il fagiolo singleton:

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

Usiamo l' interfaccia Provider per iniettare il prototipo di bean. Per ogni chiamata al metodo getPrototypeInstance () , myPrototypeBeanProvider. Il metodo g et () restituisce una nuova istanza di PrototypeBean .

6. Proxy con ambito

Per impostazione predefinita, Spring contiene un riferimento all'oggetto reale per eseguire l'iniezione. Qui creiamo un oggetto proxy per collegare l'oggetto reale con quello dipendente.

Ogni volta che viene chiamato il metodo sull'oggetto proxy, il proxy decide se creare una nuova istanza dell'oggetto reale o riutilizzare quella esistente.

Per configurarlo, modifichiamo la classe Appconfig per aggiungere una nuova annotazione @Scope :

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

Per impostazione predefinita, Spring utilizza la libreria CGLIB per sottoclassare direttamente gli oggetti. Per evitare l'utilizzo di CGLIB, possiamo configurare la modalità proxy con ScopedProxyMode. INTERFACCE, per utilizzare invece il proxy dinamico JDK.

7. Interfaccia ObjectFactory

Spring fornisce l'interfaccia ObjectFactory per produrre oggetti su richiesta del tipo specificato:

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

Diamo un'occhiata al metodo getPrototypeInstance () ; getObject () restituisce una nuova istanza di PrototypeBean per ogni richiesta. Qui abbiamo un maggiore controllo sull'inizializzazione del prototipo.

Inoltre, ObjectFactory è una parte del framework; questo significa evitare configurazioni aggiuntive per utilizzare questa opzione.

8. Creare un bean in runtime utilizzando java.util.Function

Un'altra opzione è creare le istanze del prototipo del bean in fase di esecuzione, il che ci consente anche di aggiungere parametri alle istanze.

Per vedere un esempio di ciò, aggiungiamo un campo nome alla nostra classe PrototypeBean :

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

Successivamente, inietteremo una fabbrica di fagioli nel nostro bean singleton utilizzando l' interfaccia java.util.Function :

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

Infine, dobbiamo definire il bean di fabbrica, il prototipo e il bean singleton nella nostra configurazione:

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

Scriviamo ora un semplice test JUnit per esercitare il caso con l' interfaccia 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); }

Dopo aver avviato con successo il test, possiamo vedere che ogni volta che il metodo getPrototypeInstance () viene chiamato, viene creata una nuova istanza di bean prototipo.

10. Conclusione

In questo breve tutorial, abbiamo imparato diversi modi per iniettare il bean prototipo nell'istanza singleton.

Come sempre, il codice completo per questo tutorial può essere trovato sul progetto GitHub.