5. Movie rental
Assume that we need to write a component that provides a list of
movies directed by a par5cular director1
1
This use case is an adapta/on from the Mar/n Fowler's seminal example on Inversion of Control (see references)
7. MovieService
A"er much delibera/on, we delineate the following component,
which we decide to call MovieService
public class MovieService {
private CSVMovieRepository repository;
public List<Movie> moviesDirectedBy(String directorName) { ... }
}
8. MovieService
Note that MovieService depends on CSVMovieRepository
public class MovieService {
private CSVMovieRepository repository;
public List<Movie> moviesDirectedBy(String directorName) { ... }
}
9. CSVMovieRepository
CSVMovieRepository is an addi'onal component that abstracts
the comma delimited file containing the list of movies
public class CSVMovieRepository {
public CSVMovieRepository(String filename) { ... }
public List<Movie> findAll() { ... }
}
10. The moviesDirectedBy use case
An immediate implementa,on of moviesDirectedBy() is
therefore as follows:
public List<Movie> moviesDirectedBy(String directorName) {
List<Movie> movies = repository.findAll();
Iterator<Movie> it = movies.iterator();
while (it.hasNext()) {
Movie movie = it.next();
if (!movie.getDirector().equals(directorName))
it.remove();
}
return movies;
}
11. The moviesDirectedBy use case
Alterna(vely, we can use the Stream API in order to obtain a terser
implementa(on
public List<Movie> moviesDirectedBy(String directorName) {
repository.findAll()
.stream()
.filter(m -> m.getDirector().equals(directorName))
.collect(Collectors.toList());
}
12. MovieService class
public class MovieService {
private CSVMovieRepository repository;
public MovieService() {
this.repository = new CSVMovieRepository("movies.csv");
}
public List<Movie> moviesDirectedBy(String directorName) {
List<Movie> movies = repository.findAll();
Iterator<Movie> it = movies.iterator();
while (it.hasNext()) {
Movie movie = it.next();
if (!movie.getDirector().equals(directorName))
it.remove();
}
return movies;
}
}
14. Unit tes(ng MovieService
Assume that we now want to unit test MovieService
public class MovieServiceTest {
@Test
public void moviesDirectedByTest() {
...
}
}
15. Unit tes(ng MovieService
We want to ensure the correctness of moviesDirectedBy() by
checking its output against a set of controlled movies
16. Unit tes(ng MovieService
For instance, given the following data
Sophia Coppola, Lost in Translation
Christopher Nolan, Inception
Christopher Nolan, The Dark Knight
17. Unit tes(ng MovieService
We want to test if moviesDirectedBy("Sophia Coppola")
returns the expected output
@Test
public void moviesDirectedByTest() {
List<Movie> movies = service.moviesDirectedBy("Shopia Coppola");
assertTrue(movies.length == 1);
assertTrue(movies.get(0).getDirector().equals("Sophia Coppola"));
assertTrue(movies.get(0).getTitle().equals("Lost in Translation"));
}
18. MockMovieRepository
We can therefore devise a MockMovieRepository:
public class MockMovieRepository {
public List<Movie> findAll() {
return Arrays.asList(
new Movie("Sophia Coppola", "Lost in Translation"),
new Movie("Christopher Nolan", "Inception"),
new Movie("Christopher Nolan", "The Dark Knight")
);
}
}
19. Unit tes(ng MovieService
To use our test data, we need to replace CSVMovieRepository
with MockMovieRepository
21. Unit tes(ng MovieService
However, there is no way to replace CSVMovieRepository with
MockMovieRepository
public class MovieService {
private CSVMovieRepository repository;
public MovieService() {
this.repository = new CSVMovieRepository("movies.csv");
}
}
22. Unit tes(ng MovieService
This is because MovieService instan-ates its own dependency
public class MovieService {
private CSVMovieRepository repository;
public MovieService() {
this.repository = new CSVMovieRepository("movies.csv");
}
}
23. MovieService cannot be tested
Thus, we found out that our solu1on is not unit testable
public class MovieService {
private CSVMovieRepository repository;
public MovieService() {
this.repository = new CSVMovieRepository("movies.csv");
}
}
27. Variability
Assume that we are suddenly been told that from now on movies
are going to be stored in a rela%on database
28. Variability
That requires changing MovieService, even though the real
change involves a different component
public class MovieService {
public SQLMovieRepository repository;
public MovieService() {
this.repository = new SQLMovieRepository(...);
}
...
}
29. Obtaining the movie list
This appears even more suspicious if we no2ce that the
implementa2on of moviesDirectedBy() does not change
public List<Movie> moviesDirectedBy(String directorName) {
List<Movie> movies = repository.findAll();
...
}
30. Obtaining the movie list
As a ma&er of fact, MovieService does not need to know how
the movies are stored
public List<Movie> moviesDirectedBy(String directorName) {
List<Movie> movies = repository.findAll();
...
}
31. Obtaining the movie list
There are many different ways of storing the movie list
• Database
• File system
• Remote web service
32. MovieRepository as an interface
We can explicitly model this variability by defining a
MovieRepository interface with a unique method
public interface MovieRepository {
List<Movie> findAll();
}
33. MovieRepository as an interface
Each MovieRepository implementa-on has its own way of
retrieving the data
public class SQLMovieRepository implements MovieRepository {...}
public class CSVMovieRepository implements MovieRepository {...}
public class RESTMovieRepository implements MovieRepository {...}
34. External dependency
Instead of le,ng MovieService look up the right
implementa5on, we provide it as an external dependency
public class MovieService {
private MovieRepository repository;
public MovieService(MovieRepository repository) {
this.repository = repository;
}
...
}
35. Composing objects
We can now provide different implementa2ons of
MovieRepository to MovieService
public class MovieService {
private MovieRepository repository;
public MovieService(MovieRepository repository) {
this.repository = repository;
}
...
}
36. Composing objects
This allows different developers to reuse the same components in
different situa5ons
public class MovieService {
private MovieRepository repository;
public MovieService(MovieRepository repository) {
this.repository = repository;
}
...
}
38. Dependency inversion principle
1. High-level modules should not depend on low-level modules.
Both should depend on abstrac:ons
2. Abstrac:ons should not depend upon details. Details should
depend upon abstrac:ons
42. Testability
As a welcome byproduct, we gain the possibility of tes8ng
MovieService
@Test
public void moviesDirectedByTest() {
MovieService ms = new MovieService(new MockMovieRepository());
List<Movie> movies = ms.moviesDirectedBy("Sophia Coppola");
assertTrue(movies.length == 1);
assertTrue(movies.get(0).getDirector().equals("Sophia Coppola"));
assertTrue(movies.get(0).getTitle().equals("Lost in Translation"));
}
44. Deciding for a MovieRepository
At a certain point we need to decide for a specific implementa-on
of MovieRepository
45. Factories
To this end, we write a factory class, whose responsibility is to
provide fully-constructed MovieService objects
46. Factories
For instance, we could devise the following simple factory2
public class MovieServiceFactory {
public MovieService create(String type) {
if (type.equals("CSV")) return new MovieService(new CSVMovieRepository(...));
if (type.equals("SQL")) ...
return null;
}
}
2
Here, we are using the so called simple factory pa/ern. Although it is in turn sub-op9mal, we decided for this
pa<ern because of the similarity of its interface with that of Spring's own factories
47. Factories
We can finally obtain a fully-constructed MovieService
public static void main(String[] args) {
...
MovieService service = factory.create("SQL");
List<Movie> movies = service.moviesDirectedBy("Sophia Coppola");
}
52. Wiring objects
We would like a 3rd
-party factory to take care of crea0ng and
wiring the different objects of the applica0on on our behalf
MovieService movieService = externFactory.getComponent("SQL");
53. Dependency Injec+on
Dependency injec+on (DI) is a pa*ern whereby the responsibility
of crea7ng and wiring applica7on objects is deferred to a 3rd
-party
factory
54. Dependency Injec+on
Namely, the 3rd
-party factory will:
• Instan'ate components
• Provide dependencies to each component
• Manage the lifecycle of each component
56. The Spring framework
Spring is a Java framework that provides comprehensive
infrastructure support for developing Java applica6ons
57. Spring DI Containers
In Spring, applica-on components are instan&ated and managed
directly by the framework
58. Spring DI Containers
Objects live within a dependency-inversion container
+-------------------------------+
| +-------+ +-------+ |
| | | | | |
| | A +------> B | |
| | | | | |
| +---^---+ +---^---+ |
| | | |
| | | |
| +---+---+ | |
| | | | |
| | C +----------+ |
| | | |
| +-------+ |
+-------------------------------+
Spring DI container
59. Spring DI Containers
Such a DI container plays the role of the 3rd
-party factory
+-------------------------------+
| +-------+ +-------+ |
| | | | | |
| | A +------> B | |
| | | | | |
| +---^---+ +---^---+ |
| | | |
| | | |
| +---+---+ | |
| | | | |
| | C +----------+ |
| | | |
| +-------+ |
+-------------------------------+
Spring DI container
60. Spring DI Containers
It is responsible for instan&a&ng and assembling the objects
+-------------------------------+
| +-------+ +-------+ |
| | | | | |
| | A +------> B | |
| | | | | |
| +---^---+ +---^---+ |
| | | |
| | | |
| +---+---+ | |
| | | | |
| | C +----------+ |
| | | |
| +-------+ |
+-------------------------------+
Spring DI container
61. Spring beans
A bean is an applica)on object that is instan)ated, and otherwise
managed by a Spring DI container
+-------------------------------+
| +-------+ +-------+ |
| | | | | |
| | A +------> B | |
| | | | | |
| +---^---+ +---^---+ |
| | | |
| | | |
| +---+---+ | |
| | | | |
| | C +----------+ |
| | | |
| +-------+ |
+-------------------------------+
Spring DI container
62. Spring DI Containers
Spring comes with two families of containers:
• Containers that implement the BeanFactory interface
• Containers that implement the ApplicationContext
interface
65. Bean retrieving
The ApplicationContext interface models a sophis2cated
implementa2on of the factory pa*ern
T getBean(String name, Class<T> requiredType)
66. Bean retrieving
By using the getBean() method, developers can retrieve
instances of the applica7on beans
T getBean(String name, Class<T> requiredType)
67. Bean retrieving
Example of bean retrieving:
ApplicationContext c = new ClassPathXmlApplicationContext("ex1.xml");
MovieService movieService = c.getBean("SQL", MovieService.class);
68. Bean retrieving
ClassPathXmlApplicationContext is an implementa+on of
ApplicationContext
ApplicationContext c = new ClassPathXmlApplicationContext("ex1.xml");
MovieService movieService = c.getBean("SQL", MovieService.class);
69.
70. Configura)on metadata
The container gets instruc/ons on what objects to instan/ate and
configure by reading some configura)on metadata
+ Business objects
|
|
|
|
+---------v---------+
| |
| Spring |
+-------------> Container |
| |
Configuration +---------+---------+
metadata |
(e.g., XML file) |
|
v Fully configured
system
71. Configura)on metadata
Through the configura.on metadata, the developer tells the Spring
container how to assemble objects
+ Business objects
|
|
|
|
+---------v---------+
| |
| Spring |
+-------------> Container |
| |
Configuration +---------+---------+
metadata |
(e.g., XML file) |
|
v Fully configured
system
73. Configura)on metadata
The configura-on metadata ex1.xml is loaded from the
CLASSPATH
ApplicationContext c = new ClassPathXmlApplicationContext("ex1.xml");
MovieService movieService = c.getBean("SQL", MovieService.class);
74. Dependencies vs. business logic
The DI Container allows decoupling the specifica(on of
dependencies from the actual program logic
76. POJOs
Many Java frameworks require developers to extend classes or
implement interfaces provided by the framework itself
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response);
public void doPost(HttpServletRequest request,
HttpServletResponse response);
public void init();
public void destroy();
}
77. POJOs
Spring beans are instead Plain Old Java Objects (POJOs)
public class MovieService {
private MovieRepository repository;
public MovieService(MovieRepository repository) {
this.repository = repository;
}
...
}
78. POJOs
Because of this, components in a Spring-based applica8on o9en
have no indica(on that they are being used by Spring
79. Movie rental
Assume that we want to take advantage of the Spring framework
for our movie rental applica.on
80. Movie rental
We need to instruct the Spring DI container about the dependency
between MovieService and MovieRepository
+--------------+ +-----------------+
| | | |
| MovieService +----------> MovieRepository |
| | | |
+--------------+ +--------^--------+
|
|
|
+---------+----------+
| |
| SQLMovieRepository |
| |
+--------------------+
81. Configuring the container
We need to configure Spring to tell it what beans it should contain,
and how to wire those beans
+---------------------------------------------------+
| +--------------+ +-----------------+ |
| | | | | |
| | MovieService +----------> MovieRepository | |
| | | | | |
| +--------------+ +--------^--------+ |
| | |
| | |
| | |
| +---------+----------+ |
| | | |
| | SQLMovieRepository | |
| | | |
| +--------------------+ |
+---------------------------------------------------+
Spring DI container
83. The <beans> element
The root element of the Spring configura4on file is the <beans>
element
<?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">
<!-- CONFIGURE APPLICATION BEANS HERE -->
</beans>
84. The <bean> element
Each applica)on bean is associated with a <bean> element in the
XML configura)on file
<bean id="inMemoryMovieRepository”
class="it.polimi.awt.repository.InMemoryMovieRepository">
...
</bean>
85. The <bean> element
Every bean has one (or more) id a0ribute, and a class a0ribute
<bean id="inMemoryMovieRepository”
class="it.polimi.awt.repository.InMemoryMovieRepository">
...
</bean>
86. The <bean> element
Namely:
• If more iden,fiers are specified, the extra ones are considered as
aliases
• The class a8ribute could either specify the class of the bean to
be constructed or the name of a sta,c factory method
87. How to inject dependencies
In Spring, dependencies are injected through:
• Construc*on-based injec*on
• Se4er-based injec*on
• Arguments to a factory method
88. Constructor-based injec1on
Constructor-based injec1on is accomplished by the container
invoking the bean constructor
<bean id="movieService" class="it.polimi.awt.service.MovieService">
<constructor-arg ref="inMemoryMovieRepository"/>
</bean>
89. Constructor-based injec1on
The <constructor-arg> element allows injec0ng a dependency
through a constructor call
<bean id="movieService" class="it.polimi.awt.spring.MovieService">
<constructor-arg ref="inMemoryMovieRepository"/>
</bean>
90. Se#er-based injec/on
Se#er-based injec/on is accomplished by the container calling
se3er methods on the bean3
<bean id="lostInTranslation" class="it.polimi.awt.domain.Movie">
<property name="title" value="Lost in Translation"/>
<property name="director" value="Sophia Coppola"/>
</bean>
3
As we will see, Movies are domain objects, which usually do not need injec5on. Here, we are doing this for the
sole sake of presen5ng an example of se>er-based injec5on
91. Se#er-based injec/on
The <property> element injects the dependency by calling the
property se5er
<bean id="lostInTranslation" class="it.polimi.awt.domain.Movie">
<property name="title" value="Lost in Translation"/>
<property name="director" value="Sophia Coppola"/>
</bean>
92. Constructor vs. se-ers
As a rule of thumb, use constructor arguments for mandatory
dependencies and se6ers for op*onal dependencies
93. Spring beans vs. JavaBeans
Despite the similar name, keep in mind that a DI container is not
limited to JavaBeans4
and it can manage virtually any class
4
Recall that JavaBeans are objects with i) only a default constructor; and ii) appropriate ge<ers and se<ers
97. References
• Mar%n Fowler, Inversion of Control and Dependency Injec%on
pa;ern
• SpringSource, Spring Framework Reference
• Craig Walls, Spring in Ac%on (3rd Edi%on), Manning Publica%ons