This post describes the basics of how to do dependency injection with the Spring framework. If you are unfamiliar with dependency injection, you may want to check out my first post on dependency injection before continuing.
Let’s start of with an example
Here we have two classes – DependencyInjectionWithSpring and WeatherService. WeatherService is a dependency of DependencyInjectionWithSpring. DependencyInjectionWithSpring instantiates WeatherService. Its
run() method calls the
printWeather() method on WeatherService. Pretty simple stuff.
Can you spot the problem? Take a minute and look at DependencyInjectionWithSpring. This class instantiates the WeatherService class, and sets this object to its private member variable
weatherService. This is not a problem per se, or rather not compile time or even a runtime problem. This code still compiles. It still runs. It still produces the result we are trying to achieve.
I don’t see any problems with this, can you explain?
Here are a few
- Testability – you can’t test DependencyInjectionWithSpring without testing WeatherService as well. What if weather service gets its data from a thermometer or some external system? You wouldn’t want to wait for the service to run for your tests to complete, that could take a while. Unit tests should run fast. Also, you don’t have control over what WeatherService returns. Since you don’t have this control, how can you test for certain conditions, like returning null or throwing an exception.
- Reusability – as in the Testability problem, you can’t reuse DependencyInjectionWithSpring without getting WeatherService. This limits how and where we use this class, since wherever we have DependencyInjectionWithSpring we get WeatherService. What if we wanted different behavior than what WeatherService provides? We can’t in this case.
- No configuration without recompilation – what if we wanted to change the implementation of WeatherService? What if, instead of reading the weather from a thermometer we wanted to read it from a web service instead? In our current implementation, we are stuck. We would have to write code to change WeatherService to another implementation, recompile, and redeploy. As mentioned before, if we intend to use DependencyInjectionWithSpring we automatically get the WeatherService implementation with it. In other words, they are highly coupled.
So how does Spring help?
Here’s where the Spring framework can help us out.
The Spring framework is known as an IOC container. An IOC container serves two main purposes:
- Manage creation (and full lifecycle) of objects to be used in a system.
- Configure coupling of components. Spring provides (or injects) the objects it manages to the classes that depend on these objects.
For #1, that means we can get rid of the
new WeatherService() piece. Like this:
If you try to run this, you’ll get a
weatherService has not been instantiated.
How does Spring fix this?
Since Spring is an IOC container it can instantiate objects on our behalf. The objects that get instantiated are known as
Beans in the Spring framework. The way that you flag a class that you want Spring to be aware of (that is, you want it to be a Spring bean) is through the
@Component annotation, like this:
This annotation lets Spring know that it should create and manage the lifecycle of this object.
However, if we run our code now, we’ll still run into a NullPointerException. Why is this so?
That’s where #2 from above comes into play. The Spring Framework also configures how components are coupled together. It injects objects into the classes that depend on them.
Spring injects objects via the @Autowired annotation. There are multiple ways that @Autowired can be used. To keep things simple, we’ll annotate a private member variable with @Autowired, and let Spring inject it for us. See below:
Pay particular attention to lines 23-24. This is where the WeatherService dependency gets injected into DependencyInjectionWithSpring.
Now, when we run it, we no longer see a NullPointerException, and the text
The weather is sunny with a 20% chance of rain gets printed out, as expected.
To reiterate. Spring instantiates the
weatherService for us. It also injects it where requested (via the @Autowired annotation).
If you notice in the gist above, and if you run the code, you’ll see that this application is not a web application, but a command line application. In particular, take a look at line 21 above
CommandLineRunner is an interface from Spring Boot that has one method with the following signature:
public void run(String... args) throws Exception. In this example, our
DependencyInjectionWithSpring class implements this interface, and in the
run method calls the
In this post I described the basics of dependency injection with the Spring framework. Project examples can be found on my Spring Beginner Tutorial on github.