Embracing the Messiness in Search of Epic Solutions

Spring: Choosing the Right Dependency Injection Approach

Posted

in

There are many different ways to perform dependency injection (DI) in Spring. As Spring evolves over time, the DI configuration also changes over time, which may contribute to some confusion to those who are just learning Spring.

Scenario

Let’s assume we have this scenario: We have Team Awesome that consists of one awesome member, called Donkey Hulk. Donkey Hulk can only do two things: walk and smash. When someone calls Team Awesome for help, Donkey Hulk will walk around and smash stuff. Okay, I’m very tired this early AM, but you get the point here…

Once we have configured the dependency injection, we are going to use the code below to call for help.

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        TeamAwesome teamAwesome = context.getBean(TeamAwesome.class);

        System.out.println(teamAwesome.rescue());
    }
}

Solution #1: Solving with Angular Stuff

… and no, I’m not referring to AngularJS. Rather, I’m talking about angle brackets here, also known as XML.

To accomplish DI using XML, we created DonkeyHulk class…

public class DonkeyHulk {
    public String walk() {
        return "DonkeyHulk walks slowly.";
    }

    public String smash() {
        return "DonkeyHulk smashes some pumpkins.";
    }
}

… and created TeamAwesome class…

public class TeamAwesome {
    private DonkeyHulk donkeyHulk;

    public void setDonkeyHulk(DonkeyHulk donkeyHulk) {
        this.donkeyHulk = donkeyHulk;
    }

    public String rescue() {
        return donkeyHulk.walk() + " " + donkeyHulk.smash();
    }
}

Finally, we use XML to wire them up.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="donkeyHulk" class="com.choonchernlim.epicapp.service.DonkeyHulk"/>

    <bean id="teamAwesome" class="com.choonchernlim.epicapp.service.TeamAwesome">
        <property name="donkeyHulk" ref="donkeyHulk"/>
    </bean>
</beans>

Solution #2: @Service and @Autowired

Realizing all the XML mess, Spring 2.5 introduced several useful annotations, most importantly @Autowired and @Service.

With these annotations, we essentialy cut down the XML configurations down to one line:-

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.choonchernlim.epicapp"/>
</beans>

For DonkeyHulk class, all we need to do is to annotate the class with @Service annotation so that the component scan is aware of this Spring bean.

@Service
public class DonkeyHulk {
    public String walk() {
        return "DonkeyHulk walks slowly.";
    }

    public String smash() {
        return "DonkeyHulk smashes some pumpkins.";
    }
}

Now, this is where things get a little tricky, especially for those who are just learning Spring. The @Autowired annotation allows you to autowire a bean at the field level, method level or constructor level.

Solution #2a: @Autowired at Field Level

This is probably the simplest way to inject a dependency into a class. All we do here is to create a field and annotate it with @Autowired. It can’t get any simpler than this.

@Service
public class TeamAwesome {
    @Autowired
    private DonkeyHulk donkeyHulk;

    public String rescue() {
        return donkeyHulk.walk() + " " + donkeyHulk.smash();
    }
}

While this may seem like an elegant piece of code, the biggest downside to this approach is there’s no way we can mock the injected dependency in our test cases. Donkey Hulk may take one minute to walk before it starts smashing, and everytime we test Team Awesome, we have to suffer through and wait for Donkey Hulk to do its job.

That said, this approach works, if:-

  • The injected dependency is relatively fast.
  • The injected dependency does not interact with external system(s).
  • You don’t write test cases.

Solution #2b: @Autowired at Method Level

To fix the above problem, we can perform the dependency injection at the method level.

@Service
public class TeamAwesome {
    private DonkeyHulk donkeyHulk;

    @Autowired
    public void setDonkeyHulk(DonkeyHulk donkeyHulk) {
        this.donkeyHulk = donkeyHulk;
    }

    public String rescue() {
        return donkeyHulk.walk() + " " + donkeyHulk.smash();
    }
}

With this approach is we can mock (or spy) Donkey Hulk and make it walk faster.

The downside to this approach is it doesn’t look “semantically” correct, especially for those who comes from the Object Oriented (OO) world. Let’s assume the Team Awesome now consists of three members:-

@Service
public class TeamAwesome {
    private DonkeyHulk donkeyHulk;
    private SpiderPig spiderPig;
    private NinjaTortoise ninjaTortoise;

    @Autowired
    public void setDonkeyHulk(DonkeyHulk donkeyHulk) {
        this.donkeyHulk = donkeyHulk;
    }

    @Autowired
    public void setSpiderPig(SpiderPig spiderPig) {
        this.spiderPig = spiderPig;
    }

    @Autowired
    public void setNinjaTortoise(NinjaTortoise ninjaTortoise) {
        this.ninjaTortoise = ninjaTortoise;
    }

    public String rescue() {
        return ...;
    }
}

See the problem here? The OO programers are taught to create objects using constructors and the constructor’s job is to contruct the object. Now, there are all these setter methods that “seem” to violate the principle of immutability. Further, these setter methods tend to clutter our otherwise perfect code.

Solution #2c: @Autowired at Constructor Level

A much better approach is to perform the dependency injection at the constructor level.

@Service
public class TeamAwesome {
    private DonkeyHulk donkeyHulk;

    @Autowired
    public TeamAwesome(DonkeyHulk donkeyHulk) {
        this.donkeyHulk = donkeyHulk;
    }

    public String rescue() {
        return donkeyHulk.walk() + " " + donkeyHulk.smash();
    }
}

Even for those who don’t know Spring, by inspecting the constructor, they know that Team Awesome requires Donkey Hulk for it to work.

Going back to the previous example where Team Awesome has three members, it is as easy as passing them into the same constructor.

@Service
public class TeamAwesome {
    private DonkeyHulk donkeyHulk;
    private SpiderPig spiderPig;
    private NinjaTortoise ninjaTortoise;

    @Autowired
    public TeamAwesome(DonkeyHulk donkeyHulk,
                       SpiderPig spiderPig,
                       NinjaTortoise ninjaTortoise) {
        this.donkeyHulk = donkeyHulk;
        this.spiderPig = spiderPig;
        this.ninjaTortoise = ninjaTortoise;
    }

    public String rescue() {
        return ...;
    }
}

What if we have two implementations of Donkey Hulk: the default implementation and a faster implementation of Donkey Hulk? We still can choose which implementation to use with @Qualifier annotation without affecting other injected dependencies.

@Service
public class TeamAwesome {
    private DonkeyHulk donkeyHulk;
    private SpiderPig spiderPig;
    private NinjaTortoise ninjaTortoise;

    @Autowired
    public TeamAwesome(@Qualifier("fasterDonkeyHulk") DonkeyHulk donkeyHulk,
                       SpiderPig spiderPig,
                       NinjaTortoise ninjaTortoise) {
        this.donkeyHulk = donkeyHulk;
        this.spiderPig = spiderPig;
        this.ninjaTortoise = ninjaTortoise;
    }

    public String rescue() {
        return ...;
    }
}

Solution #3: Solving with @Configuration

What if you are so-called “Java purist” and you hate to see your project code cluttered with Spring annotations?

The good news is Spring 3.0 introduced @Configuration annotation that allows us to use Java-based configuration.

Both DonkeyHulk and TeamAwesome look like regular Java code.

public class DonkeyHulk {
    public String walk() {
        return "DonkeyHulk walks slowly.";
    }

    public String smash() {
        return "DonkeyHulk smashes some pumpkins.";
    }
}
public class TeamAwesome {
    private DonkeyHulk donkeyHulk;

    public TeamAwesome(DonkeyHulk donkeyHulk) {
        this.donkeyHulk = donkeyHulk;
    }

    public String rescue() {
        return donkeyHulk.walk() + " " + donkeyHulk.smash();
    }
}

Then, we create a separate configuration class annotated with @Configuration that contains the dependency injection configurations.

@Configuration
public class MyConfiguration {

    @Bean
    public DonkeyHulk getDonkeyHulk() {
        return new DonkeyHulk();
    }

    @Bean
    public TeamAwesome getTeamAwesome(DonkeyHulk donkeyHulk) {
        return new TeamAwesome(donkeyHulk);
    }
}

The downside to this approach is… well, let me tell a story here…

… Once upon a time, we the Java developers liked to hardcode our configurations in the Java code. Then, someone said “That’s a BAD idea and we should externalize them into a flat file!”. As we looked for a solution, we stumbled upon something called the dot properties file. Then, someone said “This key-value pair structure is too simple, and we can’t group things nicely!”. Then, we stumbled upon the epic angle brackets called the Extensible Markup Language. As years passed by, the project scope got larger, and we the Java developers spent more time managing XML code than Java code. The Ruby developers laugh at us and our pride hurt for many years. Then, the annotation-based configuration saved the day and we lived happily ever since. Not too long ago, somebody with too much of free time decided to come up with Java-based configuration to completely eliminate all the remaining XML-based configuration. We the Java developers celebrated and rejoiced for the epic discovery. Then, wait… WTF?? Are we repeating the history here??

For the record, there are many important uses of Java-based configuration especially when dealing with configurations that has to be determined at runtime. However, this example of Team Awesome is not one of them.

What’s My Take?

My take is to use @Autowired at the constructor level 90% of the time. Then, depending on the problem set, I may use XML-based configuration or Java-based configuration where I deem fit.

Tags:

Comments

3 responses to “Spring: Choosing the Right Dependency Injection Approach”

  1. warmuuh Avatar
    warmuuh

    If you use field-based @Autowired, you can still inject mocks in an easily and fast manner: E.g. if you use Mockito, you could use @Mock, @InjectMocks and the MockitoJUnitRunner and the mocks get injected automatically into your class.

    I personally prefer using field-based @Autowired, because it adds less clutter to your code. If you have around 5 dependencies, you would have 5 fields only. If you then add the constructor or methods only for the injection to work, you basically add 10~15 lines of code that don’t have *any* additional information for the reader, they are just organizational *white noise*.

    1. Choon-Chern Lim Avatar

      @warmuuh, thank you for your feedback. You definitely have legit points here, and I fully respect your decision. In my case, I’m leaning towards constructor-based injection for 2 reasons.

      First, I have peers who are learning Java for the first time in my team. The constructor-based injection “feels” more natural to them compared to field-based injection, even though we, the more experienced Spring users, know it is mostly syntactic sugar (with minor differences). I agree it is definitely cleaner when using field-based injection, but even with constructor-based injection, it isn’t too bad. Think about it, if we are injecting 3 dependencies, with field-based injection, we have a total of 6 lines (3 lines for @Autowired and 3 lines for field declarations). With constructor-based injection, we usually have 3 lines more = 9 lines (3 lines for field declarations, 3 statement lines in the constructor, 1 line for constructor arguments, 1 line for @Autowired, and 1 line for closing curly brace).

      Second, if I use field-based injection, I can’t set that field as final. I’m trying to make it a habit to always use final as much as possible and constructor-based injection allows me to do that. With Java 8 moving towards functional-styled programming, the principle of immutability is going to be the key. Thus, I want to get my mindset ready for that, even though my corporation is only beginning to switch to Java 7… duh.

  2. Marcus Pereira Avatar

    Fully agree with the previous comment.

Leave a Reply