Inversion of Control (IoC) and dependency injection (DI) are used interchangeably. IoC is achieved through DI. DI is the process of providing dependencies and IoC is the end result of DI. Spring's IoC container enforces the DI pattern for your components, and this leaves them loosely coupled and allows you to code to abstractions.
Dependency injection is a style of object configuration in which an object's fields and collaborators are set by an external entity. In other words, objects are configured by an external entity. Dependency injection is an alternative to having the object configure itself. This might sound a bit vague, so let's look at a simple example.
After visiting the Packt Publishing website, you can search books by the author's name or different criteria. We'll look at the service that lists books by author.
The following interface defines book retrieval:
public interface BookService { List<Book> findAll(); }
The following class lists books by author names:
public class BookLister {
private BookService bookFinder = new BookServiceImpl();
public List<Book> findByAuthor(String author){
List<Book> books = new ArrayList<>();
for(Book aBook:bookFinder.findAll()){
for(String anAuthor:aBook.getAuthors()){
if(anAuthor.equals(author)){
books.add(aBook);
break;
}
}
}
return books;
}
}
The BookLister
class needs a BookService
implementation; this means that the BookLister
class depends on it. It cannot carry out its work without a BookService
implementation. Therefore, BookLister
has a dependency on the BookService
interface and on some implementation of it. The BookLister
class itself instantiates BookServiceImpl
as its BookService
implementation. Therefore, the BookLister
class is said to satisfy its own dependencies. When a class satisfies its own dependencies, it automatically also depends on the classes it satisfies the dependencies with. In this case, BookLister
now also depends on BookServiceImpl
, and if any, on the other values passed as a parameter to the BookServiceImpl
constructor. The BookService
interface can have many implementations such as Spring JDBC-based data access and JPA-based data access implementation. We cannot use a different implementation of the BookService
interface without changing the code.
To refactor this tight coupling, we can move the BookService
instantiation to the constructor of the class. The following is the modified BookLister
class:
public class BookLister { private final BookService bookFinder; public BookLister(BookService bookFinder) { this.bookFinder = bookFinder; } public List<Book> findByAuthor(String author){ List<Book> books = new ArrayList<>(); for(Book aBook:bookFinder.findAll()){ for(String anAuthor:aBook.getAuthors()){ if(anAuthor.equals(author)){ books.add(aBook); break; } } } return books; } }
Note that the BookService
dependency is passed to the BookLister
constructor as a constructor argument. Now, BookLister
is only depending on BookService
. Whoever instantiates the BookLister
constructor will satisfy the dependency. The BookService
dependency is said to be injected into the BookLister
constructor, hence the term dependency injection. It is now possible to change the BookService
implementation used by the BookLister
class without changing the BookLister
class.
There are two types of dependency injections:
A Spring configuration file creates/defines and configures (resolves dependencies) beans. In the Spring configuration file, the constructor injection is constructed as follows:
<bean id="bookLister" class="com.packt.di.BookLister"> <constructor-arg ref="bookService"/> </bean> <bean id="bookService" class="com.packt.di.BookServiceImpl" />
The preceding code is equivalent to the following:
BookService service = new BookServiceImpl();
BookLister bookLister = new BookLister(service);
The setter injection is carried out by setting a property. In a setter injection, instead of passing bookService
as a constructor argument, we change the class to pass as a setter method argument.
The Spring configuration is as follows:
<bean id="bookListerSetterInjection" class="com.packt.di.BookLister"> <property name="bookService" ref="bookService" /> </bean> <bean id="bookService" class="com.packt.di.BookServiceImpl" />
The preceding code snippet is equivalent to the following:
BookService service = new BookServiceImpl();
BookLister bookLister = new BookLister();
bookLister.setBookService(service);
The Spring IoC container is known as ApplicationContext
. The objects that are used in our application, defined in ApplicationContext
, and managed by the Spring IoC container are called beans; for example, bookService
is a bean.
A bean is an object that is managed by the Spring IoC container; beans are created with the configuration metadata that you supply to the container, such as in the form of XML <bean/>
definitions or using Java annotations.
A bean definition describes a bean instance. The bean definition contains the information called configuration metadata, which is needed by the container to know how to create the bean, the life cycle of the bean, and the dependencies of the bean.
The following properties are used to define a bean:
class
: This is mandatory and provides the fully qualified bean class name required for the container to create the bean instance.name
: This attribute (also known as id
) uniquely identifies a bean.scope
: This provides the scope of the objects created from a bean definition, such as prototype
and singleton
. We'll learn about them later.constructor-arg
: This injects a dependency as a bean's constructor argument.properties
: This injects a dependency as a setter method argument.lazy-init
: If this is set as true
, the IoC container creates the bean instance when it is first requested, rather than at startup, which means any configuration error is not discovered until the bean is eventually instantiated inside the Spring context.init-method
: This provides the method name of the bean that is being invoked just after all necessary properties on the bean are set by the IoC container. This is useful when we need to initialize/compute something after the bean is instantiated.destroy-method
: The container calls this method when the bean is destroyed; this is necessary when we need to clean up something before the bean is destroyed.The following are the bean scopes:
singleton
: A single instance of the bean per IoC container. This is not actually the same as in the singleton design pattern (that is, one instance per classloader).prototype
: A single bean definition to have any number of object instances. A new bean instance is created each time one is needed.request
: A bean instance per HTTP request, only valid in the web-aware application context.session
: A bean instance per HTTP session, only valid in the web-aware application context.global-session
: A bean instance per global HTTP session, only valid in the web-aware application context.The following are the steps in a bean's life cycle:
setBeanName
method is invoked on the beans; if they implement the BeanNameAware
interface, the setBeanName()
method is invoked by passing the ID of the bean.BeanFactoryAware
interface, the setBeanFactory()
method is called with an instance of itself.BeanPostProcessor
is done. If a bean has any BeanPostProcessor
interface associated with it, the processBeforeInitialization()
methods are called on the post processors.init-method
, it will be called.BeanPostProcessors
associated with the bean, their postProcessAfterInitialization()
methods are invoked.Note that a POJO doesn't need to depend on anything Spring-specific. For particular cases, Spring provides hooks in the form of these interfaces. Using them means introducing a dependency on Spring. The following figure depicts the bean's life cycle:
To learn more about DI and IoC, visit the Martin Fowler site at http://martinfowler.com/articles/injection.html.
18.216.88.54