views:

1141

answers:

4

Is there a way in Spring that I can auto populate a list with all of beans of a type AND any of its subtypes? I have a setter method that looks like:

setMyProp(List<MyType> list)

And I would like to autowire in any beans of MyType and all subclasses of MyType.

Thanks, Jeff

A: 

Short answer: no.

Long answer: Java generics work by type erasure, meaning that at runtime that parameter is simply a List, not a List of a generic type. As such you can't figure out that it is meant to be of parameter type MyType so it wouldn't be possible to implement this behaviour.

That being said, there are alternative ways of doing this. The most obvious seems to be to listen for bean creation and then seeing if they are of MyType (or subclasses) and then keeping a reference.

There is probably a few ways to do this. One is creating a bean post-processor. This way you'll get notified of every bean that's created.

cletus
Type information for method parameters is not completely erased and still available to the runtime. You can access this via the Method or Constructor getGenericParameterTypes method.
mlk
@mik: yes but he was talking about a parameter on a function where no such information is available.
cletus
+3  A: 

If it's acceptable to fill the list from your application code and not from within the bean definition file you could use the org.springframework.beans.factory.xml.XmlBeanFactory and ask it "getBeansOfType( MyType.class )". This gives you all beans of type (and subtype) of MyType.

tangens
This actually works for all classes implementing ListableBeanFactory ( http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/beans/factory/ListableBeanFactory.html#getBeansOfType%28java.lang.Class%29 )
Thomas Jung
+4  A: 

Yup, you can do this. The spring docs say:

It is also possible to provide all beans of a particular type from the ApplicationContext by adding the annotation to a field or method that expects an array of that type.

Note that it says you need to expect an array, not a list. This makes sense, because generic type erasure means a list may not work at runtime. However, take the following unit test, which works:

<?xml version="1.0" encoding="UTF-8"?>
<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-2.5.xsd"&gt;  

    <bean class="test.Test.TypeB"/>
    <bean class="test.Test.TypeC"/>
    <bean class="test.Test.TypeD"/>
</beans>

and this unit test:

package test;

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {

    private @Autowired List<TypeA> beans;

    @org.junit.Test
    public void test() {
     assertNotNull(beans);
     assertEquals(2, beans.size());
     for (TypeA bean : beans) {
      assertTrue(bean instanceof TypeA);
     }
    }     

    public static interface TypeA {}
    public static class TypeB implements TypeA {}
    public static class TypeC extends TypeB {}
    public static class TypeD {}

}

So officially, you're supposed to autowire TypeA[], not List<TypeA>, but the List works good.

skaffman
+2  A: 

If you can use @Autowired inside the code to be populated you can safely use the way mentioned by skaffman. If you insist on XML configuration there is a small library called Hera to achieve this. Esentially configuration of a scenario described by you looks like this:

<bean id="client" class="..">
    <property name="injectDynamicListHere">
        <hera:list class="my.custom.SuperType" />
    </property>
</bean>

This will inject all top-level Spring beans implementing SuperType as List into the client bean.

Oliver Gierke
cool, thanks. I was able to use the @Autowired in the code, but this is good to know.
Jeff Storey
Perhaps one minor thing to add is that the Hera list respects @Ordered annotations resp. implementing Ordered interface to allow determining order of the beans injected.
Oliver Gierke