1. Overview
In this article, we’ll introduce the concepts of IoC (Inversion of Control) and DI (Dependency Injection), and we’ll then take a look at how these are implemented in the Spring framework.
2. What is Inversion of Control?
Inversion of Control is a principle in software engineering by which the control of objects or portions of a program is transferred to a container or framework. It is most often used in the context of object-oriented programming.
By contrast with traditional programming, in which our custom code makes calls to a library, IoC enables a framework to take control of the flow of a program and make calls to our custom code. In order to make this possible, frameworks are defined through abstractions with additional behavior built in. If we want to add our own behavior, we need to extend the classes of the framework or plugin our own classes.
The advantages of this architecture are:
- decoupling the execution of a task from its implementation
- making it easier to switch between different implementations
- greater modularity of a program
- greater ease in testing a program by isolating a component or mocking its dependencies and allowing components to communicate through contracts
Inversion of Control can be achieved through various mechanisms such as: Strategy design pattern, Service Locator pattern, Factory pattern, and Dependency Injection (DI).
We’re going to look at DI next.
3. What is Dependency Injection?
Dependency injection is a pattern through which to implement IoC, where the control being inverted is the setting of object’s dependencies.
The act of connecting objects with other objects, or “injecting” objects into other objects, is done by an assembler rather than by the objects themselves.
Here’s how you would create an object dependency in traditional programming:
public class Store { private Item item; public Store() { item = new ItemImpl1(); } }
In the example above, you can see that in order to instantiate the item dependency of a Store object you have to specify which implementation of interface Item we will use within the Store class itself.
By using DI, we can rewrite the example without specifying the implementation of Item that we want:
public class Store { private Item item; public Store(Item item) { this.item = item; } }
The implementation of Item to be injected will then be provided through metadata, as we will see in the examples below.
Both IoC and DI are simple concepts, but have deep implications in the way we structure our systems, so they’re well worth understanding well.
4. The Spring IoC Container
An IoC container is a common characteristic of frameworks that implement IoC.
In the Spring framework, the IoC container is represented by the interface ApplicationContext. The Spring container is responsible for instantiating, configuring and assembling objects known as beans, as well as managing their lifecycle.
The Spring framework provides several implementations of the ApplicationContext interface — ClassPathXmlApplicationContext and FileSystemXmlApplicationContext for standalone applications, and WebApplicationContext for web applications.
In order to assemble beans, the container uses configuration metadata, which can be in the form of XML configuration or annotations.
Here is one way to manually instantiate a container:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
5. Dependency Injection in Spring
In order to set the item attribute in the example above, we can use metadata that will be used for assembling objects at runtime by the Spring container.
Dependency Injection in Spring can be done through constructors or setters.
5.1. Constructor-Based Dependency Injection
In the case of constructor-based dependency injection, the container will invoke a constructor with arguments each representing a dependency we want to set.
Resolving each argument is done primarily by the type of the argument, followed by name of the attribute and index for disambiguation. The configuration of a bean and its dependencies can be done using annotations:
@Configuration public class AppConfig { @Bean public Item item1() { return new ItemImpl1(); } @Bean public Store store() { return new Store(item1()); } }
The @Configuration annotation indicates that the class is a source of bean definitions and can be used on multiple configuration classes. The @Bean annotation is used on a method to define a bean. If you do not specify a custom name, the method name will be used.
If using the default singleton scope for a bean, each time a method annotated with @Bean is called, Spring checks first if a cached instance of the bean already exists and only creates a new one if it doesn’t exist. If the prototype scope is specified, then a new bean instance is returned for each call of the method.
Another way to create the configuration of the beans is through XML configuration:
<bean id="item1" class="org.baeldung.store.ItemImpl1" /> <bean id="store" class="org.baeldung.store.Store"> <constructor-arg type="ItemImpl1" index="0" name="item" ref="item1" /> </bean>
5.2. Setter-Based Dependency Injection
For setter-based DI, the container will call setter methods of your class, after invoking a no-argument constructor or no-argument static factory method to instantiate the bean. Let’s create this configuration using annotations:
@Bean public Store store() { Store store = new Store(); store.setItem(item1()); return store; }
We can also use XML for the same configuration of beans:
<bean id="store" class="org.baeldung.store.Store"> <property name="item" ref="item1" /> </bean>
Constructor-based and setter-based types of injection can be combined for the same bean. It is recommended by the Spring documentation to use constructor-based injection for mandatory dependencies, and setter-based injection for optional ones.
5.3. Autowiring Dependencies
Wiring allows the Spring container to automatically resolve dependencies between collaborating beans by inspecting the beans that have been defined.
There are four modes of autowiring a bean using an XML configuration:
- no: the default value – this means no autowiring is used for the bean and the dependencies have to be explicitly named
- byName: autowiring is done based on the name of the property, therefore Spring will look for a bean with the same name as the property that needs to be set
- byType: similar to the byName autowiring, only based on the type of the property, which means Spring will look for a bean with the same type of the property to set; if more than one bean of that type is found, the framework throws an exception
- constructor: autowiring is done based on constructor arguments, meaning Spring will look for beans with the same type as the constructor arguments
For example, let’s autowire the item1 bean defined above by type into the store bean:
@Bean(autowire = Autowire.BY_TYPE) public class Store { private Item item; public setItem(Item item){ this.item = item; } }
You can also inject beans using the @Autowired annotation for autowiring by type:
public class Store { @Autowired private Item item; }
If there is more than one bean of the same type, you can add the @Qualifier annotation to reference a bean by name:
public class Store { @Autowired @Qualifier("item1") private Item item; }
Let’s autowire beans by type through XML configuration:
<bean id="store" class="org.baeldung.store.Store" autowire="byType"> </bean>
Now let’s inject a bean named item into the item property of store bean by name through XML:
<bean id="item" class="org.baeldung.store.ItemImpl1" /> <bean id="store" class="org.baeldung.store.Store" autowire="byName"> </bean>
You can also override the autowiring by defining dependencies explicitly through constructor arguments or setters.
5.4. Lazy Initialized Beans
By default, the container creates and configures all singleton beans during initialization. To avoid this, you can specify the lazy-init attribute with value true on the bean configuration:
<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />
As a consequence, the item1 bean will be initialized only when it is first requested, and not at startup. The advantage of this is faster initialization time, but the trade-off is that configuration errors may be discovered only after the bean is requested, which could be several hours or even days after the application has already been running.
6. Conclusion
In this article, we have presented the concepts of inversion of control and dependency injection and exemplified them in the Spring framework.
You can read more about these concepts in Martin Fowler’s articles:
And you can learn more about the Spring implementations of IoC and DI in the Spring Framework Reference Documentation.