Dependency Injection Java Example
In this article, we will examine Dependency Injection in Java and some dependency injection examples.
You can also check this tutorial in the following video:
1. Introduction
In a previous article, we explored the topic of Inversion of Control and how this design principle is appropriately suited for creating loosely coupled software applications. The IoC principle states that certain tasks typically performed by a class – for example, creating objects – should be consigned to an external entity, such as a container. The result is an application that is configurable, modular, extensible, and easier to test.
But how is IoC implemented? There are several design patterns available to implement IoC. These design patterns include:
- Service Locator
- Factory
- Strategy
- Template Method
- Dependency Injection
In this article, we will cover Dependency Injection.
1.1 Technologies Used
Eclipse IDE for Enterprise Java Developers Version: 2020-03 (4.15.0)
2. Dependency Injection Java Example with Spring
2.1 Dependency Injection
In a software application, some objects (consumers) require the services of other objects to perform some task. This compels the consumer class to obtain a reference to the service class instance to call its methods. Therefore, the consumer class has a dependency on the service class.
Traditionally, the consumer class will create an instance of the service class using the new keyword. This makes the classes tightly coupled. A better approach is to delegate the responsibility of creating the dependency to another entity (typically a container) and having it pass in (inject) the object to the consumer via Dependency Injection.
2.2 Benefits of Using Dependency Injection
Some of the benefits of using DI are:
- Your code is loosely coupled
- You have less boilerplate code in your application
- Adding and switching between implementations of a dependency is relatively simple
- You can test your class in isolation by using mock objects
- Your code is easier to maintain
- Your code is easier to read
2.3 Dependency Injection and Spring
There are several DI frameworks available for Java applications. For example, there is CDI for Java EE and its reference implementation WELD. Another option is Google Guice. The most popular DI framework for Java is Spring.
Spring uses its IoC container to create and manage the objects that make up your application. The managed objects are known as beans in Spring jargon.
The IoC container is also responsible for injecting dependencies into the classes that require them. The BeanFactory
interface and its subinterface ApplicationContext
are used to interact with the IoC container. Note that the factory will inject a fully constructed bean, that is, if the injected bean itself has dependencies, they will be resolved before the bean is injected. Dependency Injection occurs at runtime.
To use dependency injection in a Spring application, the developer must do two things:
- Specify the components (beans) that will be injected into the dependent classes using metadata. This can be done via Annotation Configuration, Java Configuration, or XML Configuration. Doing so will inform the IoC container to construct and register the beans in the
ApplicationContext
at startup. - Define constructors or setters in the consumer class with metadata to have those dependencies injected. (This is referred to as “autowiring” in Spring.)
Notes:
- While XML configuration is still supported in Spring, Annotations Configuration and Java Configuration are typically used to provide the metadata used to wire your application.
- Constructor-based and setter-based are the most common types of injection. A field-based injection is supported but has fallen out of favor due to some undesired side effects. For example, you cannot test your class outside of the IoC container.
2.4 Example without Dependency Injection
To demonstrate how dependency injection works in Spring, we’ll first create a simple application with classes that instantiate their dependencies directly. We will then refactor the application to use dependency injection
In the New Project – Select a Wizard dialog box, expand Spring Boot and select Spring Starter Project. Click “Next”.
In the New Spring Starter Project dialog box, enter a name for the project. Also, enter the group, artifact, and package information. Select Java Version 11. Click “Next”.
In the New Spring Starter Project Dependencies dialog window, select “Spring Web” and click “Finish”.
Create a file quote.txt in a new folder /src/main/resources/files/ with the following contents:
quote.txt
You cannot escape the responsibility of tomorrow by evading it today I think therefore I am It was the best of times, it was the worst of times... Don't cry because it's over, smile because it happened Be yourself; everyone else is already taken So many books, so little time
We will use this file in our sample application.
Next, create a class FileQuoteList
with the following code:
FileQuoteList.java
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public class FileQuoteList { final String fileName = "files/quote.txt"; final File file = getFileFromResource(fileName); public List<String> getQuoteList() { try (FileReader reader = new FileReader(file); BufferedReader br = new BufferedReader(reader)) { return br.lines().collect(Collectors.toList()); } catch (IOException e) { return new ArrayList<String>(); } } File getFileFromResource(String fileName) { File quotes = null; Resource resource = new ClassPathResource(fileName); try { quotes = resource.getFile(); } catch (IOException e) { e.printStackTrace(); return quotes; } return quotes; } }
FileQuoteList
has a private method that reads a file from the classpath and a public method getQuoteList
that returns a List
of lines read from the file.
Next, create a service class RandomQuoteService
with a public method that returns a random quote from the list.
RandomQuoteService.java
import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; import java.util.Random; public class RandomQuoteService { final FileQuoteList quote = new FileQuoteList(); public String getRandomQuote() throws FileNotFoundException, IOException { List<String> quoteList = quote.getQuoteList(); Random random = new Random(); int index = random.nextInt(quoteList.size()); return (String) quoteList.get(index); } }
Notice that we are instantiating a FileQuote
object directly to our class using the new operator.
Next, we’ll create a Spring controller RandomQuoteController
.
RandomQuoteController.java
import java.io.IOException; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class RandomQuoteController { RandomQuoteService randQ = new RandomQuoteService(); @RequestMapping("daily-quote") public String getMyQuote() { try { return randQ.getRandomQuote(); } catch (IOException e) { return "To be or not to be"; } } }
The call to daily-quote will return a random quote from the service or a static message if an exception occurs.
Start the application, open a browser, and navigate to localhost:8080/daily-quote.
Recall that we hardcoded the FileQuoteList
dependency in our RandomQuoteService
class. This works, but what if you want to substitute FileQuoteList
with a different implementation, say for testing purposes? As it stands, we need to modify RandomQuoteService
every time we want to switch between the actual implementation and the test implementation. You can recognize how this is impractical. A better approach to handle this predicament is to code to the interface and use dependency injection.
2.4 Example with Dependency Injection
Let’s refactor our code by extracting an interface from the existing implementation. Open FileQuoteList
. In the editor highlight FileQuoteList
. Right-click and select Refactor -> Extract Interface…
Enter QuoteList
as the interface name and select getQuoteList()
from Members to declare in the interface. Click OK.
Next, create a new class MockQuoteList
that implements QuoteList
. Add the following code:
MockQuoteList.java
import java.util.List; import org.springframework.stereotype.Component; @Component public class MockQuoteList implements QuoteList { @Override public List<String> getQuoteList() { return List.of( "May the force be with you", "There is no place like home", "I'll be back", "You're going to need a bigger boat", "My precious"); } }
We can provide configuration metadata using annotation-based configuration. During application startup, Spring will scan specific packages for Java classes annotated with @Component
and other specialized annotations. This process is known as component scanning.
Here are some of the specialized (stereotype) annotations Spring will seek during the component scan process:
- @Controller
- @Service
- @Repository
Spring will automatically register these annotated classes as beans in the application context.
Next, let’s have Spring inject the QuoteList
dependency into the RandomQuoteService
using constructor-based injection. Open RandomQuoteService
and modify it as follows:
RandomQuoteService.java
@Service public class RandomQuoteService { final QuoteList quote; @Autowired public RandomQuoteService(QuoteList quote) { this.quote = quote; } public String getRandomQuote() throws FileNotFoundException, IOException { List<String> quoteList = quote.getQuoteList(); Random random = new Random(); int index = random.nextInt(quoteList.size()); return (String) quoteList.get(index); } }
Like @Component
, the @Service
annotation denotes this class as a bean to be managed by Spring. Notice that we also changed the dependency to use an interface rather than the concrete class. (Actually, it was changed for us when we refactored FileQuoteList
.) This will allow us to plugin any implementation of the QuoteList
type.
Also, we are no longer instantiating a concrete implementation of QuoteList
directly in the class. We will have the IoC container inject one for us. The @Autowired
annotation decorating the constructor instructs Spring to look for a bean that matches the parameter type. If it finds a matching bean, it will inject it into the object.
Note: When using constructor-based injection, the @Autowired
annotation is optional – Spring will automatically inject a matching bean.
Let’s also have Spring inject the RandomQuoteService
into the controller. Modify RandomQuoteController
as follows:
RandomQuoteController.java
@RestController public class RandomQuoteController { RandomQuoteService randQ; @Autowired public void setRandQ(RandomQuoteService randQ) { this.randQ = randQ; } @RequestMapping("daily-quote") public String getMyQuote() { try { return randQ.getRandomQuote(); } catch (IOException e) { return "To be or not to be"; } } }
We are using setter-based injection in the RandomQuoteService
class. This is achieved by decorating the setter method with the @Autowired
annotation. Setter-based injection is best used when the dependency is optional, which may be true if we have other methods that do not require RandomQuoteService
.
Restart the application and go back to your browser. You should now be seeing famous movie quotes.
2.5 Switching Between Implementations
If you want to be able to switch back to the FileQuoteList
implementation, you will need to add the @Component
annotation so that Spring adds it to the application context.
FileQuoteList.java
@Component public class FileQuoteList implements QuoteList { ... }
Restart the application.
2020-06-13 13:20:21.207 WARN 5392 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'randomQuoteController': Unsatisfied dependency expressed through method 'setRandQ' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'randomQuoteService' defined in file [C:\Users\Gilbert\workspaces\java12examples\di-spring\target\classes\com\jcg\examples\RandomQuoteService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.jcg.examples.QuoteList' available: expected single matching bean but found 2: fileQuoteList,mockQuoteList 2020-06-13 13:20:21.210 INFO 5392 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat] *************************** APPLICATION FAILED TO START *************************** Description: Parameter 0 of constructor in com.jcg.examples.RandomQuote required a single bean, but 2 were found: - fileQuoteList: defined in file FileQuote.class] - mockQuoteList: defined in file MockQuoteList.class] Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
Wait, what? You are seeing this error because Spring does not know which implementation to inject into the service class since both beans fulfill the requirement. (Both are of type QuoteList
.) So how do we resolve this? There are a few options that can be used to address this issue, as the error message suggests. The simplest fix is to use the @Primary
annotation on the class you want to be injected. Modify FileQuoteList
as follows:
FileQuoteList.java
@Component @Primary public class FileQuoteList implements QuoteList { ... }
The @Primary
annotation tells Spring, “Hey, I’m the primary bean that fills the requirement so use me”.
Restart the application. The RandomQuoteService
is now using the primary bean.
If you want a truly configurable application that’s able to change implementations without modifying code, you can use Spring Profiles. Unfortunately, the topic is beyond the scope of this article.
2.5 Costs of Using Dependency Injection
As stated before, dependency injection occurs at runtime. This has some ramifications:
- Errors that you would normally discover at compile time may not be apparent until you run your application
- The overhead of injecting beans into your objects can increase your application’s startup time
- The implementation used for an interface is hidden, conceivably making code maintenance challenging
You may want to evaluate these points when considering applying dependency injection in your application.
3. Summary
In this article, we covered the dependency injection in Java. We talked about the Dependency Injection design pattern and some of the benefits of using it in your applications. We demonstrated how dependency injection is implemented using Spring. Finally, we examined some of the possible drawbacks associated with using dependency injection in your application.
4. Download the Source Code
This was a Java Dependency Injection example with Spring.
You can download the full source code of this example here:
Dependency Injection Java Example
Last updated on May 18th, 2021
I hope this is the correct definition of dependency Injection. This is the definition I am going to base my understanding on..😎