In this article we are going to learn about one of the foundational pieces of the Spring Framework; its implementation of Dependency Injection (which they usually refer by the more confusing name: Inversion of Control or IoC).
If you don’t know what Dependency Injection is, you can start by reading the first section of my Dependency Injection with Dagger and Bazel article. That section explains what is dependency injection and how it’s typically used.
Spring Beans
The term bean
is used to refer to a few things in the Java world, so it’s important to know what it means when used in the context of Spring.
A Spring bean
refers to an object that is injected by Spring’s depedency injection framework (Usually referred as IoC framework or IoC container).
ApplicationContext
The ApplicationContext
interface takes care of creation and injection of beans. There are different implementations of this interface that allow us to configure our beans using different methods. We’ll start by looking at the traditional way: ClassPathXmlApplicationContext
.
We can instantiate an ApplicationContext
that loads beans from the configuration file beans.xml
with this code:
1
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Once we have an instance of ApplicationContext
, we can use it to get beans
:
1
Zoo zoo = context.getBean("zoo", Zoo.class);
Configuring beans
In the previous section we load a configuration file named beans.xml
. As the name of the application context suggests (ClassPathXmlApplicationContext
), the specified file is going to be loaded from the CLASSPATH
.
Let’s now look at a valid beans.xml
file:
1
2
3
4
5
6
7
8
9
10
11
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="elephant" class="diexample.Elephant"></bean>
<bean id="zoo" class="diexample.Zoo">
<constructor-arg ref="elephant" />
</bean>
</beans>
We can see that there is a top-level xml element called beans
where we load the schema for the file. Inside, we use the bean
tag to define our beans. Let’s take a closer look at our beans:
1
<bean id="elephant" class="diexample.Elephant"></bean>
The id
attribute is used to refer to this specific bean. In the previous section we used getBeam
to retrieve the bean with the id zoo
.
The class
attribute specifies the class to be instantiated. For the elephant
bean, that’s all we need since it has a no-arguments constructor:
1
2
3
4
5
6
7
package diexample;
class Elephant implements Animal {
public void talk() {
System.out.println("Hello, I'm an elephant");
}
}
The zoo
bean uses constructor-arg
to define arguments to pass to the constructor when it’s being created:
1
2
3
<bean id="zoo" class="diexample.Zoo">
<constructor-arg ref="elephant" />
</bean>
It uses ref="elephant"
to specify that the bean with id
elephant is going to be used as first argument when constructing zoo
.
1
2
3
4
5
6
7
8
9
10
11
12
13
package diexample;
public class Zoo {
private Animal animal;
Zoo(Animal animal) {
this.animal = animal;
}
public void talk() {
animal.talk();
}
}
This is just an example of how to create beans. Spring provides many other ways (factories, setters, etc…) that I don’t cover in this article.
You can find a full example of using an xml file to configure bean at: Github: Xml Configuration
Configuring beans using code
In the previous section we used an xml file to configure our beans. This was the only option available in the early days of IoC in Spring, but we have other options now.
We can configure our application using code and annotations instead of XML files. The beans.xml
file would be replaced with a file called BeansConfiguration.java
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package diexample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeansConfiguration {
@Bean
public Animal elephant() {
return new Elephant();
}
@Bean
public Zoo zoo() {
return new Zoo(elephant());
}
}
In order to use this new configuration we also need to change the ApplicationContext
implementation:
1
2
3
4
5
6
7
8
9
10
11
12
package diexample;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DiExample {
public static void main(String args[]) {
ApplicationContext context = new AnnotationConfigApplicationContext(BeansConfiguration.class);
Zoo zoo = context.getBean("zoo", Zoo.class);
zoo.talk();
}
}
You can find the full example at: Github: Code Configuration
Configuring beans using annotations
In the previous sections we defined our beams in an XML file or in a configuration file written in Java. In this section we are going to tell Spring to scan our project for annotated classes and use those annotations to figure out how to construct beans.
Similar to what we did when we used XML for configuration, we are going to create an XML file. The difference is that this time we are only going to tell Spring to scan the project for beans. The XML file looks like this:
1
2
3
4
5
6
7
8
9
10
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="diexample" />
</beans>
Now we need to add the annotations to our classes.
Elephant.java
:
1
2
3
4
5
6
7
8
9
10
package diexample;
import org.springframework.stereotype.Service;
@Service
class Elephant implements Animal {
public void talk() {
System.out.println("Hello, I'm an elephant");
}
}
Zoo.java
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package diexample;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service("zoo")
public class Zoo {
private Animal animal;
@Autowired
Zoo(Animal animal) {
this.animal = animal;
}
public void talk() {
animal.talk();
}
}
By annotating these classes, Spring will be able to figure out that it can instantiate an Elephant
by using a default constructor and it can instantiate a Zoo
by passing it an Elephant
.
The main file stays the same:
1
2
3
4
5
6
7
8
9
10
11
12
package diexample;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DiExample {
public static void main(String args[]) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Zoo zoo = context.getBean("zoo", Zoo.class);
zoo.talk();
}
}
You can find the full example at: Github: Annotation Configuration
It’s also possible to combine the code configuration with annotations. We just need to change our BeansConfiguration.java
file to look like this:
1
2
3
4
5
6
7
8
package diexample;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"diexample"})
@Configuration
public class BeansConfiguration {}
You can find the full example at: Github: Code and Annotation Configuration
Conclusion
After reading this article we should understand how Spring beans are created and injected.
There are many options that we didnt’ cover, but they follow the same pattern of creating an application context where we specify how beans are created and injected.
architecture
design_patterns
java
programming
]