Spring with Kotlin - Inversion of Control, Dependency Injection
Being a
Polyglot Programmer comes with challenges. You need to learn new stuff as you go along and most importantly brush up old skills! A couple of years back I purchased a 42-hour
long course on Spring and Hibernate. It occurred to me that I never finished the course and it was just lying there in my Udemy archives. A moment of realization and time to act with a slight twist! I have started working with
Kotlin recently so why not code everything in this course in Kotlin?
Setup was not so different than Java as Kotlin is a JVM language. But running Spring
Boot project in any IDE is effortless as there is already a lot of inbuilt stuff like servers and other Spring project dependencies. The course though uses pure
Spring framework (most of the time) and so I had to download Eclipse as IntelliJ Community version won't let me add an application server. Sure, there is a plugin for it in
IntelliJ, but I thought let's do it the good old way. Downloaded Eclipse,
installed Kotlin plugin, created project, and imported all the Spring JAR
files. But wait, the latest Eclipse Kotlin plugin doesn't work somehow with
Spring and so I had to roll back to the previous version. Ultimately, my
code setup was ready and I went on exploring Dependency injection and
Inversion of Control of Spring Core technology in my first few modules.
Inversion of Control (IOC)
is a process of giving Spring Framework all the responsibility to create and manage Beans (Beans are nothing but Java objects).
Dependency injection (DI)
is a subset of IOC where dependencies are created and injected into beans.
All of this is done by Spring behind the scenes. Spring Core
showcases different ways to perform IOC and DI such as the
following:
- Full XML Config (no Annotations)
- XML Component scan with Annotations (mostly annotations)
- Kotlin coding with no XML (only code and Annotations)
For the purpose of this blog, I will focus more on options 2,3 as they are most commonly used. I will
refer to the following classes/methods/interfaces/files constantly in the blog:
- applicationContext.xml - .xml config file that contains bean definitions, dependencies, component scannig
- MyApp.kt - Kotlin file containing main method to run the Kotlin app
- FortuneService.kt - interface defining getFortune() method
- HappyFortuneService.kt - implements FortuneService interface
- SportConfig.kt - config class (Kotlin class alternative to applicationContext.xml file)
- Coach.kt - interface defining getDailyWorkout() and getFailyFortune() methods
- TennisCoach.kt - implements Coach interface
- sport.properties - contains literals with key-value pairs
Let's dive in!
XML Component scan with Annotations
1) Inversion of Control
2) DI - Constructor Injection
There's no need to use annotation @Autowired if you have only one
constructor. But I personally would still use it to maintain uniformity.
3) DI - Setter Injection
Setter-based injection can be done, but I wasn’t able to make it work with custom setters, moreover, field injections are a much better choice.
4) DI - Field Injection
This is one of the most widely used methods. Dependencies are injected and everything happens behind the scene using
Java reflection.
5) DI - Method Injection
You can inject dependencies in any method by annotating with
@Autowired. I think setter/field injection is already very efficient, not sure what purpose would it serve to use a separate method to inject dependencies. Maybe a dependency that only needs to use for that particular method.
6) DI - Literal Injection from .properties file
Kotlin coding with no XML (only code and Annotations)
This is where we drop the annotations that we were using previously and instead write Kotlin code. The dependency injection would look something
like following.
1) Dependency injection Kotlin Coding example 1:
2) Dependency injection Kotlin Coding example 2: No
@Compoent/@Autowired annotations
3) Dependency injection Kotlin Coding example 3: Injecting literals
from .properties file
Now, let's discuss a few more important annotations which are
applicable in both the types we discussed -
1) @Qualifier:
What happens when you have multiple implementations of a single interface? Which one Spring pick? That's when you need to explicitly tell Spring to use specific bean id using @Qualifier annotation else it
will create a conflict and throw NoUniqueBeanDefinitionException.
Here's how you use it for field and constructor injections -
2) @Scope:
The default scope of a bean is always Singleton ie. create only 1
instance of the bean, cache it in memory, and shared ref across for all
the requests. We need to explicitly use @Scope annotation to tell
Spring to use any other scope.
3) @PostConstruct & @PreDestroy:
These are the annotations to use on any method if you need some
additional functionality to execute just after the bean is created
( @PostConstruct ) or
right before the bean is destroyed ( @PreDestroy ). Note that Spring does not manage the complete lifecycle of prototype scoped bean
ie @PreDestroy won't work if the scope if prototype. We need
to implement the DisposableBean interface and add a custom bean processor. I will definitely discuss this in future blogs.
Before ending this blog here are a few important points take away -
- When retrieving bean from the container, context.getBean() make sure you use appropriate Kotlin casting or add the .java will force the compiler to use the Java class spec.
- If you are using lateinit keyword, make sure you inject the correct dependencies so that code won't blow up! In short, always make sure the variable is assigned to something that's not null.
- The default bean scope is always Singleton. You have to specify otherwise explicitly.
- If you are using Kotlin coding method (to reduce or get rid of annotations) make sure your config class and method have open modifier.
- Finally, each IOC/DI type has its pros and cons. Chose a style you are most comfortable with and follow along!
Side note - the reason I say Spring Boot is so convenient is that there is
no need for custom @ComponentScan and @Configuration and additional
Kotlin/Java coding. When you use the @SpringBootApplication, it
enables @ComponentScan @Configuration and @EnableAutoConfiguration
features. After that, you just need to put @Component over your classes and start using @Autowired for injecting dependencies.
PS - Agree, switching between JVM languages is relatively easy, but Kotlin definitely stands out with somewhat similar performance as Java and more streamlined syntax. I will try to cover my course progress in future blogs. Thanks and stay tuned!
Comments
Post a Comment