Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring @Autowiring with generic factory-built beans

I have a set of classes with a complex initialization scheme. Basically, I start with the interface I need to get a hold of, and then make a bunch of calls, and I end up with an object that implements that interface.

In order to handle this, I made a factory class that can, given an interface, produce the final object. I made this factory into a bean, and in XML I specified my various service beans as being instantiated via this factory object with a parameter of the interface that they will implement.

This works great, and I totally get exactly the beans I need. Unfortunately, I would like to access them from my controller classes, which are discovered via component scanning. I use @Autowired here, and it appears that Spring has no idea what type of object these are, and since @Autowired works by type, I'm SOL.

Using @Resource(name="beanName") here would work perfectly, however it seems odd to use @Resource for some beans and @Autowired for others.

Is there a way to get Spring to know what interface the factory will be creating for each of these beans without having a different factory method for each type?

I'm using Spring 2.5.6, by the way, otherwise I'd just JavaConfig the whole thing and forget about it.

Factory class:

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

app context:

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

my controller:

public class HomepageController {

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

   //This does
   @Resource(name="scoreService") ScoreServiceInterface scoreService;
}
like image 655
Brandon Yarbrough Avatar asked Jan 30 '11 23:01

Brandon Yarbrough


People also ask

Which of the following can be annotated with @autowired in Spring?

@Autowired annotation can be applied on variables and methods for autowiring byType. We can also use @Autowired annotation on constructor for constructor based spring autowiring. For @Autowired annotation to work, we also need to enable annotation based configuration in spring bean configuration file.

What is the default wiring used by Spring container?

By default, Spring resolves @Autowired entries by type. If more than one bean of the same type is available in the container, the framework will throw a fatal exception. To resolve this conflict, we need to tell Spring explicitly which bean we want to inject.

What is generic bean?

GenericBeanDefinition is a one-stop shop for standard bean definition purposes. Like any bean definition, it allows for specifying a class plus optionally constructor argument values and property values. Additionally, deriving from a parent bean definition can be flexibly configured through the "parentName" property.

What is factory bean and factory method in Spring?

factory-method: represents the factory method that will be invoked to inject the bean. factory-bean: represents the reference of the bean by which factory method will be invoked. It is used if factory method is non-static.


3 Answers

I suggest you take the factory pattern one step further and implement your factories as Spring FactoryBean classes. The FactoryBean interface has a getObjectType() method which the contain calls to discover what type the factory will return. This gives your autowiring something to get its teeth into, as long as your factory returns a sensible value.

like image 106
skaffman Avatar answered Sep 28 '22 16:09

skaffman


I had a similar problem, but for me I wanted to use a single factory for creating mocked-out implementations of my auto-wired dependencies using JMockit (the testing framework that I am required to use).

After finding no satisfactory solution on the interwebs, I threw together a simple solution that is working really well for me.

My solution uses a Spring FactoryBean as well, but it only uses a single factory bean for creating all my beans (which the original asker seems to have wished to do).

My solution was to implement a factory-of-factories meta-factory that serves-up FactoryBean wrappers around the real, single factory.

Here is the Java for my JMockit mock bean factory:

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

And then in the Spring context XML file, you just can simply create the meta factory that creates the specific bean-type factories:

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

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

To make this work for the original asker's situation, it could be tweaked to make the FactoryBeans into wrappers/adapter for the 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;
        }
    }
}

Then in the XML (notice the userServiceFactory id and the userService bean id are necessary only to work with the @Qualifier annotation):

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

And that's it, just one little Java class and a smidge of boiler-plate configuration and your custom bean factory can create all of your beans and have Spring resolve them successfully.

like image 29
rees Avatar answered Sep 28 '22 16:09

rees


You should be able to achieve this using:

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

Then you can use either @Resource or @Autowired + @Qualifier to inject into your object directly.

like image 35
IceBox13 Avatar answered Sep 28 '22 17:09

IceBox13