Perché il valore ApplicationContext.getBean di Spring è considerato negativo?

Ho fatto una domanda generale di Spring: lanciare automaticamente Spring Beans e rispondere a più persone che chiamano Spring’s ApplicationContext.getBean() dovrebbero essere evitate il più ansible. Perché?

In quale altro modo dovrei accedere ai bean che ho configurato Spring per creare?

Sto usando Spring in un’applicazione non web e ho programmato di accedere a un object ApplicationContext condiviso come descritto da LiorH .

Emendamento

Accetto la risposta di seguito, ma ecco un esempio alternativo di Martin Fowler che discute i vantaggi di Dependency Injection rispetto all’utilizzo di un Localizzatore di servizi (che è essenzialmente uguale a chiamare un ApplicationContext.getBean() ).

In parte, Fowler afferma: ” Con il localizzatore di servizi la class applicativa chiede esplicitamente [il servizio] tramite un messaggio al locatore: con l’iniezione non c’è una richiesta esplicita, il servizio appare nella class dell’applicazione – da qui l’inversione del controllo. L’inversione di controllo è una caratteristica comune dei framework, ma è qualcosa che ha un prezzo, tende a essere difficile da capire e causa problemi quando si tenta di eseguire il debug. Quindi, nel complesso, preferisco evitarlo [Inversion of Control ] a meno che non ne abbia bisogno. Questo non vuol dire che sia una cosa negativa, solo che penso che debba giustificarsi sull’alternativa più diretta.

Ho menzionato questo in un commento sull’altra domanda, ma l’idea di Inversion of Control è di non sapere a nessuno dei tuoi corsi o di preoccuparsi di come ottengono gli oggetti da cui dipendono . In questo modo è facile cambiare il tipo di implementazione di una determinata dipendenza che si usa in qualsiasi momento. Rende anche le classi facili da testare, dato che puoi fornire finte implementazioni di dipendenze. Infine, rende le classi più semplici e focalizzate sulla loro responsabilità principale.

Chiamare ApplicationContext.getBean() non è Inversion of Control! Mentre è ancora facile cambiare quale implementazione è configurata per il nome del bean, la class ora si affida direttamente a Spring per fornire tale dipendenza e non può ottenerla in altro modo. Non puoi semplicemente creare la tua simulazione di implementazione in una class di test e trasmetterla a te stesso. Ciò sostanzialmente sconfigge lo scopo di Spring come contenitore per l’iniezione di dipendenza.

Ovunque tu voglia dire:

 MyClass myClass = applicationContext.getBean("myClass"); 

dovresti invece, ad esempio, dichiarare un metodo:

 public void setMyClass(MyClass myClass) { this.myClass = myClass; } 

E poi nella tua configurazione:

 ...    

Spring automaticamente inietterà myClass in myOtherClass .

Dichiarare tutto in questo modo e alla base di tutto c’è qualcosa di simile:

     

MyApplication è la class più centrale e dipende, almeno indirettamente, da ogni altro servizio nel tuo programma. Quando esegui il bootstrap, nel tuo metodo main , puoi chiamare applicationContext.getBean("myApplication") ma non dovresti chiamare getBean() da nessun’altra parte!

I motivi per preferire Service Locator su Inversion of Control (IoC) sono:

  1. Service Locator è molto, molto più semplice da seguire per le altre persone nel tuo codice. IoC è “magico”, ma i programmatori di manutenzione devono capire le configurazioni a molla contorte e tutte le miriadi di posizioni per capire come cablare i tuoi oggetti.

  2. IoC è terribile per il debug dei problemi di configurazione. In alcune classi di applicazioni, l’applicazione non si avvia se configurata in modo errato e potresti non avere la possibilità di esaminare cosa sta succedendo con un debugger.

  3. IoC è principalmente basato su XML (le annotazioni migliorano le cose ma c’è ancora molto XML là fuori). Ciò significa che gli sviluppatori non possono lavorare sul tuo programma a meno che non conoscano tutti i tag magici definiti da Spring. Non è abbastanza buono sapere più Java. Ciò ostacola i programmatori meno esperti (ad esempio, è in realtà un progetto scadente utilizzare una soluzione più complicata quando una soluzione più semplice, come Service Locator, soddisferà gli stessi requisiti). Inoltre, il supporto per la diagnosi dei problemi XML è molto più debole del supporto per i problemi Java.

  4. L’iniezione di dipendenza è più adatta a programmi più grandi. Il più delle volte la complessità aggiuntiva non vale la pena.

  5. Spesso Spring viene utilizzato nel caso in cui “si desideri modificare l’implementazione in un secondo momento”. Ci sono altri modi per raggiungere questo risultato senza la complessità di IoC di spring.

  6. Per le applicazioni web (Java EE WARs) il contesto Spring è effettivamente vincolato al momento della compilazione (a meno che non si desideri che gli operatori si estendano intorno al contesto nella guerra esplosa). È ansible rendere i file di proprietà Spring use, ma con i file delle proprietà dei servlet sarà necessario trovarsi in una posizione predeterminata, il che significa che non è ansible distribuire più servlet della stessa ora sulla stessa casella. È ansible utilizzare Spring con JNDI per modificare le proprietà al momento dell’avvio del servlet, ma se si utilizza JNDI per i parametri modificabili dall’amministratore, la necessità di Spring stesso diminuisce (poiché JNDI è effettivamente un Localizzatore di servizi).

  7. Con Spring puoi perdere il controllo del programma se Spring invia i tuoi metodi. Questo è conveniente e funziona per molti tipi di applicazioni, ma non per tutte. Potrebbe essere necessario controllare il stream del programma quando è necessario creare attività (thread, ecc.) Durante l’inizializzazione o necessitare di risorse modificabili di cui Spring non era a conoscenza quando il contenuto era associato alla GUERRA.

Spring è molto utile per la gestione delle transazioni e presenta alcuni vantaggi. È solo che IoC può essere eccessivamente ingegneristico in molte situazioni e introdurre una complessità ingiustificata per i maintainer. Non utilizzare IoC automaticamente senza pensare a come non utilizzarlo per primo.

È vero che includere la class in application-context.xml evita la necessità di usare getBean. Tuttavia, anche questo è in realtà non necessario. Se si sta scrivendo un’applicazione standalone e NON si desidera includere la class del driver in application-context.xml, è ansible utilizzare il codice seguente per far sì che Spring mantenga le dipendenze del driver:

 public class AutowireThisDriver { private MySpringBean mySpringBean; public static void main(String[] args) { AutowireThisDriver atd = new AutowireThisDriver(); //get instance ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( "/WEB-INF/applicationContext.xml"); //get Spring context //the magic: auto-wire the instance with all its dependencies: ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); // code that uses mySpringBean ... mySpringBean.doStuff() // no need to instantiate - thanks to Spring } public void setMySpringBean(MySpringBean bean) { this.mySpringBean = bean; } } 

Ho avuto bisogno di farlo un paio di volte quando ho una sorta di class standalone che ha bisogno di usare alcuni aspetti della mia app (ad esempio per i test) ma non voglio includerla nel contesto dell’applicazione perché non è in realtà parte dell’app. Nota anche che questo evita la necessità di cercare il bean usando un nome String, che ho sempre pensato fosse brutto.

Uno dei vantaggi più interessanti dell’utilizzo di qualcosa come Spring è che non devi colbind i tuoi oggetti insieme. La testa di Zeus si spalanca e appaiono le tue classi, completamente formate con tutte le loro dipendenze create e cablate, se necessario. È magico e fantastico.

Più dici ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed"); , meno magia stai ricevendo. Meno codice è quasi sempre meglio. Se la tua class aveva davvero bisogno di un bean ClassINeed, perché non lo hai appena inserito?

Detto questo, qualcosa ha ovviamente bisogno di creare il primo object. Non c’è niente di sbagliato nel tuo metodo principale di acquisire un bean o due tramite getBean (), ma dovresti evitarlo perché ogni volta che lo stai usando, non stai davvero utilizzando tutta la magia di Spring.

La motivazione è scrivere un codice che non dipenda esplicitamente da Spring. In questo modo, se scegli di cambiare contenitore, non devi riscrivere alcun codice.

Pensa al contenitore in quanto qualcosa è invisibile al tuo codice, fornendo magicamente i suoi bisogni, senza che venga richiesto.

L’iniezione di dipendenza è un contrappunto al modello “localizzatore di servizi”. Se si cercano le dipendenze per nome, si potrebbe anche eliminare il contenitore DI e utilizzare qualcosa come JNDI.

Usare @Autowired o ApplicationContext.getBean() è davvero la stessa cosa. In entrambi i modi ottieni il bean configurato nel tuo contesto e in entrambi i modi il tuo codice dipende dalla spring. L’unica cosa che dovresti evitare è creare un’istanza di ApplicationContext. Fallo solo una volta! In altre parole, una linea come

 ApplicationContext context = new ClassPathXmlApplicationContext("AppContext.xml"); 

dovrebbe essere usato una sola volta nella tua applicazione.

L’idea è che tu ti basi sull’iniezione di dipendenza ( inversione di controllo o IoC). Cioè, i componenti sono configurati con i componenti di cui hanno bisogno. Queste dipendenze vengono iniettate (tramite il costruttore o setter) – non ottieni te stesso.

ApplicationContext.getBean() richiede di denominare un bean esplicitamente all’interno del componente. Invece, utilizzando IoC, la tua configurazione può determinare quale componente verrà utilizzato.

Questo ti permette di ricablare facilmente la tua applicazione con diverse implementazioni di componenti, o configurare oggetti per test in modo semplice fornendo varianti simulate (ad esempio un DAO deriso in modo da non colpire un database durante il test)

Altri hanno indicato il problema generale (e sono risposte valide), ma mi limiterò a offrire un commento aggiuntivo: non è che non dovresti MAI farlo, ma piuttosto farlo il meno ansible.

Di solito questo significa che è fatto esattamente una volta: durante il bootstrap. E poi è solo per accedere al bean “root”, attraverso il quale altre dipendenze possono essere risolti. Questo può essere codice riutilizzabile, come il servlet di base (se si sviluppano applicazioni web).

Ho trovato solo due situazioni in cui è richiesto getBean ():

Altri hanno menzionato l’uso di getBean () in main () per recuperare il bean “main” per un programma standalone.

Un altro uso che ho fatto di getBean () si trova in situazioni in cui una configurazione utente intertriggers determina il trucco del bean per una particolare situazione. Così, ad esempio, parte del sistema di avvio scorre attraverso una tabella di database usando getBean () con una definizione di scope = ‘prototipo’ e quindi impostando proprietà aggiuntive. Presumibilmente, c’è un’interfaccia utente che regola la tabella del database che sarebbe più amichevole del tentativo di (ri) scrivere l’XML del contesto dell’applicazione.

Uno dei locali di spring è evitare l’ accoppiamento . Definisci e usa Interfacce, DI, AOP ed evita di usare ApplicationContext.getBean () 🙂

tuttavia, ci sono ancora casi in cui è necessario il modello di localizzazione del servizio. per esempio, ho un bean controller, questo controller potrebbe avere alcuni bean di servizio predefiniti, che possono essere inseriti in dipendenza dalla configurazione. mentre potrebbero esserci anche molti servizi aggiuntivi o nuovi che questo controller può richiamare ora o più tardi, che quindi necessitano del localizzatore di servizi per recuperare i bean di servizio.

C’è un altro momento in cui l’uso di getBean ha senso. Se stai riconfigurando un sistema già esistente, in cui le dipendenze non vengono esplicitamente richiamate nei file di contesto di spring. È ansible avviare il processo inserendo chiamate su getBean, in modo da non dover colbind tutto in una volta. In questo modo puoi build lentamente la tua configurazione a molla inserendo ogni pezzo nel tempo e inserendo correttamente i bit. Le chiamate a getBean verranno infine sostituite, ma come si capisce la struttura del codice, o in mancanza di esso, è ansible avviare il processo di cablaggio di un numero sempre maggiore di bean e utilizzando sempre meno chiamate to getBean.

Uno dei motivi è la testabilità. Dì che hai questa class:

 interface HttpLoader { String load(String url); } interface StringOutput { void print(String txt); } @Component class MyBean { @Autowired MyBean(HttpLoader loader, StringOutput out) { out.print(loader.load("http://stackoverflow.com")); } } 

Come puoi testare questo fagiolo? Ad esempio in questo modo:

 class MyBeanTest { public void creatingMyBean_writesStackoverflowPageToOutput() { // setup String stackOverflowHtml = "dummy"; StringBuilder result = new StringBuilder(); // execution new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append); // evaluation assertEquals(result.toString(), stackOverflowHtml); } } 

Facile, giusto?

Mentre dipendi ancora da Spring (a causa delle annotazioni) puoi rimuovere la dipendenza da spring senza modificare alcun codice (solo le definizioni di annotazione) e lo sviluppatore di test non ha bisogno di sapere nulla su come funziona la molla (forse dovrebbe comunque, ma permette di rivedere e testare il codice separatamente da ciò che fa la molla).

È ancora ansible fare lo stesso quando si utilizza ApplicationContext. Tuttavia, è necessario simulare ApplicationContext che è una grande interfaccia. O hai bisogno di un’implementazione fittizia o puoi usare un sistema di derisione come Mockito:

 @Component class MyBean { @Autowired MyBean(ApplicationContext context) { HttpLoader loader = context.getBean(HttpLoader.class); StringOutput out = context.getBean(StringOutput.class); out.print(loader.load("http://stackoverflow.com")); } } class MyBeanTest { public void creatingMyBean_writesStackoverflowPageToOutput() { // setup String stackOverflowHtml = "dummy"; StringBuilder result = new StringBuilder(); ApplicationContext context = Mockito.mock(ApplicationContext.class); Mockito.when(context.getBean(HttpLoader.class)) .thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get); Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append); // execution new MyBean(context); // evaluation assertEquals(result.toString(), stackOverflowHtml); } } 

Questa è una possibilità, ma penso che la maggior parte delle persone sarebbe d’accordo sul fatto che la prima opzione è più elegante e semplifica il test.

L’unica opzione che è davvero un problema è questa:

 @Component class MyBean { @Autowired MyBean(StringOutput out) { out.print(new HttpLoader().load("http://stackoverflow.com")); } } 

Provare questo richiede enormi sforzi o il tuo bean sta per tentare di connettersi allo stackoverflow su ogni test. E non appena si verifica un errore di rete (o gli amministratori dello stackoverflow ti bloccano a causa dell’eccessivo tasso di accesso), si verificheranno test atipici casuali.

Quindi, come conclusione, non direi che l’uso diretto di ApplicationContext è automaticamente sbagliato e dovrebbe essere evitato a tutti i costi. Tuttavia se ci sono opzioni migliori (e ci sono nella maggior parte dei casi), quindi utilizzare le opzioni migliori.