1. Introduction to constructor-based dependency injection
In this article, we’ll discuss Constructor-Based Dependency Injection within Spring. This type of dependency injection means – the required dependencies are passed into a class at the time of creation of object (or instantiation).
The fields or member variables in a class are the dependencies which a class needs. In constructor based dependency injection, these dependencies are passed into the class at the time of instantiation. Since we know that the constructor is used for instantiation, these dependencies are passed into the class when it’s constructor is executed.
When we use Spring, the injection of dependencies is taken care by the Spring.
Let us now understand constructor-based dependency injection with the help of an example.
Suppose we have a car. Every car needs an engine. We can say that car has a dependency on engine. When this relationship is represented with the help of classes will look something like the following:
public class Car { Engine engine; public Car(Engine engine) { this.engine = engine; } }
When the engine is passed into the car at the time of instantiation with the help of constructor, it is called as constructor-based dependency injection.
Before we demonstrate this, we have to do the following:
- Specify
spring-context
dependency inpom.xml
.
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.8</version> </dependency>
- Configuration of our application either by Configuration file or XML file.
2. Annotation based configuration
We’ll first create the Engine class. This class has three fields, manufacturer, type and cylinders.
package com.learnitweb.dependency; public class Engine { String manufacturer; String type; int cylinders; public Engine(String manufacturer, String type, int cylinders) { super(); this.manufacturer = manufacturer; this.type = type; this.cylinders = cylinders; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String getType() { return type; } public void setType(String type) { this.type = type; } public int getCylinders() { return cylinders; } public void setCylinders(int cylinders) { this.cylinders = cylinders; } @Override public String toString() { return "Engine [manufacturer=" + manufacturer + ", type=" + type + ", cylinders=" + cylinders + "]"; } }
We’ll now create Car class.
package com.learnitweb.components; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.learnitweb.dependency.Engine; @Component public class Car { Engine engine; @Autowired public Car(Engine engine) { this.engine = engine; } @Override public String toString() { return "Car [engine=" + engine + "]"; } }
@Component
indicates that an annotated class is a “component”. Such classes are considered as candidates for auto-detection when using annotation-based configuration and classpath scanning.@Autowired
indicates the constructor to autowire when used as a Spring bean.
Let’s do the configuration now.
package com.learnitweb.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import com.learnitweb.dependency.Engine; @Configuration @ComponentScan("com.learnitweb.components") public class AppConfig { @Bean public Engine engine() { return new Engine("Audi", "automatic", 4); } }
@Configuration
indicates that this is the configuration class.
@ComponentScan
is used to specify the packages to scan.
@Bean
is used to declare a bean. Simply annotate a method with the @Bean
annotation. When JavaConfig encounters such a method, it will execute that method and register the return value as a bean within a BeanFactory.
Let’s now test our constructor based dependency injection.
package com.learnitweb.ConstructorInjectionExample; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.learnitweb.components.Car; import com.learnitweb.configuration.AppConfig; public class App { public static void main( String[] args ) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Car car = context.getBean(Car.class); System.out.println(car); } }
Output
Car [engine=Engine [manufacturer=Audi, type=automatic, cycliners=4]]
3. XML based configuration
In XML based configuration, we’ll use XML to configure. We can remove all annotations used in previous example and Configuration class.
package com.learnitweb.components; import com.learnitweb.dependency.Engine; public class Car { Engine engine; public Car(Engine engine) { this.engine = engine; } @Override public String toString() { return "Car [engine=" + engine + "]"; } }
package com.learnitweb.dependency; public class Engine { String manufacturer; String type; int cylinders; public Engine(String manufacturer, String type, int cylinders) { super(); this.manufacturer = manufacturer; this.type = type; this.cylinders = cylinders; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String getType() { return type; } public void setType(String type) { this.type = type; } public int getCylinders() { return cylinders; } public void setCylinders(int cylinders) { this.cylinders = cylinders; } @Override public String toString() { return "Engine [manufacturer=" + manufacturer + ", type=" + type + ", cylinders=" + cylinders + "]"; } }
package com.learnitweb.constructorInjectionXml; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.learnitweb.components.Car; public class App { public static void main( String[] args ){ ApplicationContext ctx = new ClassPathXmlApplicationContext("app-context.xml"); Car car = ctx.getBean("audi", Car.class); System.out.println(car); } }
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="audi" class="com.learnitweb.components.Car"> <constructor-arg index="0" ref="engine" /> </bean> <bean id="engine" class="com.learnitweb.dependency.Engine"> <constructor-arg index="0" value="audi" /> <constructor-arg index="1" value="automatic" /> <constructor-arg index="2" value="4" /> </bean> </beans>
constructor-arg is used to provide the constructor based dependencies. constructor-arg’s index attribute accepts a literal value and indicates the index position of argument in constructor. ref attribute is used to refer to another bean. index and type are optional attributes which help in resolving ambiguity when constructor takes multiple arguments of the same type.
Note
As of Spring 4.3, classes with a single constructor can omit the @Autowired
annotation.
4. Constructor Argument Resolution
Constructor argument resolution matching occurs by using the argument’s type. The order in which the arguments are supplied to the constructor of the bean to be instantiated is same as the order in which the constructor arguments are defined in a bean definition.
Both the following are valid:
public Engine(String manufacturer, String type, int cycliners) { super(); this.manufacturer = manufacturer; this.type = type; this.cycliners = cycliners; }
<bean id="engine" class="com.learnitweb.dependency.Engine"> <constructor-arg value="audi" /> <constructor-arg value="automatic" /> <constructor-arg value="4" /> </bean>
4.1 Constructor argument type matching
We can explicitly specify the type of the constructor argument by using the type
attribute, to remove any ambiguity. For example:
<bean id="engine" class="com.learnitweb.dependency.Engine"> <constructor-arg type="java.lang.String" value="audi" /> <constructor-arg type="java.lang.String" value="automatic" /> <constructor-arg type="int" value="4" /> </bean>
4.2 Constructor argument index
You can use the index
attribute to specify explicitly the index of constructor arguments. For example:
<bean id="engine" class="com.learnitweb.dependency.Engine"> <constructor-arg index="0" value="audi" /> <constructor-arg index="1" value="automatic" /> <constructor-arg index="2" value="4" /> </bean>
The index is 0-based.
4.3 Constructor argument name
There is one more attribute name, which is the constructor parameter name and can be used to remove ambiguity. For example:
<bean id="engine" class="com.learnitweb.dependency.Engine"> <constructor-arg name="manufacturer" value="audi" /> <constructor-arg name="type" value="automatic" /> <constructor-arg name="cylinders" value="4" /> </bean>
5. When to use constructor injection?
It is advised to use constructor based dependency injection for all mandatory dependencies. Constructor injection ensures all mandatory dependencies have been satisfied and the object is never instantiated in an invalid state.