Introduction
Dependency Injection (DI) often relies on a container, which is an application in charge to configure all required collaborators that an object relies on by loading information from an external file. An application container can also be implemented by developers as part of a project. However, writing a sophisticated container that is going to be able to read configuration files, parse them and presumably do error checking requires effort and it could be very time consuming work. But luckily, in the last years there have been open source projects that have implemented just that. Among them Spring.
Spring is an open source framework comprised by independent modules (Spring: Core, MVC, AOP and several more..) that aim to address complexity of enterprise development. There's lots to say about Spring really, but in this post we're going to concentrate on its core module; which is the module that provides a DI container that allows developers to easily implement this design pattern without dealing with the implementation details.
BookService.java
public void registerNewBook(Book newBook) { // Tightly coupled to BookDaoImpl BookDaoImpl bookDao = new BookDaoImpl(); bookDao.save(newBook); }
// easy, I've created a new Hibernate implementation! // that's all I need!! Right? :/ BookDaoHibernateImpl bookDao = new BookDaoHibernateImpl();
In order to reduce coupling between classes there are three non-atomic steps that can be performed in a system:
- Program to interfaces:
- Dependency Injection:
- Centralize Configuration:
Step 1 - Program to an interface: This a very fundamental principle; basically, when defining attribute types as interfaces they'll be abstracted of concrete behaviors allowing to switch implementations at runtime.
Let's say we have two services that interact with each other. We have a BookServiceImpl which is in charge of doing some business logic in the service layer which then communicates with BookDaoImpl which performs database operations in the persistence layer.
BookDaoImpl.java
public class BookDaoImpl implements BookDao { //Following the program to an interface principle //we've created a new Interface BookService //with a registerNewBook method public void save(Book book) { System.out.println("Saving book: " + book); } }
BookServiceImpl.java
public class BookServiceImpl implements BookService { //Following the program to an interface principle //we've created a new Interface BookService //with a registerNewBook method public void registerNewBook(Book newBook) { //We can instantiate here any implementer of BookDao BookDao bookDao = new BookDaoImpl(); bookDao.save(newBook); } }Both classes are coded to interfaces BookDaoImpl and BookServiceImpl now implement BookDao and BookService respectively. An interface will force the implementer to include all methods defined in that interface. By following this principle, we can create new implementations as we like i.e BookDaoHibernateImpl. New implementations will be assigned to the interface type and now you can invoke their methods safely because all the implementer classes are guaranteed to contain the methods defined in the interface.
Step two - Dependency Injection: The idea behind DI is that instead of objects being responsible for instantiating the references they require these references are provided or injected to them. In step one we've gained some flexibility by coding to interfaces. However, BookServiceImpl is still responsible for instantiating BookDaoImpl!!
BookServiceImpl.java
public class BookServiceImpl implements BookService { private BookDao bookDao; public void registerNewBook(Book newBook) { bookDao.save(newBook); } public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }We've modified BookServiceImpl to include a BookDao private attribute in which we'll inject a reference using setBookDao(BookDao bookDao) rather than hardcoding it. In that way we can use BookServiceImpl in many different ways, we can set a BookDaoHibernateImpl a BookDaoJdbcImpl or even more interesting a BookDaoMockImpl to mock the data base for rapid prototyping.
If we want to switch implementations we could create a main class to configure the dependencies (this process is known as "wiring") and see the application working. Notice, that in the main class you can wire dependencies by just instantiating the implementation that you require i.e BookDao bookDao =
new BookDaoMockImpl(); In the next example we've done just that.
Client.java
public class Client { public static void main(String[] args) { //Create a new book Book newBook = new Book("1234", "Spring in Action", "Rod Johnson"); //Create the bookService to process books BookServiceImpl bookService = new BookServiceImpl(); //Plugin any impl you'd like BookDao bookDao = new BookDaoMockImpl(); bookService.setBookDao(bookDao); //register the book in the data base bookService.registerNewBook(newBook); } }Step three - Centralize Configuration: A good software development practice is to encapsulate aspects that are likely to vary into a single place. We have partially accomplished that statement. However, a better approach is to define these changes in an external file (of any kind: *.java, *.txt, *.xml etc) and then load them with all the required configuration. This file is what the container will use to configure all the dependencies required by all the objects in the system. Currently, there are several open source implementations of DI containers i.e Google-Guice, PicoContainer, and of course Spring.
Download the Spring framework from the Spring's download website. Make sure to unzip the file and reference the library in the runtime classpath. In Eclipse for example: Right click on the project -> Properties -> Java Build path -> Libraries -> Add jars.
With Spring in place we can start off by declaring the external configuration file. Spring is quite evolved and you can nowadays configure objects (called beans in Spring) in many different ways. But we are going to stick to the most traditional way of doing it which is by using XML. Here's the required xml configuration file for our application:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="bookDao" class="dao.BookDaoImpl"/> <bean id="bookService" class="service.BookServiceImpl"> <property name="bookDao" ref="bookDao"/> </bean> </beans>
When using XML as a file configurator you need to define the beans that interact in your system within the tag <beans>. In our example we have two BookDaoImpl and BookServiceImpl Each entry has a root tag called <bean> where we define the id and the qualified class name. Remember, that we defined an attribute to be set in BookServiceImpl class called bookDao to inject the required references (i.e new BookServiceImpl()or any required implementation ) ? Well, in order to do that with Spring we define the <property> tag and pass the reference that we require; in this case bookDao which resolves to BookDaoImpl.
The only thing we are missing is to load the bean from our client. So let's create a dummy main class to test this out.
public static void main(String[] args) { //Create the Spring container ApplicationContext container = new ClassPathXmlApplicationContext("spring-config.xml"); //Create the bookService to process books BookService bookService = (BookService) container.getBean("bookService"); //Create a new book Book newBook = new Book("1234", "Spring in Action", "Rod Johnson"); //register a new book bookService.registerNewBook(newBook); }Where the container object is telling Spring which strategy to use in order to load the configuration file (there are several strategies to load the config file which are not relevant by now) but the important bit here is how to tell Spring which bean is required. And this is done by sending the bean id as a parameter in container.getBean("bookService");. Additionally the container is capable of reaching all kind of objects and therefore we are required to explicitly cast to BookService.
This way of working give us full flexibility of configuring our dependencies, by only doing a single change in a single file. Let's say we have finally implemented BookDaoHibernateImpl, We would only need to change the class attribute in the <bean> tag:
<bean id="bookDao" class="dao.BookDaoHibernateImpl"/>And BookDaoHibernateImpl or any other BookDao implementation will be injected as a bookDao property in BookServiceImpl without modifying a single line of java code, without recompiling and without retesting.
Conclusion
Dependency Injection is a popular design pattern in the software industry due to it allows to write more scalable, reusable, maintainable, and testable software. By implementing DI software applications become more scalable because objects will be capable of adapting to change faster. By coding to interfaces we can swap required implementations without even touching existing production functionality (..huge gain just by decreasing the risk of letting developers manipulate already tested, documented, and released functionality). Reusability is achieved by having modular objects that can be plugged-in across the application. Better testability because having loosely coupled objects allow developers to write unit tests using mocks; concentrating in units without indirectly testing collaborator dependencies. And finally better maintainability because aspects that are likely to change, (and potentially break existing functionality and introduce new bugs) are concentrated in one single place.