Java Dependency Injection design pattern allows us to remove the hard-coded dependencies and make our application loosely coupled, extendable and maintainable. We can implement dependency injection in java to move the dependency resolution from compile-time to runtime.
Java Dependency injection seems hard to grasp with theory, so I would take a simple example and then we will see how to use dependency injection pattern to achieve loose coupling and extendability in the application. Let’s say we have an application where we consume EmailService
to send emails. Normally we would implement this like below.
package com.journaldev.java.legacy;
public class EmailService {
public void sendEmail(String message, String receiver){
//logic to send email
System.out.println("Email sent to "+receiver+ " with Message="+message);
}
}
EmailService
class holds the logic to send an email message to the recipient email address. Our application code will be like below.
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = new EmailService();
public void processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this.email.sendEmail(msg, rec);
}
}
Our client code that will use MyApplication
class to send email messages will be like below.
package com.journaldev.java.legacy;
public class MyLegacyTest {
public static void main(String[] args) {
MyApplication app = new MyApplication();
app.processMessages("Hi Pankaj", "pankaj@abc.com");
}
}
At first look, there seems nothing wrong with the above implementation. But above code logic has certain limitations.
MyApplication
class is responsible to initialize the email service and then use it. This leads to hard-coded dependency. If we want to switch to some other advanced email service in the future, it will require code changes in MyApplication class. This makes our application hard to extend and if email service is used in multiple classes then that would be even harder.One can argue that we can remove the email service instance creation from MyApplication
class by having a constructor that requires email service as an argument.
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = null;
public MyApplication(EmailService svc){
this.email=svc;
}
public void processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this.email.sendEmail(msg, rec);
}
}
But in this case, we are asking client applications or test classes to initializing the email service that is not a good design decision. Now let’s see how we can apply java dependency injection pattern to solve all the problems with the above implementation. Dependency Injection in java requires at least the following:
For our case, we can have MessageService
that will declare the contract for service implementations.
package com.journaldev.java.dependencyinjection.service;
public interface MessageService {
void sendMessage(String msg, String rec);
}
Now let’s say we have Email and SMS services that implement the above interfaces.
package com.journaldev.java.dependencyinjection.service;
public class EmailServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
//logic to send email
System.out.println("Email sent to "+rec+ " with Message="+msg);
}
}
package com.journaldev.java.dependencyinjection.service;
public class SMSServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
//logic to send SMS
System.out.println("SMS sent to "+rec+ " with Message="+msg);
}
}
Our dependency injection java services are ready and now we can write our consumer class.
We are not required to have base interfaces for consumer classes but I will have a Consumer
interface declaring contract for consumer classes.
package com.journaldev.java.dependencyinjection.consumer;
public interface Consumer {
void processMessages(String msg, String rec);
}
My consumer class implementation is like below.
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(MessageService svc){
this.service=svc;
}
@Override
public void processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this.service.sendMessage(msg, rec);
}
}
Notice that our application class is just using the service. It does not initialize the service that leads to better “separation of concerns”. Also use of service interface allows us to easily test the application by mocking the MessageService and bind the services at runtime rather than compile time. Now we are ready to write java dependency injector classes that will initialize the service and also consumer classes.
Let’s have an interface MessageServiceInjector
with method declaration that returns the Consumer
class.
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
public interface MessageServiceInjector {
public Consumer getConsumer();
}
Now for every service, we will have to create injector classes like below.
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new EmailServiceImpl());
}
}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.SMSServiceImpl;
public class SMSServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new SMSServiceImpl());
}
}
Now let’s see how our client applications will use the application with a simple program.
package com.journaldev.java.dependencyinjection.test;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.injector.EmailServiceInjector;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.injector.SMSServiceInjector;
public class MyMessageDITest {
public static void main(String[] args) {
String msg = "Hi Pankaj";
String email = "pankaj@abc.com";
String phone = "4088888888";
MessageServiceInjector injector = null;
Consumer app = null;
//Send email
injector = new EmailServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, email);
//Send SMS
injector = new SMSServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, phone);
}
}
As you can see that our application classes are responsible only for using the service. Service classes are created in injectors. Also if we have to further extend our application to allow facebook messaging, we will have to write Service classes and injector classes only. So dependency injection implementation solved the problem with hard-coded dependency and helped us in making our application flexible and easy to extend. Now let’s see how easily we can test our application class by mocking the injector and service classes.
package com.journaldev.java.dependencyinjection.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplicationJUnitTest {
private MessageServiceInjector injector;
@Before
public void setUp(){
//mock the injector with anonymous class
injector = new MessageServiceInjector() {
@Override
public Consumer getConsumer() {
//mock the message service
return new MyDIApplication(new MessageService() {
@Override
public void sendMessage(String msg, String rec) {
System.out.println("Mock Message Service implementation");
}
});
}
};
}
@Test
public void test() {
Consumer consumer = injector.getConsumer();
consumer.processMessages("Hi Pankaj", "pankaj@abc.com");
}
@After
public void tear(){
injector = null;
}
}
As you can see that I am using anonymous classes to mock the injector and service classes and I can easily test my application methods. I am using JUnit 4 for the above test class, so make sure it’s in your project build path if you are running above test class. We have used constructors to inject the dependencies in the application classes, another way is to use a setter method to inject dependencies in application classes. For setter method dependency injection, our application class will be implemented like below.
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(){}
//setter dependency injection
public void setService(MessageService service) {
this.service = service;
}
@Override
public void processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this.service.sendMessage(msg, rec);
}
}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
MyDIApplication app = new MyDIApplication();
app.setService(new EmailServiceImpl());
return app;
}
}
One of the best example of setter dependency injection is Struts2 Servlet API Aware interfaces. Whether to use Constructor based dependency injection or setter based is a design decision and depends on your requirements. For example, if my application can’t work at all without the service class then I would prefer constructor based DI or else I would go for setter method based DI to use it only when it’s really needed. Dependency Injection in Java is a way to achieve Inversion of control (IoC) in our application by moving objects binding from compile time to runtime. We can achieve IoC through Factory Pattern, Template Method Design Pattern, Strategy Pattern and Service Locator pattern too. Spring Dependency Injection, Google Guice and Java EE CDI frameworks facilitate the process of dependency injection through use of Java Reflection API and java annotations. All we need is to annotate the field, constructor or setter method and configure them in configuration xml files or classes.
Some of the benefits of using Dependency Injection in Java are:
Java Dependency injection has some disadvantages too:
Download Dependency Injection Project
That’s all for dependency injection pattern in java. It’s good to know and use it when we are in control of the services.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
hi, why does it need the consumer class and not enough to use the injector in order to return the appropriate message service
- irving
Pankaj, hello, could you explain, plz, what do we test here? @Test public void test() { Consumer consumer = injector.getConsumer(); consumer.processMessages(“Hi Pankaj”, “pankaj@abc.com”); }
- Hannah
Is this code correct? @Component public class Bean { @Inject private Bean1 anotherBean1; @Inject private Bean2 anotherBean2; private String property; public Bean(Bean1 anotherBean1, Bean2 anotherBean2) { this.anotherBean1 = anotherBean1; this.anotherBean2 = anotherBean2; } public void method1() { // use all member variables } }
- JAMIE
Thank you very much !!! Really good and clear concepts…
- Roshan
Amazing! Thank you! Your articles are outstanding! They are clear and precise to the point.
- Perkone
While the article outlines key elements of dependency injection, the code examples provided illustrate how one can implement the factory design pattern. This is obvious because you have to explicitly call getConsumer() every time. Anyway, decent article but the examples will cause confusion.
- Andrei
So clear to me now, thanks!
- Sizhong Du
TL;DR: DI just put all those connection points into an Object.
- Nyng
Great article and well explained! Would you consider to add a few diagrams to this article? Or, at least, one diagram before and after the inversion of dependencies?
- Alejandro Carlstein
A great article. A message for those who don’t understand the advantage as I did. It can look confusing at first and why we need so much classes and interfaces. But when you are going to test them as individual components you will know the advantage of it. You no more need to modify classes when there is a new change If we use this pattern we can avoid rewriting test cases again.
- Saravanan