2011-01-30 10 views
10

Ho un set di classi con uno schema di inizializzazione complesso. Fondamentalmente, comincio con l'interfaccia di cui ho bisogno per ottenere una sospensione, e poi faccio un sacco di chiamate, e finisco con un oggetto che implementa quell'interfaccia.Spring @Autowiring con bean generici di fabbrica

Per gestire questo, ho creato una classe di fabbrica che può, data un'interfaccia, produrre l'oggetto finale. Ho trasformato questa fabbrica in un bean e in XML ho specificato i miei vari bean di servizio come istanziati tramite questo oggetto factory con un parametro dell'interfaccia che implementeranno.

Questo funziona benissimo e ottengo esattamente esattamente i chicchi di cui ho bisogno. Sfortunatamente, mi piacerebbe accedervi dalle mie classi controller, che vengono scoperte tramite la scansione dei componenti. Io uso @Autowired qui, e sembra che Spring non abbia idea di che tipo di oggetto siano, e dal momento che @Autowired funziona per tipo, io sono SOL.

Utilizzare @Resource (name = "beanName") qui funzionerebbe perfettamente, tuttavia sembra strano usare @Resource per alcuni bean e @Autowired per altri.

C'è un modo per far sapere a Spring quale interfaccia verrà creata dalla fabbrica per ciascuno di questi bean senza avere un metodo factory diverso per ogni tipo?

Sto usando Spring 2.5.6, a proposito, altrimenti avrei solo JavaConfig il tutto e non pensarci più.

classe di fabbrica:

<T extends Client> T buildService(Class<T> clientClass) { 
    //Do lots of stuff with client class and return an object of clientClass. 
} 

contesto app:

<bean id="serviceFactoryBean" class="com.captainAwesomePants.FancyFactory" /> 
<bean id="userService" factory-bean="serviceFactoryBean" factory-method="buildService"> 
    <constructor-arg value="com.captain.services.UserServiceInterface" /> 
</bean> 
<bean id="scoreService" factory-bean="serviceFactoryBean" factory-method="buildService"> 
    <constructor-arg value="com.captain.services.ScoreServiceInterface" /> 
</bean> 

mio controller:

public class HomepageController { 

    //This doesn't work 
    @Autowired @Qualifier("userService") UserServiceInterface userService; 

    //This does 
    @Resource(name="scoreService") ScoreServiceInterface scoreService; 
} 

risposta

8

vi consiglio di dare un ulteriore passo avanti e implement your factories as Spring FactoryBean classes il modello di fabbrica. L'interfaccia FactoryBean ha un metodo getObjectType() che contiene chiamate per scoprire il tipo restituito dalla fabbrica. Questo dà al tuo autowiring qualcosa in cui mettere i denti, purché la tua fabbrica ritorni un valore ragionevole.

+0

Mi piace. All'inizio mi stavo allontanando da FactoryBeans, ma onestamente sembra che faranno esattamente quello che voglio che facciano. –

+0

@CaptainAwesomePants: tendo a mantenere le mie implementazioni 'FactoryBean' il più sottili possibile, facendole delegare a un'altra classe che non usa l'API Spring. Si potrebbe fare lo stesso, delegare semplicemente al proprio 'serviceFactoryBean' esistente. – skaffman

+0

@skaffman: Quindi in questo caso ci dovrebbero essere altrettante implementazioni di 'FactoryBean' come numero di variazioni dei tipi di ritorno. Will Spring eseguirà il rilevamento del tipo corretto se vengono aggiunti due metodi aggiuntivi alla fabbrica 'UserServiceInterface buildUserService() {return buildService (UserServiceInterface.class); } 'e' ScoreServiceInterface buildScoreService() {return buildService (ScoreServiceInterface.class); } '(e il contesto viene ri-sintonizzato per usarli)? –

3

Dovreste essere in grado di raggiungere questo obiettivo utilizzando:

<bean id="myCreatedObjectBean" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> 
    <property name="targetClass"> 
     <value>com.mycompany.MyFactoryClass</value> 
    </property> 
    <property name="targetMethod"> 
     <value>myFactoryMethod</value> 
    </property> 
</bean> 

Quindi è possibile utilizzare sia @Resource o @Autowired + @Qualifier per iniettare nel vostro oggetto direttamente.

5

Avevo un problema simile, ma per me volevo utilizzare un singolo factory per creare implementazioni mocked-out delle mie dipendenze con cablaggio automatico utilizzando JMockit (il framework di test che sono obbligato a utilizzare).

Dopo aver trovato una soluzione soddisfacente all'interwebs, ho riunito una soluzione semplice che funziona davvero bene per me.

La mia soluzione utilizza anche una molla FactoryBean, ma utilizza solo un singolo bean di fabbrica per creare tutti i miei bean (che il richiedente originale sembra aver voluto fare).

La mia soluzione era quella di implementare una fabbrica di fabbriche di metarete che servissero fino a FactoryBean involucri intorno alla vera fabbrica singola.

Ecco la Java per la mia JMockit finto fabbrica di fagioli:

public class MockBeanFactory<C> implements FactoryBean<C> { 

    private Class<C> mockBeanType; 
    protected MockBeanFactory(){} 

    protected <C> C create(Class<C> mockClass) { 
     return Mockit.newEmptyProxy(mockClass); 
    } 

    @Override 
    public C getObject() throws Exception { 
     return create(mockBeanType); 
    } 

    @Override 
    public Class<C> getObjectType() { 
     return mockBeanType; 
    } 

    @Override 
    public boolean isSingleton() { 
     return true; 
    } 

    public static class MetaFactory { 
     public <C> MockBeanFactory<C> createFactory(Class<C> mockBeanType) { 
      MockBeanFactory<C> factory = new MockBeanFactory<C>(); 
      factory.mockBeanType = mockBeanType; 
      return factory; 
     } 
    } 
} 

E poi nel file di contesto Primavera XML, appena può semplicemente creare il meta fabbrica che crea le specifiche fabbriche di fagioli-tipo:

<bean id="metaFactory" class="com.stackoverflow.MockBeanFactory$MetaFactory"/> 

<bean factory-bean="metaFactory" factory-method="createFactory"> 
    <constructor-arg name="mockBeanType" value="com.stackoverflow.YourService"/> 
</bean> 

per fare questo lavoro per la situazione del richiedente originale, potrebbe essere ottimizzato per rendere la FactoryBeans in involucri/adattatore per la serviceFactoryBean:

public class FancyFactoryAdapter<C> implements FactoryBean<C> { 

    private Class<C> clientClass; 
    private FancyFactory serviceFactoryBean; 

    protected FancyFactoryAdapter(){} 

    @Override 
    public C getObject() throws Exception { 
     return serviceFactoryBean.buildService(clientClass); 
    } 

    @Override 
    public Class<C> getObjectType() { 
     return clientClass; 
    } 

    @Override 
    public boolean isSingleton() { 
     return true; 
    } 

    public static class MetaFactory { 

     @Autowired FancyFactory serviceFactoryBean; 

     public <C> FancyFactoryAdapter<C> createFactory(Class<C> clientClass) { 
      FancyFactoryAdapter<C> factory = new FancyFactoryAdapter<C>(); 
      factory.clientClass = clientClass; 
      factory.serviceFactoryBean = serviceFactoryBean; 
      return factory; 
     } 
    } 
} 

Poi nel XML (notare la userServiceFactory id e l'id userService fagioli sono necessarie solo a lavorare con il @Qualifier annotazione):

<bean id="metaFactory" class="com.stackoverflow.FancyFactoryAdapter$MetaFactory"/> 

<bean id="userServiceFactory" factory-bean="metaFactory" factory-method="createFactory"> 
    <constructor-arg name="clientClass" value="com.captain.services.UserServiceInterface"/> 
</bean> 

<bean id="userService" factory-bean="userServiceFactory"/> 

<bean id="scoreServiceFactory" factory-bean="metaFactory" factory-method="createFactory"> 
    <constructor-arg name="clientClass" value="com.captain.services.ScoreServiceInterface"/> 
</bean> 

<bean id="scoreService" factory-bean="scoreServiceFactory"/> 

E questo è tutto, solo un po 'di classe Java e uno smidge di la configurazione della piastra di caldaia e la tua fabbrica di fagioli personalizzata possono creare tutti i tuoi bean e risolverli correttamente con Spring.

+2

Sto inviando questo principalmente perché questo è Java e quindi un oggetto FactoryFactory è sempre corretto! –

Problemi correlati