Dependency Injection - PawelBogdan/BecomeJavaHero GitHub Wiki
- Introduction
- XML-based configuration
- Annotation-based configuration
- Mixed configuration
- Exercises
- Sources
According to wikipedia:
dependency injection is a technique whereby one object supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client's state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
This fundamental requirement means that using values (services) produced within the class from new or static methods is prohibited. The class should accept values passed in from outside. This allows the class to make acquiring dependencies something else's problem.
This is very important if we want to use different implementation of some interfaces depending on purpose. Let's consider database connection, we want to be able to execute final version of our app using real database, but during test we want to use some test database. Of course, we can recompile the code after tests, but, in fact, we cannot be sure that final version is equal to tested version of our application.
We will present some techniques of dependency injection using Spring library. In all examples, we will consider situation, that we have interface IReportFormateer
which is used by ReportGenerator
class. Of course there can be many formats of report, so we will inject different implementations of interface to objects of ReportGenerator
.
The oldest technique is based on xml files. The biggest advantage of this method is that, we define objects in xml file, so we can change injections without rebuilding the whole project. To present this method, let's create our classes and interfaces:
public interface IReportFormatter {
public void format();
}
public class ReportGenerator {
private String name;
private IReportFormatter formatter;
public ReportGenerator() {
System.out.println("Default constructor of Generator");
}
public ReportGenerator(String name, IReportFormatter formatter) {
System.out.println("Constructor with arguments of Generator");
this.name = name;
this.formatter = formatter;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("Name setter in Generator");
this.name = name;
}
public IReportFormatter getFormatter() {
return formatter;
}
public void setFormatter(IReportFormatter formatter) {
System.out.println("Formatter setter in Generator");
this.formatter = formatter;
}
public void format() {
formatter.format();
}
}
public class CsvReportFormatter implements IReportFormatter {
public void format() {
System.out.println("CSV FORMATTER");
}
}
public class XmlReportFormatter implements IReportFormatter {
public void format() {
System.out.println("XML FORMATTER");
}
}
We need additional dependencies:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
Now, we can create spring configuration file in directory src/resources/MAETA-INF
:
<?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="reportGenerator1" class="pl.edu.bogdan.training.di.xml.ReportGenerator">
<property name="formatter">
<bean id="formatter1" class="pl.edu.bogdan.training.di.xml.CsvReportFormatter"/>
</property>
<property name="name" value="generator1"/>
</bean>
<bean id="formatter2" class="pl.edu.bogdan.training.di.xml.XmlReportFormatter"/>
<bean id="reportGenerator2" class="pl.edu.bogdan.training.di.xml.ReportGenerator">
<property name="formatter" ref="formatter2"/>
<property name="name" value="generator2"/>
</bean>
<bean id="formatter3" class="pl.edu.bogdan.training.di.xml.CsvReportFormatter"/>
<bean id="reportGenerator3" class="pl.edu.bogdan.training.di.xml.ReportGenerator">
<constructor-arg name="formatter" ref="formatter3"/>
<constructor-arg name="name" value="generator3"/>
</bean>
</beans>
We can use objects defined in above file:
public class App {
public static void main( String[] args ) {
ApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring-configuration.xml");
ReportGenerator generator1 = (ReportGenerator) context.getBean("reportGenerator1");
generator1.format();
}
}
We can change this project, to define dependencies using annotations. In that case configuration file is changed:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="pl.edu.bogdan.training.di.annotation"/>
</beans>
We add annotation @Service
to class ReportGenerator
. It means that we create object of this class and we are able to inject it wherever we need. We add the same annotation to class CsvReportFormatter
. We add annotation @Autowired
to formatter setter in class ReportGenerator
, it means that the setter is executed just after creation of object of ReportGenerator
and dependency is injected. We can use this object in following way:
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring-configuration.xml");
BeanFactory factory = context;
ReportGenerator generator = factory.getBean(ReportGenerator.class);
generator.format();
}
}
Previous method is not as flexible as first one, but we can extend this approach. In this method we don't need spring configuration file. But we need to create special class:
public class AppConfiguration {
@Bean(name = "formatter1")
public IReportFormatter getFormatter1() {
return new CsvReportFormatter();
}
@Bean(name = "formatter2")
public IReportFormatter getFormatter2() {
return new XmlReportFormatter();
}
@Bean(name = "generator1")
public ReportGenerator getGenerator1(@Qualifier(value="formatter1") IReportFormatter formatter) {
return new ReportGenerator("name", formatter);
}
@Bean(name = "generator2")
public ReportGenerator getGenerator(@Qualifier(value="formatter2") IReportFormatter formatter) {
ReportGenerator generator = new ReportGenerator();
generator.setFormatter(formatter);
generator.setName("name");
return generator;
}
}
And we can use this method:
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring-configuration.xml");
BeanFactory factory = context;
ReportGenerator generator = factory.getBean(ReportGenerator.class);
generator.format();
}
}
- Check how we can use annotation
@Autowired
with annotation@Qualifier
.