DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Frameworks
  4. Spring DI Patterns: The Good, The Bad, and The Ugly

Spring DI Patterns: The Good, The Bad, and The Ugly

Let's explore the three ways Spring lets you declare dependencies using annotations: field injection, setter injection, and constructor injection.

Roger Guldbrandsen user avatar by
Roger Guldbrandsen
·
Feb. 21, 18 · Tutorial
Like (38)
Save
Tweet
Share
95.77K Views

Join the DZone community and get the full member experience.

Join For Free

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 are 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 its 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 realize 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 memorizing 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 the field to be null with @Autowired(required = false)) or lazy (set the field after resolving beans that depend on this bean with @Lazy). As everyone who has experienced this dreadful exception knows, finding exactly which beans make up this circle is usually a long and arduous endeavor. And once you have found all the pieces to your puzzle, which dependency do you sacrifice? How do you document this decision properly?

There is an unreasonable number of workarounds to solving the circular dependency problem, but hopefully by now, something is starting to smell.

Pros

  • The tersest 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 number 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 recognize the number 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 initialized 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.

Spring Framework Dependency injection

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 10 Easy Steps To Start Using Git and GitHub
  • Required Knowledge To Pass AWS Certified Solutions Architect — Professional Exam
  • Multi-Tenant Architecture for a SaaS Application on AWS
  • How Elasticsearch Works

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: