简体   繁体   中英

How to create a list filled with instances of a prototype bean using annotations?

From this Q/A: How to define a List bean in Spring? I know I can define a List<Foo> fooList filled with Foo bean instances but using XML configuration. Here's an example:

public interface Foo {
    //methods here...
    void fooMethod();
}

@Service("foo")
@Scope("prototype")
public class FooImpl implements Foo {
    //fields and methods...
    @Override
    public void fooMethod() {
        //...
    }
}

@Service("fooCache")
@Scope
public class FooCacheImpl implements Foo {
    //fields and methods...
    @Override
    public void fooMethod() {
        //retrieves data from some cache
        //...
    }
}

@Service("fooWS")
@Scope("prototype")
public class FooWSImpl implements Foo {
    //fields and methods...
    @Override
    public void fooMethod() {
        //retrieves data from web service
        //...
    }
}

I can configure a client through XML:

<bean id="fooClient" class="some.package.FooClient">
    <property name="fooList">
        <list>
            <bean ... /> <!-- This may be fooImpl -->
            <bean ... /> <!-- This may be fooCacheImpl -->
            <bean ... /> <!-- This may be fooWSImpl -->
            <!-- I can have more beans here -->
        </list>
    </property>
</bean>

I want to know if this can be done with annotations only, no need to define the bean through XML. Something like this:

@Component
@Scope("prototype")
public class FooClient {
    //which annotation(s) to use here to fill this list with FooImpl instances?
    //I understand that if I have two implementations of Foo I may use a @Qualifier
    //or use another list to note the different implementations.
    private List<Foo> fooList;

    public void bar() {
        for (Foo foo : fooList) {
            foo.fooMethod();
        }
    }
}

I think it would be better a solution that doesn't involve injecting the ApplicationContext nor the BeanFactory so FooClient is not tightly coupled to Spring classes. Also, for my case, I cannot use any Java EE classes like javax.inject.Provider as shown in this blog post: Spring 2.5.x+3.0.x: Create prototype instances from code .

What about using a Factory Bean?

I know you mentioned you did not want to be too coupled to spring - with a factory bean your bean containing the list is not so coupled - just your factory is.

Something like

@Component("fooList")
class ListFactory<List<Foo>> implements FactoryBean, ApplicationContextAware {

     ApplicationContext context;
     public List<Foo>> getObject() {
           List<Foo> list = new ArrayList();
           list.add(context.getBean("foo");
           list.add(context.getBean("foo");
           return list;
     }

     public void setApplicationContext(ApplicationContext context) {
             this.context = context;
     }

     public boolean isSingleton() {
           return false;
     }
}

@Component
@Scope("prototype")
class FooClient {

    @Inject
    @Named("footList")
    private List<Foo> fooList;

    public void bar() {
        for (Foo foo : fooList) {
            foo.fooMethod();
        }
    }
}

Haven't tried it myself, or had the scenario where I've needed it so I'm not sure it will work.

If you're doing it in the code directly, then I think using the PostConstruct annotation would be the way to go:

@Component
@Scope("prototype")
public class FooClient {

....

    @PostConstruct
    public void init() throws Exception {
        fooList = new ArrayList<Foo>();
        fooList.add(new FooImpl());
    }

I think using this approach would be more flexible, since I think you will struggle with annotations only if the FooImpl objects themselves require additional configuration.

That's a limitation (or feature) of prototype scope. The docs say this

In contrast to the other scopes, Spring does not manage the complete lifecycle of a prototype bean: the container instantiates, configures, and otherwise assembles a prototype object, and hands it to the client, with no further record of that prototype instance.

So after Spring hands it off to you, it doesn't keep any reference to it and therefore cannot autowire any of them into your fooList . If you did add @Autowired

@Autowired 
private List<Foo> fooList;

it would just create a new FooImpl object and autowire that as the single element in your List .

If you're trying to keep a reference of all the Foo instances created, you'll most likely have to do it yourself.

You can use method injection as this:

public class PrototypeClient {

    protected abstract PrototypeBean createPrototype();

    private List<PrototypeBean> createPrototypeList() {
        int listSize = calculateListSize();

        List<Prototype> result = new ArrayList<Prototype(listSize);

        for (int i = 0; i < listSize; i++) {
            result.add(createPrototype());
        }
        return result;
    } 

    private int calculateListSize() {
       // do your stuff here
    }

    // ...
}

and have an Spring config as:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="prototypeBean"
          class="fully.qualified.class.name.of.Prototype"
          scope="prototype" />

    <bean id="prototyeClient"
          class="fully.qualified.class.name.of.PrototypeClient">
         <lookup-method name="createPrototype" bean="prototypeBean"/>
    </bean>
</beans>                                 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM