Dependency Injection is a design pattern used in software development to perform inversion of control (IoC). It enables you to decouple class dependencies, making your code more modular, testable, and manageable. Because of its strong support for DI, the Spring Framework is a popular choice for developing enterprise-level Java applications.
DI in Spring is generally accomplished through the usage of the Inversion of Control (IoC) container, which oversees object creation and wiring. The IoC container is in charge of object instantiation, dependency injection, and lifecycle management.
Spring provides various techniques for injecting dependencies:
Constructor-based injection is one of the dependency injection techniques supported by the Spring framework. In this approach, dependencies are provided to a class through its constructor. Spring’s dependency injection container is responsible for instantiating the class and injecting the required dependencies.
Here’s an example that demonstrates constructor-based injection in Spring:
dependencyinjection based folder within the learn-spring project and create sub folder called constructorbasedcom.springcourse.learnspring.springcore.dependencyinjection.constructorbased
And make the following classes to learn about constructor-based dependency injection.
Suppose we have two classes, UserService and EmailService, where UserService depends on EmailService to send notifications. We want to utilize Dependency Injection to inject the EmailService dependency into the UserService class.
First, let’s define the EmailService class:
package com.springcourse.learnspring.springcore.dependencyinjection.constructorbased;
public class EmailService {
public void sendEmail(String message) {
// Code to send email
System.out.println("Email sent: " + message);
}
}
Next, we’ll define the UserService class that has a dependency on EmailService:
package com.springcourse.learnspring.springcore.dependencyinjection.constructorbased;
public class UserService {
private final EmailService emailService;
// Constructor Injection
public UserService(EmailService emailService) {
this.emailService = emailService;
}
public void notifyUser(String username) {
String message = "Hello, " + username + "! You have a new notification.";
emailService.sendEmail(message);
}
}
In the UserService class, we use constructor injection to inject the EmailService dependency. The EmailService object is passed as an argument to the constructor, and it is assigned to the emailService field.
Now, let’s see how we configure and utilize Dependency Injection using the Spring Framework:
package com.springcourse.learnspring.springcore.dependencyinjection.constructorbased;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(emailService());
}
@Bean
public EmailService emailService() {
return new EmailService();
}
}
In the configuration class (AppConfig), we use the @Configuration annotation to indicate that it provides bean definitions. We define two methods, userService() and emailService(), and annotate them with @Bean. These methods create and configure the beans for UserService and EmailService, respectively.
To utilize Dependency Injection, we can create an instance of the ApplicationContext and retrieve the UserService bean from it:
package com.springcourse.learnspring.springcore.dependencyinjection.constructorbased;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ConstructorBasedInjectionApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.notifyUser("SteveJobs - ConstructorBased");
}
}
In the ConstructorBasedInjectionApplication class, we create an instance of AnnotationConfigApplicationContext, passing AppConfig.class as the configuration class. Then, we retrieve the UserService bean from the context using the getBean() method. Finally, we call the notifyUser() method on the UserService instance.
If you run ConstructorBasedInjectionApplication main class, notifyUser() is called, the EmailService dependency is automatically injected into the UserService instance through the constructor. The EmailService bean is created and managed by the Spring IoC container, and we will get the following output.
12:10:50.762 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext -- Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@28f67ac7
12:10:50.790 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
12:10:51.023 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
12:10:51.027 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
12:10:51.029 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
12:10:51.031 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
12:10:51.043 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'appConfig'
12:10:51.050 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'userService'
12:10:51.055 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'emailService'
Email sent: Hello, SteveJobs - ConstructorBased! You have a new notification.
The above example shows how Dependency Injection works in the Spring Framework via constructor injection.
In Spring, setter-based injection is a type of dependency injection where dependencies are injected into a class using setter methods. This is achieved by annotating the setter methods with appropriate annotations. Here’s an example to illustrate setter-based injection in Spring using annotations:
Here are the steps to get the setter-based injection working.
setterbased inside dependencyinjection foldercom.springcourse.learnspring.springcore.dependencyinjection.setterbased
Suppose we have two classes, UserService and EmailService, where UserService depends on EmailService to send notifications. We want to utilize Dependency Injection to inject the EmailService dependency into the UserService class.
First, let’s define the EmailService class:
package com.springcourse.learnspring.springcore.dependencyinjection.setterbased;
public class EmailService {
public void sendEmail(String message) {
// Code to send email
System.out.println("Email sent: " + message);
}
}
Next, we’ll define the UserService class that has a dependency on EmailService:
package com.springcourse.learnspring.springcore.dependencyinjection.setterbased;
public class UserService {
private EmailService emailService;
// Setter method
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
public void notifyUser(String username) {
String message = "Hello, " + username + "! You have a new notification.";
emailService.sendEmail(message);
}
}
Now, let’s see how we configure and utilize Dependency Injection using the Spring Framework:
package com.springcourse.learnspring.springcore.dependencyinjection.setterbased;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public com.springcourse.learnspring.springcore.dependencyinjection.setterbased.UserService userService() {
UserService service = new UserService();
// Setter Injection
service.setEmailService(emailService());
return service;
}
@Bean
public com.springcourse.learnspring.springcore.dependencyinjection.setterbased.EmailService emailService() {
return new EmailService();
}
}
In the configuration class (AppConfig), we use the @Configuration annotation to indicate that it provides bean definitions. We define two methods, userService() and emailService(), and annotate them with @Bean. These methods create and configure the beans for UserService and EmailService, respectively.
To utilize Dependency Injection, we can create an instance of the ApplicationContext and retrieve the UserService bean from it:
package com.springcourse.learnspring.springcore.dependencyinjection.setterbased;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SetterBasedApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.notifyUser("Steve Jobs - SetterBased");
}
}
In the ConstructorBasedInjectionApplication class, we create an instance of AnnotationConfigApplicationContext, passing AppConfig.class as the configuration class. Then, we retrieve the UserService bean from the context using the getBean() method. Finally, we call the notifyUser() method on the UserService instance.
If you run SetterBasedInjectionApplication main class, notifyUser() is called, the EmailService dependency is automatically injected into the UserService instance through the constructor. The EmailService bean is created and managed by the Spring IoC container, and we will get the following output.
13:49:31.376 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext -- Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@28f67ac7
13:49:31.403 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
13:49:31.635 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
13:49:31.637 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
13:49:31.639 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
13:49:31.641 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
13:49:31.654 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'appConfig'
13:49:31.661 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'userService'
13:49:31.666 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory -- Creating shared instance of singleton bean 'emailService'
Email sent: Hello, Steve Jobs - SetterBased! You have a new notification.
The above example shows how Dependency Injection works in the Spring Framework via setter injection.
Field injection is a type of dependency injection in the Spring framework in which dependencies are injected directly into class fields. This method lets you annotate the fields with the @Autowired annotation to tell Spring to wire the dependencies for you. Here’s an example of Spring field injection:
In constructor based injection package, replace the following in UserService with, we will use field injection by annotating with @Autowired.
Similar to Constructor-based Dependency Injection, here the Autowiring takes place by means of field emailService The bean object created for EmailService class will be Autowired & injected via the field, ‘emailService’ in UserService class.
// Constructor Injection
public UserService(EmailService emailService) {
this.emailService = emailService;
}
For Field Injection, replace the above code with the following.
@Autowired
private EmailService emailService;
package com.springcourse.learnspring.springcore.dependencyinjection.constructorbased;
public class UserService {
@Autowired
private EmailService emailService;
public void notifyUser(String username) {
String message = "Hello, " + username + "! You have a new notification.";
emailService.sendEmail(message);
}
}
In the example above, the UserService class has a field named emailService that is annotated with @Autowired. This annotation tells Spring to inject an instance of EmailService into that field.
As of Spring Framework 4.3, an
@Autowiredannotation on such a constructor is no longer necessary if the target bean defines only one constructor to begin with. However, if several constructors are available and there is no primary/default constructor, at least one of the constructors must be annotated with @Autowired in order to instruct the container which one to use. See the discussion on constructor resolution for details.
In Spring Framework, method injection can also be achieved using annotations. The @Autowired annotation is commonly used for method injection. Here’s an example of method injection using annotations:
Similar to Setter-based Dependency Injection, here the Autowiring takes place by means of field emailService The bean object created for EmailService class will be Autowired & injected via the field, ‘emailService’ in UserService class.
package com.springcourse.learnspring.springcore.dependencyinjection.setterbased;
import org.springframework.beans.factory.annotation.Autowired;
public class UserService {
private EmailService emailService;
// Method Injection
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
public void notifyUser(String username) {
String message = "Hello, " + username + "! You have a new notification.";
emailService.sendEmail(message);
}
}
Dependency Injection (DI) in the Spring Framework provides various benefits that contribute to application flexibility, maintainability, and testability. Here are some main benefits of Spring Framework Dependency Injection:
Despite these possible drawbacks, Dependency Injection is still frequently used and offers considerable advantages in terms of modularity, maintainability, and testability. Before selecting to utilise DI in any particular circumstance, it is critical to thoroughly assess the advantages and downsides and analyse the project’s individual requirements.