Domanda

Ho fatto una domanda generale di primavera: Fagioli autunnali di lancio automatico e ho risposto più persone che la chiamata del ApplicationContext.getBean () di Spring dovrebbe essere evitata il più possibile. Perché?

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

Sto usando Spring in un'applicazione non web e avevo pianificato di accedere a un oggetto ApplicationContext condiviso come descritto da LiorH .

Modifica

Accetto la risposta di seguito, ma ecco una versione alternativa di Martin Fowler che discute i meriti della dipendenza Iniezione vs. utilizzo di un Service Locator (che è essenzialmente lo stesso di una chiamata a ApplicationContext.getBean () ).

In parte, afferma Fowler, " Con il localizzatore di servizi la classe applicativa lo richiede [il servizio] esplicitamente tramite un messaggio al localizzatore. Con l'iniezione non c'è una richiesta esplicita, il servizio appare nella classe dell'applicazione - da qui l'inversione del controllo. L'inversione del controllo è una caratteristica comune dei framework, ma è qualcosa che ha un prezzo. Tende a essere difficile da capire e porta a 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 è una brutta cosa, solo che penso che debba giustificarsi rispetto all'alternativa più semplice. "

È stato utile?

Soluzione

L'ho menzionato in un commento sull'altra domanda, ma l'idea generale di Inversion of Control è di avere nessuna delle tue classi a sapere o preoccuparsi di come ottengono gli oggetti da cui dipendono . Ciò semplifica la modifica del tipo di implementazione di una determinata dipendenza che si utilizza in qualsiasi momento. Inoltre semplifica il test delle classi, in quanto è possibile fornire implementazioni fittizie di dipendenze. Infine, rende le classi più semplici e più focalizzate sulla loro responsabilità principale.

Chiamare ApplicationContext.getBean () non è Inversion of Control! Mentre è ancora facile cambiare l'implementazione è configurata per il nome del bean dato, la classe ora si affida direttamente a Spring per fornire quella dipendenza e non può ottenerla in altro modo. Non puoi semplicemente realizzare la tua finta implementazione in una classe di test e passarla a te stesso. Questo sostanzialmente vanifica lo scopo di Spring come contenitore di 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:

<bean id="myClass" class="MyClass">...</bean>

<bean id="myOtherClass" class="MyOtherClass">
   <property name="myClass" ref="myClass"/>
</bean>

Spring quindi inserirà automaticamente myClass in myOtherClass .

Dichiara tutto in questo modo e alla radice tutto ha qualcosa del tipo:

<bean id="myApplication" class="MyApplication">
   <property name="myCentralClass" ref="myCentralClass"/>
   <property name="myOtherCentralClass" ref="myOtherCentralClass"/>
</bean>

MyApplication è la classe più centrale e dipende almeno indirettamente da ogni altro servizio nel programma. Durante l'avvio, nel tuo metodo main , puoi chiamare applicationContext.getBean (" myApplication ") ma non dovresti aver bisogno di chiamare getBean () altrove!

Altri suggerimenti

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

  1. Service Locator è molto, molto più facile per gli altri seguire il tuo codice. L'IoC è "magico", ma i programmatori di manutenzione devono comprendere le configurazioni contorte della Primavera e tutte le innumerevoli posizioni per capire come cablare i tuoi oggetti.

  2. IoC è terribile per i problemi di configurazione del debug. In alcune classi di applicazioni l'applicazione non si avvierà se configurata in modo errato e potresti non avere la possibilità di passare attraverso ciò che sta accadendo 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 se non conoscono tutti i tag magici definiti da Spring. Non è abbastanza buono per conoscere più Java. Ciò ostacola i programmatori meno esperti (vale a dire che in realtà è una progettazione 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 ne vale la pena.

  5. Spesso viene utilizzata la molla nel caso in cui "tu voglia cambiare l'implementazione in un secondo momento". Esistono altri modi per raggiungere questo obiettivo senza la complessità di Spring IoC.

  6. Per le applicazioni Web (Java EE WARs) il contesto Spring è effettivamente associato al momento della compilazione (a meno che non si desideri che gli operatori si aggrovinino al contesto nella guerra esplosa). È possibile fare in modo che Spring utilizzi i file delle proprietà, ma con i file delle proprietà dei servlet sarà necessario trovarsi in una posizione predeterminata, il che significa che non è possibile distribuire più servlet contemporaneamente nella stessa casella. È possibile utilizzare Spring con JNDI per modificare le proprietà all'avvio del servlet, ma se si utilizza JNDI per parametri modificabili dall'amministratore, la necessità di Spring stessa diminuisce (poiché JNDI è effettivamente un Service Locator).

  7. Con Spring puoi perdere il controllo del programma se Spring sta inviando i tuoi metodi. Questo è conveniente e funziona per molti tipi di applicazioni, ma non per tutti. Potrebbe essere necessario controllare il flusso del programma quando è necessario creare attività (thread, ecc.) Durante l'inizializzazione o se sono necessarie risorse modificabili di cui Spring non era a conoscenza quando il contenuto era associato a WAR.

La primavera è ottima per la gestione delle transazioni e presenta alcuni vantaggi. È solo che l'IoC può essere eccessivamente ingegneristico in molte situazioni e introdurre complessità ingiustificata per i manutentori. Non utilizzare automaticamente l'IoC senza pensare a come non utilizzarlo prima.

È vero che includere la classe in application-context.xml evita la necessità di usare getBean. Tuttavia, anche questo non è effettivamente necessario. Se stai scrivendo un'applicazione autonoma e NON vuoi includere la tua classe di driver in application-context.xml, puoi usare il codice seguente per fare in modo che Spring autowire 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 dovuto farlo un paio di volte quando ho una sorta di classe standalone che deve usare alcuni aspetti della mia app (ad esempio per i test) ma non voglio includerli nel contesto dell'applicazione perché in realtà non fa 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 collegare i tuoi oggetti insieme. La testa di Zeus si spalanca e le tue classi appaiono, 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 ottenendo. Meno codice è quasi sempre migliore. Se la tua classe aveva davvero bisogno di un bean ClassINeed, perché non l'hai semplicemente collegato?

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

La motivazione è scrivere codice che non dipenda esplicitamente da Spring. In questo modo, se si sceglie di cambiare contenitore, non è necessario riscrivere alcun codice.

Pensa al contenitore come qualcosa di invisibile al tuo codice, che fornisce magicamente le sue esigenze, senza che ti venga chiesto.

L'iniezione di dipendenza è un contrappunto al "localizzatore di servizio" modello. Se stai cercando le dipendenze per nome, potresti anche sbarazzarti del contenitore DI e usare qualcosa come JNDI.

L'uso di @Autowired o ApplicationContext.getBean () è davvero la stessa cosa. In entrambi i modi si ottiene il bean configurato nel proprio contesto e in entrambi i modi il codice dipende dalla primavera. 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 è di fare affidamento sull'iniezione delle dipendenze ( inversione del controllo o IoC) . Cioè, i componenti sono configurati con i componenti di cui hanno bisogno. Queste dipendenze sono iniettate (tramite il costruttore o i setter) - non puoi ottenerti da solo.

ApplicationContext.getBean () richiede di nominare un bean in modo esplicito all'interno del componente. Invece, usando IoC, la tua configurazione può determinare quale componente verrà utilizzato.

Ciò ti consente di ricollegare facilmente la tua applicazione con diverse implementazioni di componenti o di configurare oggetti per i test in modo semplice fornendo varianti derise (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 offrirò solo un commento aggiuntivo: non è che non dovresti MAI farlo, ma piuttosto che lo faccia il meno possibile.

Di solito questo significa che viene fatto esattamente una volta: durante l'avvio. E poi è solo per accedere a " root " bean, attraverso il quale è possibile risolvere altre dipendenze. Questo può essere un codice riutilizzabile, come il servlet di base (se si sviluppano app Web).

Una delle premesse di Spring è evitare accoppiamento . Definisci e usa Interfacce, DI, AOP ed evita di usare ApplicationContext.getBean () :-)

Uno dei motivi è la testabilità. Supponi di avere questa classe:

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 bean? Per 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, vero?

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

È ancora possibile fare lo stesso quando si utilizza ApplicationContext. Tuttavia, devi deridere ApplicationContext che è un'interfaccia enorme. O hai bisogno di un'implementazione fittizia o puoi usare un framework beffardo 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 concorderebbe 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"));
    }
}

Il test richiede enormi sforzi o il bean tenterà di connettersi allo stackoverflow su ogni test. E non appena si verifica un errore di rete (o gli amministratori di StackOverflow ti bloccano a causa di un tasso di accesso eccessivo), i test falliranno casualmente.

Quindi, in conclusione, non direi che l'uso diretto del ApplicationContext sia automaticamente errato e dovrebbe essere evitato a tutti i costi. Tuttavia, se esistono opzioni migliori (e nella maggior parte dei casi), utilizzare le opzioni migliori.

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

Altri hanno menzionato l'uso di getBean () in main () per recuperare " main " bean per un programma autonomo.

Un altro uso che ho fatto di getBean () è in situazioni in cui una configurazione utente interattiva determina la composizione del bean per una particolare situazione. In modo che, ad esempio, parte del sistema di avvio esegua il ciclo attraverso una tabella del database utilizzando getBean () con una definizione del bean scope = 'prototype' e quindi impostando proprietà aggiuntive. Presumibilmente, esiste un'interfaccia utente che regola la tabella del database che sarebbe più amichevole del tentativo di (ri) scrivere l'XML del contesto dell'applicazione.

C'è un'altra volta che ha senso usare getBean. Se stai riconfigurando un sistema già esistente, in cui le dipendenze non vengono esplicitamente richiamate nei file di contesto di primavera. È possibile avviare il processo effettuando chiamate a getBean, in modo da non dover cablare tutto in una volta. In questo modo puoi costruire lentamente la tua configurazione a molla mettendo ogni pezzo in posizione nel tempo e facendo allineare correttamente i pezzi. Le chiamate a getBean verranno eventualmente sostituite, ma quando comprendi la struttura del codice o ne manchi, puoi avviare il processo di cablaggio di sempre più bean e di utilizzare sempre meno chiamate per getBean.

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top