Spring injection with Collections

Today I would like to talk about Spring dependency injection when Collections are involved; as Spring treats these data structures in a rather strange way when used as beans.

We, as always, have to distinguish between two cases:

  • constructor and
  • field injection

(the examples below are not meant to represent clever architectural designs. The idea is to prove a point.)

1. Constructor Injection

Let’s suppose we have want to design a file system crawler to detect different types of malicious software signatures. We want to distinguish between different kinds of malicious software, and also want all the infected files to be available to all of our crawlers. Let’s also suppose that we are provided with an interface we cannot modify, and looks like:

public interface Crawler {
    public void crawl();
}

In order to share the list of infected files we are going to use Spring’s constructor injection functionality. First, we create two implementations of the interface above:

public class VirusCrawler implements Crawler {

	private List<String> infectedFiles;

	public VirusCrawler(List<String> infectedFiles) {
		this.infectedFiles = infectedFiles;

	}

	@Override
	public void crawl() {
            //...
	}

and also

public class TrojanCrawler implements Crawler {

	private List<String> infectedFiles;

	public TojanCrawler(List<String> infectedFiles) {
		this.infectedFiles = infectedFiles;

	}

	@Override
	public void crawl() {
            //...
	}
}

Ok now, let’s now put together a configuration for the crawlers; it’s something like:

@Configuration
public class CrawlerConfig {

	@Bean
	public List<String> infectedFiles() {
		return new CopyOnWriteArrayList<String>();
	}

	@Bean
	public Crawler virusCrawler(List<String> infectedFiles) {
		return new VirusCrawler(infectedFiles);
	}

	@Bean
	public Crawler trojanCrawler(List<String> infectedFiles) {
		return new TrojanCrawler(infectedFiles);
	}
}

Surprisingly enough, if you tried to run the code above (provided that you have an extra class that makes Spring start up and read the config above), you would face an exception like:

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'virusCrawler' defined in class blog.di.CrawlerConfig:
Unsatisfied dependency expressed through constructor argument with index 0 of type [java.util.List]: :
No qualifying bean of type [java.lang.String] found for dependency [collection of java.lang.String]:
expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {};

What now? Spring is trying to inject a list of all beans that are of type String? Cut short: yes it does. Had we had such beans, the code above would have started flawlessly – just try it if you feel like (note that it would have meant broken semantics).

How do we fix this without hacking our business logic? The answer is the miraculous expression language (EL) together with the @Value annotation. Let’s modify our configuration accordingly; we end up with something like this:

@Configuration
public class CrawlerConfig {

	public static final String INFECTED_FILES_LIST = "INFECTED_FILES";

	@Bean(name=INFECTED_FILES_LIST)
	public List<String> infectedFiles() {
		return new CopyOnWriteArrayList<String>();
	}

	@Bean
	public Crawler virusCrawler(@Value("#{" + INFECTED_FILES_LIST + "}")List<String> infectedFiles) {
		return new VirusCrawler(infectedFiles);
	}

	@Bean
	public Crawler trojanCrawler(@Value("#{" + INFECTED_FILES_LIST + "}")List<String> infectedFiles) {
		return new TojanCrawler(infectedFiles);
	}
}

Cool! We’ve just made Spring realize we want our specific List<> injected into our beans.

But wait a minute! This means that if our goal was to inject a list of all our beans which implement the interface Crawler, then we could have achieved that with as few as four lines of code! 

	@Bean
	public CrawlerAggregator aggregator(List<Crawler> crawlers) {
		return new CrawlerAggregator(crawlers);
	}

And I also counted the annotation as a separate line; we didn’t need to inject all the Crawlers one by one and create the list by adding those instances ourselves; we delegated this task to Spring.

2. Field Injection

This one is a bit easier. Let’s suppose we have a bean like this:

public class AwesomeCrawlerBean implements Crawler{
 
    @Autowired
    private List<String> infectedFiles;
 
    public void crawl() {
        //...
        infectedFiles.add(fileName);
        //...
    }
}

No big surprise, the code above throws an exception very similar to the one mentioned above. However,in this case we have two different ways to sort this error out:

  • we either use the expression language as in the previous case
  • or we make use of the @Resource annotation

In the first case, we end up with a class like this – pretty similar to the one described above:

public class AwesomeCrawlerBean implements Crawler{
 
    @Value("#{" + CrawlerConfig.INFECTED_FILES_LIST + "}")
    private List<String> infectedFiles;
 
    public void crawl() {
        //...
        infectedFiles.add(fileName);
        //...
    }
}

While in the second case we just replace the @Autowired annotation with a named @Resource:

public class AwesomeCrawlerBean implements Crawler{
 
    @Resource(name=CrawlerConfig.INFECTED_FILES_LIST)
    private List<String> infectedFiles;
 
    public void crawl() {
        //...
        infectedFiles.add(fileName);
        //...
    }
}

which is perhaps a little bit cleaner and easier to get.

Conclusions

It is not possible to inject Collections with Spring using the traditional ways. Spring will treat collections of type X as a collection of all Spring-managed beans of type X, and not as an empty collection.

In order to clearly state that we want a new, empty Spring-managed collection, we can use either the ExpressionLanguage with the @Value annotation, or (in case of field injection) the @Resource annotation.

Advertisements

Author: tamasgyorfi

Senior software engineer, certified enterprise architect and certified Scrum master. Feel free to connect on Twitter: @tamasgyorfi

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s