When we write libraries or frameworks, we sometimes have a class Foo
whose constructor takes a Collection
of Bar
s, where the Bar
s will be supplied by dependents.
For example:
@Named
public class Foo {
private final List<Bar> bars;
@Inject
public Foo(List<Bar> bars) {
this.bars = Collections.unmodifiableList(
new ArrayList<>(bars));
}
// Implementation details.
}
Since the constructor is annotated with Inject
, it implies that at least one Bar
bean must be configured or else Spring will throw an UnsatisfiedDependencyException
caused by a NoSuchBeanDefinitionException
.
But what if an empty Collection
is valid input?
It can be hard to coax String into supporting the zero-Bar
s case, but not impossible. I will demonstrate several ways it can be done, but some are better than others.
Use @Resource
on the Collection
Field (Not Recommended)
We could use the javax.annotation.Resource
annotation. This tends to be the default response to “how do I Autowire a collection?”, time, after time, after time.
Using @Resource
, Foo
would look like this:
@Named
public class Foo {
@Resource(name = "barList")
private final List<Bar> bars = Collections.emptyList();
public Foo() { }
}
Then, dependents would supply a List
bean named barList
or not, if they want the zero-Bar
case.
This is not recommended and is my least favorite solution because we are now forcing dependents to use a DI framework; the barList
cannot be set any other way.
Rely on Default Constructor Fallback (Not Recommended)
I first saw this solution in a post by Bruno Tavares. The crux of this solution is: if Spring cannot satisfy any @Autowired(required=false)
constructors, it falls back to the default constructor. To apply this to our example, we would make the following changes to Foo
:
@Named
public class Foo {
private final List<Bar> bars;
public Foo() {
this(Collections.emptyList());
}
@Autowired(required = false)
public Foo(List<Bar> bars) {
this.bars = Collections.unmodifiableList(
new ArrayList<>(bars));
}
}
While this will produce the desired effect, this solution is limited.
First, it cannot be used when Foo
depends on more than just a collection of Bar
s:
@Named
public class Foo {
private final List<Bar> bars;
private final Baz baz;
// Problem: we cannot satisfy the Baz dependency.
public Foo() {
this(Collections.emptyList(), null);
}
@Autowired(required = false)
public Foo(List<Bar> bars, Baz baz) {
this.bars = Collections.unmodifiableList(
new ArrayList<>(bars));
this.baz = baz;
}
}
We could work around this limitation by creating a setBaz
method, but that would be bad design (for several reasons). There are better options, as we’ll see below.
Second, this solution restricts our DI framework to Spring since Java’s @Inject
does not have an equivalent to @Autowired
‘s required
parameter.
For these two reasons, this solution is not recommended.
Use a “NOP” (Recommended, when applicable)
A “NOP” in this case would be an instance of Bar
that has no effect on Foo
. Foo
doesn’t have to do any special reasoning about the NOP Bar
(otherwise it wouldn’t be a NOP).
For example, suppose Bar
s are instances of java.util.Function
, and Foo
applies the list of Function
s to some given value. In this case, the NOP is Function.identity()
since the identity function would not effect the output of Foo
.
Our configuration might look like this:
@Configuration
@ComponentScan("foo.bar") // Picks up Foo automatically
public class FooConfiguration {
/**
* Configure a single Bar value
* so that Foo can be instantiated.
*/
@Bean
public Bar nop() {
return Bar.NOP;
}
}
When there is a sensible NOP Bar
, this solution is the easiest to implement. Unfortunately, not all Bar
s will have a NOP value. Luckily, a final solution is available.
Create a Placeholder Value and Remove in a BeanPostProcessor
(Recommended)
While this solution is more complex than the others, I think it maintains a separation of concerns: it isolates the business logic from the configuration and it does not force dependents to use a particular DI framework or any DI framework at all. In this version, a placeholder Bar
value is added to the Configuration
and is removed in a BeanPostProcessor
, like so:
// Nice, clean Foo class.
@Named
public class Foo {
private final List<Bar> bars;
@Inject
public Foo(List<Bar> bars) {
this.bars = Collections.unmodifiableList(
new ArrayList<>(bars));
}
// Getters
public List<Bar> getBars() {
return bars;
}
// OR Withers
public Foo withBars(Collection<Bar> newBars) {
return new Foo(newBars);
}
}
And the configuration:
@Configuration
@ComponentScan("foo.bar") // Picks up Foo automatically
public class FooConfiguration {
private final Bar placeholder = new Bar();
/**
* Configure a single Bar value (removed below)
* so that Foo can be instantiated.
*/
@Bean
public Bar placeholder() {
return placeholder;
}
/**
* Remove the placeholder from Foo
*/
@Bean
public BeanPostProcessor fooPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(
Object o,
String s) {
return o;
}
@Override
public Object postProcessAfterInitialization(
Object o,
String s) {
if (o instanceof Foo) {
// Remove the placeholder value.
List<Bar> bars = new ArrayList<>(
((Foo) o).getBars());
bars.remove(placeholder);
return Foo.withBars(bars);
} else {
return o;
}
}
};
}
}
Notice that this solution also requires us to add getters and/or withers to Foo
. If adding a getter or wither for the Collection
is not desirable, reflection black magic might be necessary:
@Override
public Object postProcessAfterInitialization(
Object o,
String s) throws BeansException
{
if (o instanceof Foo) {
try {
Field barsField = Foo.class.getDeclaredField("bars");
barsField.setAccessible(true);
@SuppressWarnings("unchecked")
List<Bar> bars = new ArrayList<>(
(List<Bar>) barsField.get(o));
bars.remove(placeholder);
return new Foo(bars);
} catch (NoSuchFieldException
| IllegalAccessException e) {
throw new BeanCreationException(
"Could not create Foo",
e);
}
} else {
return o;
}
}
Final Thoughts
One of our goals when designing our libraries and frameworks should be to make them configuration independent. First, we shouldn’t force dependents to use a DI framework. One way we can do this by avoiding field injection. Second, we should not limit dependents’ choice of DI framework. We do this by using Java’s DI annotations, @Inject
and @Named
instead of framework specific ones such as Spring’s @Autowired
and @Component
. The final solution accomplishes all of these goals.
Maybe none of these solutions will meet your exact needs, but I hope they’ve given you ideas to create your own, perfect solution.