1. Overview
This article focuses on Kotlin Spring dependency injection. Dependency injection facilitates decoupling among the classes of your application and provides cleaner code. Your code also becomes easier to test.
2. What is dependency injection in Spring?
Bean: An object that is instantiated, assembled, and managed by the Spring container. Otherwise, a bean is a class instance controlled by the Spring framework. See our bean instantiation article to understand more about bean instantiation.
Dependency: A class object requires objects of other classes to perform its responsibilities. We call them dependencies.
For instance, the below Car
class has a primary constructor with the variable owner
which is an instance of the Owner
class. So you can say Car
depends on the Owner
class. We refer to this as a dependency. Therefore, when the Spring container instantiates the Car
class, it injects its owner dependency.
class Car(private val owner: Owner) { } class Owner(private val name: String, private val address: String) {}
Dependency injection (DI): A process whereby the Spring container gives the bean its instance variables. Therefore, DI helps to achieve Inversion of Control (IOC). Here, The Spring container takes the responsibility of object creation and injecting its dependencies instead of the class creating the dependency objects by itself.
2.1. Types of Dependency injection in Spring
We can do dependency injection in one of the below ways
- Constructor based dependency injection
- Setter based dependency injection (or)
- Field based dependency injection
This article focuses on the above Kotlin Spring dependency injection.
3. Kotlin spring constructor injection
First, let’s see about the Kotlin spring constructor injection and then the static factory method injection. The spring container treats both injections similarly.
3.1. Injection using constructor arguments
We provide the dependencies required by the class as arguments to the constructor. So each argument represents a dependency.
@Component class Car @Autowired constructor (private val owner: Owner) { fun getOwner() : Owner { return owner } } class Owner(private val name: String, private val address: String) {}
Note that we have annotated the constructor using @Autowired. This annotation instructs the Spring framework to inject the owner
dependency into the Car
bean. However, as of Spring 4.3, you no longer need to add @Autowired annotation to the class that has only one constructor.
Since our Car
class has only one constructor, you can remove the @Autowired annotation:
@Component class Car (private val owner: Owner) { fun getOwner() : Owner { return owner } } class Owner(private val name: String, private val address: String) {}
Suppose you have a class with more than one constructor, then you should explicitly specify the @Autowired annotation to one constructor that should be used by Spring to inject dependencies.
For example, the below Car
class has two constructors. So you had to add @Autowired annotation to the constructor which you want the Spring container to use for injecting dependencies.
class Owner(val name: String, val address: String) {} class Car @Autowired constructor(val owner: Owner) { private var carName : String? = null constructor(carName: String, owner: Owner): this(owner) { this.carName = carName } }
3.2. Injection using static factory method
Suppose you want your class method to decide which object (bean) to instantiate, then you would use the static factory method. A Factory Method is a static method inside the class for creating an object. Therefore, the Spring container creates the object by calling this static factory method.
For example, the below Car
class has a static function createInstance
that takes the Owner
as an argument. Similar to constructor injection, the Spring container injects the owner
dependency while calling this static factory method.
class Car private constructor(val owner: Owner) { companion object { fun createInstance(owner: Owner) : Car{ return Car(owner) } } }
3.3. Spring constructor-arg multiple arguments injection
Constructor argument resolution matching occurs by using the argument’s type. We are using the XML configuration in this article for bean definition.
package com.tedblob class Car(owner: Owner, dealer: Dealer)
For example, the above Owner and Dealer classes do not have any inheritance relationship, meaning they are not subclasses of any common superclass, so there is no ambiguity.
Therefore, the following bean configuration works fine, and you do not need to specify the constructor argument indexes or types explicitly in the <constructor-arg/>
element. The <constructor-arg/> helps you to define the arguments of your constructor in the XML bean definition.
<beans> <bean id="car" class="com.tedblob.Car"> <constructor-arg ref="owner"/> <constructor-arg ref="dealer"/> </bean> <bean id="owner" class="com.tedblob.Owner"/> <bean id="dealer" class="com.tedblob.Dealer"/> </beans>
3.3.1. Spring constructor injection – argument type matching
Suppose you have a PurchaseStatus
class with boolean and string primitive variables.
package com.tedblob class PurchaseStatus(val isPurchased: Boolean, val nameOfBuyer: String) { }
If you try to specify the below bean definition, it won’t work. Because Spring cannot determine the type of the value, meaning it cannot determine whether the value=”true” belongs to String or Boolean.
<bean id="purchaseStatus" class="com.tedblob.PurchaseStatus"> <constructor-arg value="true"/> <constructor-arg value="JB"/> </bean>
Therefore, you should explicitly specify the type of the constructor argument by using the type
attribute as below:
<bean id="purchaseStatus" class="com.tedblob.PurchaseStatus"> <constructor-arg type="Boolean" value="true"/> <constructor-arg type="java.lang.String" value="JB"/> </bean>
Here, the Spring knows that the value=”true” belongs to Boolean
type and so resolves the constructor arguments accordingly.
3.3.2. Constructor injection – argument index
You can also use the index
attribute alternatively to specify the index of constructor arguments:
<bean id="purchaseStatus" class="com.tedblob.PurchaseStatus"> <constructor-arg index="0" value="true"/> <constructor-arg index="1" value="JB"/> </bean>
In the above code, index 0 refers to the boolean first argument of the constructor, index 1 refers to the string second argument of the constructor. Besides resolving the ambiguity of multiple simple values, specifying an index resolves ambiguity where a constructor has two arguments of the same type.
Consider the below class Bug
that contains two string type desc
and latestComment
arguments in the constructor.
class Bug(val desc: String, val latestComment: String)
You can resolve the ambiguity of the same type by using the index as below:
<bean id="bug" class="com.tedblob.Bug"> <constructor-arg index="0" value="Bug on UI home page"/> <constructor-arg index="1" value="In review by JB"/> </bean>
3.3.3. Constructor injection – argument name
You can also use the constructor parameter name for value disambiguation using the name
tag:
<bean id="bug" class="com.tedblob.Bug"> <constructor-arg name="desc" value="Bug on UI home page"/> <constructor-arg name="latestComment" value="In review by JB"/> </bean>
Note that this will work only if you enable debug flag in your app. Alternatively, you can use the @ConstructorProperties JDK annotation to name your constructor arguments as below:
package com.tedblob class Bug @ConstructorProperties("desc", "latestComment") constructor(val desc: String, val latestComment: String)
4. Kotlin setter based dependency injection
You can provide the required dependencies as method arguments to your class rather than using the constructor or static factory method arguments.
Annotate your setter method with the @Autorwired annotation.
Spring container will look at the @Autowired annotation and calls your setter method to inject the required dependencies.
First, let’s look at the below example. The updateBug
setter method has the @Autowired annotation and the Spring container invokes this setter method and injects the desc
and latestComment
dependencies.
Note that the variables have the lateinit specifier to show that the Spring container will assign the variable at a later point in time.
class Bug { lateinit var desc: String; lateinit var latestComment: String; // a setter method so that the Spring container can inject desc and latestComment dependencies @Autowired fun updateBug(desc: String, latestComment: String) { this.desc = desc; this.latestComment = latestComment; } }
5. Kotlin field based dependency injection
You can assign the required dependencies directly to the fields by annotating them with @Autowired annotation.
Let’s take a similar example in Kotlin. We have @Autowired annotation to the field variables desc
and latestComment
. The Spring container looks for the @Autowired annotation in the field variables and injects the dependencies.
class Bug { @Autowired lateinit var desc: String @Autowired lateinit var latestComment: String }
6. Conclusion
In this article, we have discussed the Kotlin Spring dependency injection with examples.
Pingback: Kotlin spring commandlinerunner - TedBlob