Kinbiko

Java: Spring Dependency Injection Patterns - The good, the bad, and the ugly

February 13, 2018

Advanced technical article.

Spring developers will be familiar with its powerful Dependency Injection API. It allows you to declare @Beans 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:

  1. Field injection (The bad)

    import org.springframework.beans.factory.annotation.Autowired;
    
    public class MyBean {
       @Autowired
       private AnotherBean anotherBean;
    
       //Business logic...
    }
    
  2. 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...
    }
    
  3. 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 type AnotherBean?
  • 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.


Roger Guldbrandsen

Written by Roger Guldbrandsen.