Is it possible to do something like following in Kotlin?
#Autowired
internal var mongoTemplate: MongoTemplate
#Autowired
internal var solrClient: SolrClient
Recommended approach to do Dependency Injection in Spring is constructor injection:
#Component
class YourBean(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
) {
// code
}
Prior to Spring 4.3 constructor should be explicitly annotated with Autowired:
#Component
class YourBean #Autowired constructor(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
) {
// code
}
In rare cases, you might like to use field injection, and you can do it with the help of lateinit:
#Component
class YourBean {
#Autowired
private lateinit var mongoTemplate: MongoTemplate
#Autowired
private lateinit var solrClient: SolrClient
}
Constructor injection checks all dependencies at bean creation time and all injected fields is val, at other hand lateinit injected fields can be only var, and have little runtime overhead. And to test class with constructor, you don't need reflection.
Links:
Documentation on lateinit
Documentation on constructors
Developing Spring Boot applications with Kotlin
Yes, java annotations are supported in Kotlin mostly as in Java.
One gotcha is annotations on the primary constructor requires the explicit 'constructor' keyword:
From https://kotlinlang.org/docs/reference/annotations.html
If you need to annotate the primary constructor of a class, you need to add the constructor keyword to the constructor declaration, and add the annotations before it:
class Foo #Inject constructor(dependency: MyDependency) {
// ...
}
You can also autowire dependencies through the constructor. Remember to annotate your dependencies with #Configuration, #Component, #Service etc
import org.springframework.stereotype.Component
#Component
class Foo (private val dependency: MyDependency) {
//...
}
like that
#Component class Girl( #Autowired var outfit: Outfit)
If you want property injection but don't like lateinit var, here is my solution using property delegate:
private lateinit var ctx: ApplicationContext
#Component
private class CtxVarConfigurer : ApplicationContextAware {
override fun setApplicationContext(context: ApplicationContext) {
ctx = context
}
}
inline fun <reified T : Any> autowired(name: String? = null) = Autowired(T::class.java, name)
class Autowired<T : Any>(private val javaType: Class<T>, private val name: String?) {
private val value by lazy {
if (name == null) {
ctx.getBean(javaType)
} else {
ctx.getBean(name, javaType)
}
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
}
Then you can use the much better by delegate syntax:
#Service
class MyService {
private val serviceToBeInjected: ServiceA by autowired()
private val ambiguousBean: AmbiguousService by autowired("qualifier")
}
Related
Let me show what I mean by code.
Interface:
interface MyInterface {
}
I have 2 implementation classes:
#Singleton
class Implementation1 #Inject constructor(
private val gson: Gson,
) : MyInterface {
}
#Singleton
class Implementation2 #Inject constructor(
#ApplicationContext private val context: Context,
) : MyInterface {
}
I need a repository class with list of implementations of MyInterface:
#Singleton
class MyRepository #Inject constructor(
private val implementations: List<MyInterface>,
) {
}
Problem part: I am trying to inject as below:
#InstallIn(SingletonComponent::class)
#Module
object MyDiModule {
#Provides
fun providesImplementations(
imp1: Implementation1,
imp2: Implementation2,
): List<MyInterface> {
return listOf(imp1, imp2)
}
}
But I get compilation error:
/home/sayantan/AndroidStudioProjects/example/app/build/generated/hilt/component_sources/debug/com/example/ExampleApplication_HiltComponents.java:188: error: [Dagger/MissingBinding] java.util.List<? extends com.example.MyInterface> cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint,
^
Any way to achieve this?
This might be late but would like to drop my thoughts.
Firstly, you need to annotate your #Provides methods in the module class because dagger would certainly throw an error of multiple bindings of MyInterface.
For example:
enum class ImplType {
Impl1, Impl2
}
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class ImplTypeAnnotation(val type: ImplType)
#Singleton
#Provides
#ImplTypeAnnotation(ImplType.Impl1)
fun provideImplementation1(impl1: Implementation1): MyInterface
// Do the same for Implementation2
Secondly, once these are done, you need to individually inject them into the Repository class. e.g.
class MyRepository #Inject constructor(
#ImplTypeAnnotation(ImplType.Impl1) private val implementation1: MyInterface,
#ImplTypeAnnotation(ImplType.Impl2) private val implementation2: MyInterface
) {
// get the implementations as list here using Kotlin listOf()
}
NB: I'm not sure if providing List<MyInterface> would work.
Hope this helps!
I do not know how correct this answer is, but it works for me, so posting as answer.
This can be done using the annotation #JvmSuppressWildcards.
In the above example, everything is fine, I just need to add this annotation to the class where the dependencies are being injected, i.e. the MyRepository class.
#JvmSuppressWildcards
#Singleton
class MyRepository #Inject constructor(
private val implementations: List<MyInterface>,
) {
}
I was seeing the same problem in the project I've been working on, but moving from List<> to Array<> seemed to fix it for me.
So in your case the hilt injection would look like:
#Provides
fun providesImplementations(
imp1: Implementation1,
imp2: Implementation2,
): Array<MyInterface> {
return arrayOf(imp1, imp2)
}
Then used with:
#Singleton
class MyRepository #Inject constructor(
private val implementations: Array<MyInterface>,
) {
}
I have the following class in file A:
#Service
class MyService(
private val myLoader: Loader
) {
fun load(myKey: SomeKey) =
myLoader.loadStuff(myKey)
}
I want to call that function in another file B like so:
MyService.load(myKey)
However, the load() method is marked red in IntelliJ. The error says "Unresolved reference: load" and I can't figure out why. When I type MyService. IntelliJ even suggests the load method.
How can I resolve this issue?
Since you are using Spring, the other component that should call MyService must also be a Spring-managed Bean so that it can get a hold of the MyService bean. Your component in the file B should look as follows:
#Service
class MyServiceB (
private val myService: MyService
) {
fun test(myKey: SomeKey) = myService.load(myKey)
}
Mind the #Service annotation that makes it a Spring-managed Bean and also the fact that MyService is an argument of MyServiceB constructor, telling Spring that a Bean of type MyService must be injected.
Given that you have an object instead of a class, you will have to do something like the following:
object MyServiceB (
lateinit var myService: MyService
) {
fun test(myKey: SomeKey) = myService.load(myKey)
}
#Configuration
class MyServiceBConfiguration(private val myService: MyService) {
#Bean
fun myServiceB(): MyServiceB {
return MyServiceB.also {
it.myService = myService
}
}
}
This should work but I definitely do not recommend it. This is a hack and definitely not a clean solution. Consider my initial suggestion.
I am having trouble using my application.properties values while testing.
Here's my code:
#Repository
class RedisSubscriptionStore(): SubscriptionStore {
#Value("\${default.package}")
lateinit var defaultPackageForFCMInstance: String }
and this works as expected. It's used in my updateSubscription method with the correct value from application.properties
The problem is in my test when I use the class the value is not instantiated and throws an error
#SpringBootTest()
#Tag("integration")
internal class RedisSubscriptionStoreIntegTest #Autowired constructor(
val objectMapper: ObjectMapper,
val redisTemplate: RedisTemplate<String, String>
) {
val sut = RedisSubscriptionStore(redisTemplate, objectMapper)
#Test
fun `return 200 when updating subscription`() {
sut.updateSubscription(updatedSub)
//Assert
val newlyUpdatedSub = getSubscription(updatedSub.peerID)
Assertions.assertEquals(updatedSub, newlyUpdatedSub)
}
but this throws the error: lateinit property defaultPackageForFCMInstance has not been initialized
even though this works with the correct value:
#SpringBootTest()
#Tag("integration")
internal class RedisSubscriptionStoreIntegTest #Autowired constructor(
val objectMapper: ObjectMapper,
val redisTemplate: RedisTemplate<String, String>
) {
#Value("\${default.package}")
lateinit var defaultPackageForFCMInstance: String
}
so why is my defaultPackageForFCMInstance not initialized when calling from my test class? It obviously have a value, I've tried printing it out in the test class before calling it from sut
Your are instantiating the RedisSubscriptionStore
So there is no way for Spring to inject value. Either also pass the value in the constructor or use injecation in the test
#Autowired
var sut: RedisSubscriptionStore
I am trying to write unit tests for a Spring boot service using JUnit 4 and Mockito.
I used constructor based dependency injection for my service, The Signature is:
class VbServiceImp(val jdbcTemplate: NamedParameterJdbcTemplate,
val nuanceService: NuanceService,
val conf: AppConfigProps,
val eventService: EventServiceImp,
val audioTrimService: AudioTrimServiceIF,
val vbNuanceStagingDeletionsService: VbNuanceStagingDeletionsService) : VbService {...}
in another part of the application This service gets injected into a controller and somehow spring just magically knows what to inject without me specifying this (Any idea how this works/explanation would be appreciated, guess it's based on component scan?)
example:
class VbController(val vbService: VbService) {...}
Now in my VBServiceImpl Unit test class I try to mock all the above dependencies before declaring vBService in order to manually inject all dependencies into VBService during declaration.
the relevant part of my test class looks like this:
#RunWith(SpringRunner::class)
#ContextConfiguration()
class VBServiceTests {
#MockBean
val jdbcTemplate: NamedParameterJdbcTemplate = mock()
#MockBean
val nuanceService: NuanceService = mock()
#MockBean
val appconfigProps: AppConfigProps = AppConfigProps()
#MockBean
val eventService: EventServiceImp = mock()
#MockBean
val audioTrimService: AudioTrimService = mock()
#MockBean
val vbNuanceStagingDeletionsService: VbNuanceStagingDeletionsService = mock()
val vbService: VbServiceImp = VbServiceImp(jdbcTemplate, nuanceService, appconfigProps, eventService, audioTrimService, vbNuanceStagingDeletionsService)
#SpyBean
val vbServiceSpy: VbServiceImp = Mockito.spy(vbService)
#Before
fun setup() {
initMocks(this)
}
When I run a test I get the exception below. If I understand this correctly there is already a bean of type jdbcTemplate in the application context and therefore I can't define the #Mockbean jdbcTemplate above?
exception:
private final org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate com.cc.ff.vb.service.VBServiceTests.jdbcTemplate cannot have an existing value
So now the issue is: If I removed the #MockBean jdbcTemplate variable then I can't inject jdbcTemplate when I declare vbService in my test class. So how could I get around this/make this work?
Just to check I removed the jdbcTemplate parameter from the vbService class constructor and changed it to a #Autowired field injected class variable and provided the mock class using #TestConfig. This worked however then the exception popped up on the next constructor parameter (NuanceService)
i'm out of ideas and google hasn't returned anything of value. Do I remove all constructor injected dependencies and then make them field injected using #Autowired and then provide the beans in the nested #TestConfig annotated class or is there a better/cleaner way? AFAIK field based injection is supposed to be bad practice?
example of providing correct bean for testing #Autowired field injected jdbcTemplate variable:
#TestConfiguration
class testConfig {
#Bean
fun jdbcTemplate(): NamedParameterJdbcTemplate {
return mock<NamedParameterJdbcTemplate>()
}
}
This is how I ended up making it work:
#TestConfiguration
class testConfig {
#Bean
fun jdbcTemplate(): NamedParameterJdbcTemplate {
return mock<NamedParameterJdbcTemplate>()
}
#Bean
fun nuanceService(): NuanceService {
return mock<NuanceService>()
}
#Bean
fun appConfigProps(): AppConfigProps {
return mock<AppConfigProps>()
}
#Bean
fun eventService(): EventServiceImp {
return mock<EventServiceImp>()
}
#Bean
fun audioTrimService(): AudioTrimService {
return mock<AudioTrimService>()
}
#Bean
fun vbNuanceStagingDeletionService(): VbNuanceStagingDeletionsService {
return mock<VbNuanceStagingDeletionsService>()
}
}
#MockBean
lateinit var nuanceService: NuanceService
#SpyBean
lateinit var vbServiceSpy: VbServiceImp
I'm still not sure if this is the best/optimal way of going about this so would appreciate some more details...
From this question, it is possible to inject map with enums?
For example, i have enum:
class enum SomeEnum (val value) {
ONE("one"),
TWO("two"),
THREE("three")
}
And i have some interface with implementations:
interface SomeInterface {
}
#Component
#Qualifier("one")
class OneClass: SomeInterface {
...
}
#Component
#Qualifier("two")
class TwoClass: SomeInterface {
...
}
#Component
#Qualifier("three")
class ThreeClass: SomeInterface {
...
}
But such injection not works:
#Component
#ConfigurationProperties
class SomeProperties {
#Autowired
lateinit var someMap: Map<SomeEnum, SomeInterface>
}
I want to autoinject someMap. How can i fix it, to make such code on spring framework side?
var someMap: Map<SomeEnum, SomeInterface> = Map.of(ONE, oneClass,
TWO, twoClass,
THREE, threeClass)
// Where oneClass, twoClass, threeClass - beans instances
First of all you misuse #Qualifier annotation. It's intended not to name the bean but to help Spring to choose single autowire candidate among multiple. #Qualifier is only meaningful at injection point - either in pair with #Autowired for class properties
#Autowired
#Qualifier("one")
lateinit var someImpl: SomeInterface
or for injection into method/constructor arguments
#Bean
fun createSomeService(#Qualifier("two") someImpl: SomeInterface): SomeService {
return SomeService(someImpl)
}
//or
class SomeService(#Qualifier("three") private val someImpl: SomeInterface) {
...
}
Correct way to name a bean is simply #Component("name") (or #Service("name"))
As far as I know, you can only autowire Maps with String keys, where key is the name of the bean. However you can easily convert this map into enum-key map like this:
interface SomeInterface
#Component("one")
class OneClass: SomeInterface
#Component("two")
class TwoClass: SomeInterface
#Component("three")
class ThreeClass: SomeInterface
#Component
#ConfigurationProperties
class SomeProperties(implMap: Map<String, SomeInterface>) {
val someMap: Map<SomeEnum, SomeInterface> = implMap.mapKeys {
SomeEnum.values().firstOrNull { e -> e.value == it.key }!!
}
}
UPDATED:
or using field injection (not recommended)
#Component
#ConfigurationProperties
class SomeProperties {
private lateinit var someMap: Map<SomeEnum, SomeInterface>
#Autowired
private lateinit var implMap: Map<String, SomeInterface>
#PostConstruct
fun initMap() {
someMap = implMap.mapKeys {
SomeEnum.values().firstOrNull { e -> e.value == it.key }!!
}
}
}
Not quite sure what you want to do, but from my point of view you dont need this mapping. I assume you want to know which implementation to use for certain case. So just autowire a list or set of your interface and iterate trough it to find the right implementation. (I show the stuff in Java)
#Autowired
List<SomeInterface> someInterfaces;
In this list you will have all the injected implementations of this interface. If you still need an Enum to see which implementation to use, just add this as an attribute to every of your implementation class. So you can get the Enum value by its implementation.
EDIT:
Create a config class and autowire the list of implementations. In this config class you create a Bean of your map.
#Configuration
public class MyMapConfig {
#Autowired
List<SomeInterface> someInterfaces;
#Bean
public Map<SomeEnum, SomeInterface> myMap() {
Map<SomeEnum, SomeInterface> map = new HashMap<>();
someInterfaces.forEach(s -> {
// logic here to add correct Enum to its implementation.
map.put(SomeEnum.A, s);
});
return map;
}
public enum SomeEnum {
A, B, C
}
}
Then you can autowire your map anywhere you want:
#Autowired
Map<SomeEnum, SomeInterface> myMap;