Spring developers will be familiar with its powerful Dependency Injection API.
It allows you to declare @Bean
s that Spring then instantiates and manages.
Any dependencies between these beans is then resolved by Spring and injected
automagically.
Three Annotation-based Injection Patterns
There are three ways Spring lets you declare the dependencies of your class using annotations:
Field injection (The bad)
import org.springframework.beans.factory.annotation.Autowired; public class MyBean { @Autowired private AnotherBean anotherBean; //Business logic... }
Setter injection (The ugly)
import org.springframework.beans.factory.annotation.Autowired; public class MyBean { private AnotherBean anotherBean; @Autowired public void setAnotherBean(final AnotherBean anotherBean) { this.anotherBean = anotherBean; } //Business logic... }
Constructor Injection (The good)
public class MyBean { private final AnotherBean anotherBean; public MyBean(final AnotherBean anotherBean) { this.anotherBean = anotherBean; } //Business logic... }
The inconvenient truth about field injection
The most common of all of these patterns is the field injection pattern. Most likely because it’s the most convenient of the three patterns. Unfortunately, because of it’s ubiquity, developers rarely learn about the other two patterns, and the pros and cons associated with each of them.
Classes using field injection tend to become harder to maintain
When you use the field injection pattern, and want to further add dependencies
to your class you only need to make a field, slap an @Autowired
or @Inject
annotation on it and you’re good to go. At first this sounds great, but a few
months down the line you realise that your class has grown into a God class
that’s hard to manage. Granted, this may very well happen with the two other
patterns as well, but they force you to pay more attention to the dependencies
in your class.
Every time you use field injection a unit test dies
This line has stuck with me since I saw Josh
Long’s talk on Spring
Boot, and in a sense is what motivated
me to write this article. How do you go about testing classes using field
injection? Chances are you’re somewhere along the path of memorising the
unintuitive Mockito
idiom for doing this:
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class MyBeanTest {
@Mock
private AnotherBean anotherBean;
@InjectMocks
private MyBean target;
//Tests...
}
This reflection workaround is riddled with additional workarounds developers have to master:
- What if
MyBean
has multiple dependencies of typeAnotherBean
? - Should you create a new instance of the
target
, or just declare it? Is there a difference? - Do you have type safety with dependencies using generics?
- What if you want a real implementation of some of the dependencies, but not for others?
Classes using field injection are non-final, but are prone to circular dependencies
If you try and declare an @Autowired
field as final
, you’ll get a compile
error. This is annoying in and of itself, as the field is only being set once
anyway! Unless you also annotate it as being @Lazy
Spring tries to resolve
your dependencies as part of a
DAG during startup, and
your field may be the source of a BeanCurrentlyInCreationException
due to
unexpected circular dependencies.
For example:
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private C c;
}
public class C {
@Autowired
private A a;
}
The problem is never this simple, and the actual problem tends to span
dozens of classes through a maze of inheritance, libraries and across architecture
boundaries. This problem can be resolved by either making one of the
dependencies not required (allow field to be null with @Autowired(required = false)
) or lazy (set field after resolving beans that depend on this bean with
@Lazy
). As everyone who have experienced this dreadful exception knows,
finding exactly which beans make up this circle is usually a long and arduous
endeavour. And once you have found all the pieces to your puzzle, which
dependency do you sacrifice? How do you document this decision properly?
There are unreasonably many workarounds to solving the circular dependency problem, but hopefully by now something is starting to smell.
Pros
- The most terse of all the patterns.
- Most Java developers are aware of this pattern.
Cons
- Convenience tends to hide code design red flags.
- Hard to test.
- Dependencies are unnecessarily mutable.
- Prone to circular dependency issues.
- Requires the use of (multiple) Spring or Java EE annotations.
public void setDependency(Dependency dependency)
Boilerplate and encapsulation
Among all the patterns, the setter injection pattern has the most boilerplate
by far. Each bean must have a setter and each setter must be decorated with an
@Autowired
or @Inject
annotation. Although this boilerplate definitely
solves the problem of not thinking about the amount of dependencies you’re
injecting into your class (the plethora of setters in your class should be a
dead giveaway), it comes with another design smell. You’re violating
encapsulation by exposing the innards of your class.
Classes using setter injection make testing easy
There’s no reflection magic required. Just set the dependencies you wish you had.
import org.junit.Before;
import org.mockito.Mockito;
public class MyBeanTest {
private MyBean target = new MyBean();
private AnotherBean anotherBean = Mockito.mock(AnotherBean.class);
@Before
public void setUp() {
myBean.setAnotherBean(anotherBean);
}
//Tests...
}
Classes using setter injection are immune to circular dependencies
In using this pattern, Spring doesn’t try to represent your beans in a DAG,
which means that circular dependencies are permitted, but it also means that
circular dependencies are permitted. Allowing circular dependencies is a
double-edged sword. You won’t have to debug the nasty issues that occur as a
result of circular dependencies, but your code is much harder to break apart
later on. The BeanCurrentlyInCreationException
is in fact informing you, on
startup, that your design is flawed.
Pros
- Immune to circular dependency issues.
- Highly coupled classes are easily identified as setters are added.
Cons
- Violates encapsulation.
- Circular dependencies are hidden.
- The most boilerplate code of the three patterns.
- Dependencies are unnecessarily mutable.
The solution: Constructor injection
It turns out that the best solution to dependency injection is constructor injection. Newer platforms that support DI, such as Angular, have learned the lessons from other platforms and only support constructor injection.
Constructor injection exposes excessive coupling
Whenever your class needs a new dependency you’re forced to add it to the constructor’s parameters. This in turn forces you to recognise the amount of coupling in your class. I’ve found that fewer than 3 dependencies is good, and anything above 5 is begging for refactoring. Counting my dependencies is a piece of cake when they’re defined on just a few contiguous lines.
As an added bonus, since final
fields can be initialised in the constructor,
our dependencies can be immutable - as they should be!
Testing constructor injected classes is trivial
It’s arguably even easier than with setter injection.
import org.mockito.Mockito;
public class MyBeanTest {
private AnotherBean anotherBean = Mockito.mock(AnotherBean.class);
private MyBean target = new MyBean(anotherBean);
//Tests...
}
Constructor injected subclasses must have non-default constructors
Any subclass of a class using constructor injection must have a constructor that calls the parent constructor. This is annoying if you have inherited Spring components. I personally haven’t run into this very often. I try and avoid injected dependencies in parent components - usually done through composition over inheritance.
Pros
- Dependencies can be immutable.
- Recommended by Spring.
- Easiest to test out of all the patterns.
- Highly coupled classes are easily identified as constructor parameters grow.
- Familiar to developers coming from other platforms.
- No dependency on the
@Autowired
annotation.
Cons
- Constructors trickle down to subclasses.
- Prone to circular dependency issues.
Conclusion
Use the constructor injection pattern.
There are times where the other patterns make sense too, but “for the sake of being consistent with the rest of the codebase”, and “it’s just easier to use the field injection pattern” are not valid excuses.
For example, using the setter injection pattern is a good intermediary step
when migrating from beans declared in XML files that already use the setter
pattern, or when you need to fix a BeanCurrentlyInCreationException
in
production, ASAP (not that you should ever end up in that position anyway).
Even the field injection pattern is sufficient when, say, sketching out a solution or answering questions on StackOverflow, unless their question is about dependency injection in Java of course. In which case you should refer them to this article.