2011-01-30 13 views
10

Tengo un conjunto de clases con un complejo esquema de inicialización. Básicamente, comienzo con la interfaz que necesito controlar, y luego hago un montón de llamadas, y termino con un objeto que implementa esa interfaz.Spring @Autowiring con beans genéricos fabricados en fábrica

Con el fin de manejar esto, hice una clase de fábrica que puede, dada una interfaz, producir el objeto final. Hice esta fábrica en un frijol, y en XML especifiqué mis varios beans de servicio como instanciados a través de este objeto de fábrica con un parámetro de la interfaz que implementarán.

Esto funciona muy bien, y obtengo exactamente los granos que necesito. Desafortunadamente, me gustaría acceder a ellos desde mis clases de controlador, que se descubren a través del escaneo de componentes. Utilizo @Autowired aquí, y parece que Spring no tiene idea de qué tipo de objeto son estos, y como @Autowired funciona por tipo, soy SOL.

Usar @Resource (name = "beanName") aquí funcionaría perfectamente, sin embargo, parece extraño usar @Resource para algunos beans y @Autowired para otros.

¿Hay alguna manera de que Spring sepa qué interfaz creará la fábrica para cada uno de estos beans sin tener un método de fábrica diferente para cada tipo?

Estoy usando Spring 2.5.6, por cierto, de lo contrario, solo me gustaría JavaConfig todo y olvidarse de él.

clase de fábrica:

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

contexto de aplicaciones:

<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> 

mi controlador:

public class HomepageController { 

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

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

Respuesta

8

le sugiero que tome el patrón de la fábrica un paso más y implement your factories as Spring FactoryBean classes. La interfaz FactoryBean tiene un método getObjectType() que contiene llamadas para descubrir qué tipo devolverá la fábrica. Esto le da a su autocableado algo para que se esfuerce, siempre que su fábrica devuelva un valor razonable.

+0

Me gusta. Me estaba alejando de FactoryBeans al principio, pero sinceramente parece que harán exactamente lo que yo quiero que hagan. –

+0

@CaptainAwesomePants: tiendo a mantener mis implementaciones 'FactoryBean' tan delgadas como sea posible, haciéndolas delegar a otra clase que no usa Spring API. Usted podría hacer lo mismo: simplemente delegue en su 'serviceFactoryBean' existente. – skaffman

+0

@skaffman: Entonces, en este caso, debe haber tantas implementaciones 'FactoryBean' como número de variaciones de tipos de devolución. Will Spring realizará el descubrimiento de tipo correcto si se agregan dos métodos adicionales a la fábrica 'UserServiceInterface buildUserService() {return buildService (UserServiceInterface.class); } 'y' ScoreServiceInterface buildScoreService() {return buildService (ScoreServiceInterface.class); } '(y el contexto se vuelve a sintonizar para usarlos)? –

3

Usted debe ser capaz de lograr esto usando:

<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> 

A continuación, puede utilizar cualquiera @Resource o @Autowired + @Qualifier para inyectar en el objeto directamente.

5

Tuve un problema similar, pero para mí quería usar una sola fábrica para crear implementaciones falsificadas de mis dependencias de autoalambre usando JMockit (el marco de prueba que estoy obligado a usar).

Después de no encontrar una solución satisfactoria en las interwebs, armé una solución simple que funciona muy bien para mí.

Mi solución utiliza un resorte FactoryBean así, pero sólo se utiliza un solo grano de fábrica para crear todas mis judías (que el autor de la pregunta original parece haber querido hacer).

Mi solución fue implementar un meta-fábrica de fábrica de fábricas que sirve en marcha FactoryBean envolturas alrededor de la única fábrica real.

Aquí es el Java para mi JMockit simulacro de fábrica de beans:

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

Y luego en el archivo de contexto XML Spring, sólo puede crear simplemente la fábrica meta que crea las fábricas de tipo frijol específicos:

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

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

para hacer este trabajo para la situación del autor de la pregunta original, que podría ser ajustado para hacer que el FactoryBeans en envoltorios/adaptador para el 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; 
     } 
    } 
} 

Luego, en el XML (nótese la userServiceFactory ID y el ID userService frijol sólo son necesarias para trabajar con el @Qualifier anotación):

<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"/> 

Y eso es todo, sólo un poco de clase Java y una pizca de La configuración de la placa de la caldera y su fábrica personalizada de frijoles pueden crear todos sus granos y que Spring los resuelva con éxito.

+2

¡Voto esto principalmente porque es Java y, por lo tanto, un objeto FactoryFactory siempre es correcto! –

Cuestiones relacionadas