According to the DI pattern, dependent objects are given their dependencies at the time of the creation of the objects by some factory or third party. This factory coordinates each object in the system in such a way that each dependent object is not expected to create their dependencies. This means that we have to focus on defining the dependencies instead of resolving the dependencies of collaborating objects in the enterprise application. Let's look at the following image. You will learn that dependencies are injected into the objects that need them:
Dependency injection between the different collaborating components in the application
To illustrate this point, let's look at TransferService in the next section--a TransferService has dependency with AccountRepository and TransferRepository. Here, TransferService is capable of transferring money by any kind implementation of TransferRepository, that is, we can either use JdbcTransferRepository or JpaTransferRepository, depending on whichever comes along according to the deployment environment.
TransferServiceImpl is flexible enough to take on any TransferRepository it's given:
package com.packt.chapter1.bankapp;
public class TransferServiceImpl implements TransferService {
private TransferRepository transferRepository;
private AccountRepository accountRepository;
public TransferServiceImpl(TransferRepository transferRepository,
AccountRepository accountRepository) {
this.transferRepository =
transferRepository;//TransferRepository is injected
this.accountRepository = accountRepository;
//AccountRepository is injected
}
public void transferMoney(Long a, Long b, Amount amount) {
Account accountA = accountRepository.findByAccountId(a);
Account accountB = accountRepository.findByAccountId(b);
transferRepository.transfer(accountA, accountB, amount);
}
}
Here you can see that TransferServiceImpl doesn't create its own repositories implementation. Instead, we have given the implementation of repositories at the time of construction as a constructor argument. This is a type of DI known as constructor injection. Here we have passed the repository interface type as an argument of the constructor. Now TransferServiceImpl could use any implementation of repositories, either JDBC, JPA, or mock objects. The point is that TransferServiceImpl isn't coupled to any specific implementation of repositories. It doesn't matter what kind of repository is used to transfer an amount from one account to another account, as long as it implements the repositories interfaces. If you are using the DI pattern of the Spring Framework, loose coupling is one of the key benefits. The DI pattern always promotes P2I, so each object knows about its dependencies by their associated interface rather than associated implementation, so the dependency can easily be swapped out with another implementation of that interface instead of changing to its dependent class implementation.
Spring provides support for assembling such an application system from its parts:
- Parts do not worry about finding each other
- Any part can easily be swapped out
The method for assembling an application system by creating associations between application parts or components is known as wiring. In Spring, there are many ways to wire collaborating components together to make an application system. For instance, we could use either an XML configuration file or a Java configuration file.
Now let's look at how to inject the dependencies of TransferRepository and AccountRepository into a TransferService with Spring:
<?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="transferService"
class="com.packt.chapter1.bankapp.service.TransferServiceImpl">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="transferRepository"/>
</bean>
<bean id="accountRepository" class="com.
packt.chapter1.bankapp.repository.JdbcAccountRepository"/>
<bean id="transferRepository" class="com.
packt.chapter1.bankapp.repository.JdbcTransferRepository"/>
</beans>
Here, TransferServiceImpl, JdbcAccountRepository, and JdbcTransferRepository are declared as beans in Spring. In the case of the TransferServiceImpl bean, it's constructed, passing a reference to the AccountRepository and TransferRepository beans as constructor arguments. You might like to know that Spring also allows you to express the same configuration using Java.
Spring offers Java-based configuration as an alternative to XML:
package com.packt.chapter1.bankapp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.packt.chapter1.bankapp.repository.AccountRepository;
import com.packt.chapter1.bankapp.repository.TransferRepository;
import
com.packt.chapter1.bankapp.repository.jdbc.JdbcAccountRepository;
import
com.packt.chapter1.bankapp.repository.jdbc.JdbcTransferRepository;
import com.packt.chapter1.bankapp.service.TransferService;
import com.packt.chapter1.bankapp.service.TransferServiceImpl;
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(){
return new TransferServiceImpl(accountRepository(),
transferRepository());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository();
}
@Bean
public TransferRepository transferRepository() {
return new JdbcTransferRepository();
}
}
The benefits of the dependency injection pattern are the same whether you are using an XML-based or a Java-based configuration:
- Dependency injection promotes loose coupling. You can remove hard-coded dependencies with best practice P2I, and you could provide dependencies from outside the application by using the Factory pattern and its built-in swappable and pluggable implementation
- The DI pattern promotes the composition design of object-oriented programming rather than inheritance programming
Although TransferService depends on an AccountRepository and TransferRepository, it doesn't care about what type (JDBC or JPA) of implementations of AccountRepository and TransferRepository are used in the application. Only Spring, through its configuration (XML- or Java-based), knows how all the components come together and are instantiated with their required dependencies using the DI pattern. DI makes it possible to change those dependencies with no changes to the dependent classes--that is, we could use either a JDBC implementation or a JPA implementation without changing the implementation of AccountService.
In a Spring application, an implementation of the application context (Spring offers AnnotationConfigApplicationContext for Java-based and ClassPathXmlApplicationContext for XML-based implementations) loads bean definitions and wires them together into a Spring container. The application context in Spring creates and wires the Spring beans for the application at startup. Look into the implementation of the Spring application context with Java-based configuration--It loads the Spring configuration files (AppConfig.java for Java and Sprig.xml for XML) located in the application's classpath. In the following code, the main() method of the TransferMain class uses a AnnotationConfigApplicationContext class to load the configuration class AppConfig.java and get an object of the AccountService class.
Spring offers Java-based configuration as an alternative to XML:
package com.packt.chapter1.bankapp;
import org.springframework.context.ConfigurableApplicationContext;
import
org.springframework.context.annotation
.AnnotationConfigApplicationContext;
import com.packt.chapter1.bankapp.config.AppConfig;
import com.packt.chapter1.bankapp.model.Amount;
import com.packt.chapter1.bankapp.service.TransferService;
public class TransferMain {
public static void main(String[] args) {
//Load Spring context
ConfigurableApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
//Get TransferService bean
TransferService transferService =
applicationContext.getBean(TransferService.class);
//Use transfer method
transferService.transferAmmount(100l, 200l,
new Amount(2000.0));
applicationContext.close();
}
}
Here we have a quick introduction to the dependency injection pattern. You'll learn a lot more about the DI pattern in the coming chapters of this book. Now let's look at another way of simplifying Java development using Spring's declarative programming model through aspects and proxy patterns.