Spring series : Ioc Container and Beans
Welcome to my blog, where we explore the incredible capabilities of the Spring Framework. Whether you're a seasoned developer or just starting your programming journey, Spring Framework offers a wealth of tools and features to supercharge your Java applications. In this series we will go through the official documentation to make sure we get things from the source, we will check when we need the source code and try to understand step by step the spring framework.
First thing first let’s go through the core technologies and start by the most important part the Ioc Container.
Introduction to the Spring IoC Container and Beans
Inversion of control :
Inversion of control is also known as the dependency injection. It is a process whereby objects define their dependencies only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism.
The org.springframework.beans
and org.springframework.context
packages are the basis for Spring Framework’s IoC container."
The BeanFactory
interface is the root interface of Ioc containers provides an interface in the Spring Framework that provides a simple, yet flexible configuration mechanism to manage objects of any nature via the Spring IoC container.
ApplicationContext
is a sub-interface of BeanFactory.
Photo by – http://fatalerrors.org
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
For more details take a look here: BeanFactory interface .
You need to know The BeanFactory interface itself does not contain any methods that create objects. However, there are a number of implementations of the BeanFactory interface that do contain these methods.
The BeanFactory interface itself does not do any configuration. The configuration is done by the implementations of the BeanFactory interface. For example, the DefaultListableBeanFactory implementation of the BeanFactory interface provides methods for loading bean definitions from XML configuration files.
BeanFactory vs ApplicationContext ?
In Short and for the moment : the BeanFactory
provides the configuration framework and basic functionality, and the ApplicationContext
adds more enterprise-specific functionality.
But wait, we keep talking about beans, what is a bean actually ?
In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and managed by a Spring IoC container.Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.
Now , let’s deep dive into ApplicationContext :
The org.springframework.context.ApplicationContext
interface represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the beans.
-
The container gets its instructions on what objects to instantiate, configure, and assemble by reading configuration metadata.
-
The configuration metadata is represented in XML, Java annotations, or Java code. It lets you express the objects that compose your application and the rich interdependencies between those objects.
Several implementations of the ApplicationContext
interface are supplied with Spring:
ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, AnnotationConfigApplicationContext.
While XML has been the traditional format for defining configuration metadata, you can instruct the container to use Java annotations or code as the metadata format by providing a small amount of XML configuration to declaratively enable support for these additional metadata formats.
The following diagram shows a high-level view of how Spring works. Your application classes are combined with configuration metadata so that, after the ApplicationContext
is created and initialized, you have a fully configured and executable system or application.
Configuration metadata represents how you, as an application developer, tell the Spring container to instantiate, configure, and assemble the objects in your application.
Configuration metadata is traditionally supplied in a simple and intuitive XML format, which is what most of this chapter uses to convey key concepts and features of the Spring IoC container. The Spring IoC container itself is totally decoupled from the format in which this configuration metadata is actually written.
There are 3 formats:
- XML-based configuration.
- Annotation-based configuration.
- Java-based configuration.
Example of XML configuration:
<!-- beans1.xml -->
<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 definition for BeanA -->
<bean id="beanA" class="com.example.BeanA">
<property name="name" value="Bean A" />
</bean>
</beans>
<!-- beans2.xml -->
<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 definition for BeanB -->
<bean id="beanB" class="com.example.BeanB">
<property name="age" value="25" />
</bean>
</beans>
To compose these XML files, you can create another XML file and use the import element to include the other XML files:
<!-- application-context.xml -->
<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">
<import resource="classpath:beans1.xml" />
<import resource="classpath:beans2.xml" />
</beans>
The classpath usually contains the resources folder , so you’ll need to put the xml files there for spring to find them .
To make spring boot use the xml configuration to create the context you can use the @ImportRessource annotation.
Example using the container:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
Or using GeneralApplicationContext:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
One thing to mention before moving to next part is, There are two kinds of beans in the Spring bean container: ordinary beans and factory beans. Spring uses the former directly, whereas latter can produce objects themselves, which are managed by the framework. you can use & to retrieve the FactoryBean used to create a factory bean.
to implement a FactoryBean , you'll need to implment :
public interface FactoryBean {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
Dependency Injection :
Dependency injection (DI) is a process whereby objects define their dependencies (that is, the other objects with which they work) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean.
Code is cleaner with the DI principle, and decoupling is more effective when objects are provided with their dependencies and easier to test.
DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection:
Constructor-based Dependency Injection :
Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each representing a dependency. Calling a static
factory method with specific arguments to construct the bean is nearly equivalent, and this discussion treats arguments to a constructor and to a static
factory method similarly.
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
In the example above we suppose that ThingTwo and ThingOne have no relation (inheritance) between them so resolving them will be easy for spring, as you see , no id or index is provided .
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
But if you use primitive types , all values are passed as a string so the type need to be there is different types , otherwise the index (is zero-based) need to be there :
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
OR
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
Setter-based Dependency Injection :
Setter-based DI is accomplished by the container calling setter methods on your beans after invoking a no-argument constructor or a no-argument static
factory method to instantiate your bean.
The ApplicationContext
supports constructor-based and setter-based DI for the beans it manages. It also supports setter-based DI after some dependencies have already been injected through the constructor approach. You configure the dependencies in the form of a BeanDefinition
, which you use in conjunction with PropertyEditor
instances to convert properties from one format to another. However, most Spring users do not work with these classes directly (that is, programmatically) but rather with XML bean
definitions, annotated components (that is, classes annotated with @Component
, @Controller
, and so forth), or @Bean
methods in Java-based @Configuration
classes. These sources are then converted internally into instances of BeanDefinition
and used to load an entire Spring IoC container instance.
Constructor-based or setter-based DI?
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use :
- constructors for mandatory dependencies.
- setter methods or configuration methods for optional dependencies.
The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null
.
Code example of setter DI:
<beans>
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
</beans>
For more info about , check this: Dependencies and Configuration in Detail .
2023 Soufiane Bannouni