24

One of the strongest accents of the Spring framework is the Dependency Injection concept. I understand one of the advices behind that is to separate general high-level mechanism from low-level details (as announced by Dependency Inversion Principle).

Technically, that boils down to having a bean implementation to know as little as possible about a bean being injected as a dependency, e.g.

public class PrintOutBean {
    private LogicBean logicBean;
    public void action() {
        System.out.println(logicBean.humanReadableDetails());
    }
    //...
}

<bean class="PrintOutBean">
    <property name="loginBean" ref="ShoppingCartBean"/>
</bean>

But what if I wanted to a have a high-level mechanism operating on multiple dependent beans?

  public class MenuManagementBean {
       private Collection<Option> options;
       public void printOut() {
            for (Option option:options) {
              // do something for option
            }
            //...
       }
  }

I know one solution would be to use @Autowired annotation in the singleton bean, that is...

  @Autowired
  private Collection<Option> options;

But doesn't it violate the separation principle? Why do I have to specify what dependents to take in the very same place I use them (i.e. MenuManagementBean class in my example)? Is there a way to inject collections of beans in the XML configuration like this (without any annotation in the MMB class)?

<bean class="MenuManagementBean">
    <property name="options">
       <xxx:autowire by-type="MyOptionImpl"/>
    </property>
 </bean>
Grzegorz Oledzki
  • 23,614
  • 16
  • 68
  • 106
  • I don't think adding @Autowired to the field will create the collection of options for you - you'll still need to declare a bean in your context that lists the options. And then you might just as well drop the @Autowired and put a in your MMB context definition. – mdma May 09 '10 at 23:07
  • 4
    @mdma: I am pretty sure adding `@Autowired` to a collection property allows to gather all beans of collection element type. This is how we use it all the time. Spring 2.5 doc on it: http://static.springsource.org/spring/docs/2.5.x/reference/beans.html#beans-autowired-annotation – Grzegorz Oledzki May 10 '10 at 07:34
  • Ok, great, Nice feature! I did check the docs, but I didn't see it when I looked. Sorry for the confusion. – mdma May 10 '10 at 08:22
  • I have reported a feature request to Spring framework to cover that: http://jira.springframework.org/browse/SPR-7183 – Grzegorz Oledzki May 10 '10 at 10:51
  • @Grzegorz Oledzki Not exactly related with the question, but does the Autowired work when using Java Configuration instead of XML files? It seems it doesn't work the same way. I think Resource does that when using Java configuration, not Autowired. – redwulf Feb 13 '17 at 11:01

3 Answers3

37

Old question and in Spring 3.1 it is possible:

public class PluginPrototypeTest extends ASpringWebTest {
  @Autowired
  Collection<IDummyRepo> repos;

  @Test
  public void cacheTest() {
    assertNotNull(repos);
    assertEquals(2, repos.size());
    for(IDummyRepo r: repos){
      System.out.println(r.getName());
    }
  }
}

@Repository
public class DummyRepo implements IDummyRepo {
  @Override
  public String getName(){
    return "DummyRepo";
  }
}
@Repository
public class DummyRepo2 implements IDummyRepo {
  @Override
  public String getName(){
    return "DummyRepo2";
  }
}
takacsot
  • 1,727
  • 2
  • 19
  • 30
  • 3
    I believe the original request was to avoid using annotations, not to avoid using XML... – Jules Mar 27 '13 at 16:24
27

There's no out-of-the-box facility to do this, no. However, if you want a way of collecting all beans of a given type into a collection, without using an @Autowired list, then it's easy to write a custom FactoryBean to do it for you:

public class BeanListFactoryBean<T> extends AbstractFactoryBean<Collection<T>> {

    private Class<T> beanType;
    private @Autowired ListableBeanFactory beanFactory;

    @Required
    public void setBeanType(Class<T> beanType) {
        this.beanType = beanType;
    }

    @Override
    protected Collection<T> createInstance() throws Exception {
        return beanFactory.getBeansOfType(beanType).values();
    }

    @Override
    public Class<?> getObjectType() {
        return Collection.class;
    }    
}

and then

 <bean class="MenuManagementBean">
    <property name="options">
       <bean class="BeanListFactoryBean">
          <property name="beanType" class="MyOptionImpl.class"/>
       </bean>
    </property>
 </bean>

However, this all seems like a lot of effort to avoid putting @Autowired in your original class. It's not much of a violation of SoC, if it is at all - there's no compiltime dependency, and no knowledge of where the options are coming from.

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • Custom FactoryBean: At the first glance I guess this solution doesn't allow me to specify two (or more) collections to be autowired by type and be injected as a single collection. I didn't state that in the question, but I thought it might be useful. – Grzegorz Oledzki May 10 '10 at 07:30
  • When it comes to SoC violation: I see your point, but I still think there's something wrong about it. What if I am supposed to develop and deliver the high-level bean (`MenuManagementBean` in my example) in my library and then some depending project should be able to use that? – Grzegorz Oledzki May 10 '10 at 07:32
  • @Grzegorz: Inject the factory bean with `Option.class` rather than `MyOptionImpl.class`, that should get you all beans implementing the interface. – skaffman May 10 '10 at 07:36
  • Hmm... Maybe it's not that practical, but I thought it would be beneficial if one could inject only some implementations of the `Option` interface. Especially if the `Option` interface is quite general (has a lot of subinterfaces and classes). – Grzegorz Oledzki May 10 '10 at 08:13
  • @Grzegorz: OK, so modify the factory bean to take a list of types and make it add all beans of those types to the same collection. – skaffman May 10 '10 at 08:41
6

Alternative to @Autowired, using a context file: http://static.springsource.org/spring/docs/2.5.x/reference/beans.html#beans-factory-autowire

So you'd have:

<bean class="MenuManagementBean" autowire="byType" />

Other properties can be specified, as normal, and that would override the autowiring only for those properties.

Johanna
  • 61
  • 1
  • 2