Kotlin with Spring DI: lateinit property has not been initialized - spring

I don't get Spring-based setter dependency injection in Kotlin to work as it always terminates with the error message "lateinit property api has not been initialized". I could reduce the problem to the following scenario: There is an interface
interface IApi {
fun retrieveContent(): String
}
which is implemented by
class Api : IApi {
override fun retrieveContent() = "Some Content"
}
I want to use the implementation in another class where the dependency injection is supposed to take place:
#Component
class SomeController {
#Autowired lateinit var api: IApi
fun printReceivedContent() {
print(api.retrieveContent())
}
}
However, the application terminates with the above-mentioned error message. My Spring config looks as follows:
#Configuration
open class DIConfig {
#Bean
open fun getApiInstance(): IApi = Api()
}
In the main function I load the application context and call the method:
fun main(args: Array<String>) {
val context = AnnotationConfigApplicationContext()
context.register(DIConfig::class.java)
context.refresh()
val controller = SomeController()
controller.printReceivedContent()
}
What is the problem here?

Spring isn't involved if you just call the constructor yourself like that. Same as in Java,
val controller = context.getBean(SomeController::class.java)
Spring Framework 5.0 adds Kotlin extensions, so you could also write either one of
val controller = context.getBean<SomeController>()
val controller: SomeController = context.getBean()

your api is currently no a bean managed by spring, try annotating it with #Service or #Component

The #Autowired is usually added to the setter of a property. So instead of using it for the property, you should explicitly annotate the setter:
#set:Autowired lateinit var api: IApi

Related

Is there a way to overide automatically injected beans in Spring boot when writing tests?

I have a class annotated with a spring bean #Repository("clientDatasource") called ClientServiceDatasource which implements an interface called Datasource. I also have a mock implementation of this interface also annotated with a spring bean #Repository("mockDatasource") called MockClientServiceDatasource. I also have a class annotated with the spring bean #Service called ClientService and in in its constructor, I pass in a datasource. I do it like so:
#Service
class ClientService (#Qualifier("clientDatasource") private val dataSource: Datasource){}
As you can see that the service will default to the clientDatasource, because of the #Qualifier when the application is running.
However when I run my tests I annotate my test class with #SpringTest . In my understanding this means that it boots up the entire application as if it were normal. So I want to somehow overide that #Qualifier bean thats being used in the client service in my test so that the Client Service would then use the mockedDatasource class.
I'm fairly new to kotlin and spring. So I looked around and found ways to write a testConfig class to configure beans like so :
#TestConfiguration
class TestConfig {
#Bean
#Qualifier("clientDatasource")
fun mockDatasource(): Datasource {
return MockClientServiceDatasource()
}
}
and then using it in the test like so:
#SpringTest
#Import(TestConfig::class)
class ClientServiceTest {
...
}
I also asked chatGPT and it gave me this:
#SpringBootTest
class ClientServiceTest {
#Autowired
lateinit var context: ApplicationContext
#Test
fun testWithMockedDatasource() {
// Override the clientDatasource bean definition with the mockDatasource bean
val mockDatasource = context.getBean("mockDatasource", Datasource::class.java)
val mockClientDatasourceDefinition = BeanDefinitionBuilder.genericBeanDefinition(MockClientServiceDatasource::class.java)
.addConstructorArgValue(mockDatasource)
.beanDefinition
context.registerBeanDefinition("clientDatasource", mockClientDatasourceDefinition)
// Now the ClientService should use the mockDatasource when it's constructed
val clientService = context.getBean(ClientService::class.java)
// ... do assertions and other test logic here ...
}
}
But some of the methods don't work, I guess chatGPT knowledge is outdated.
I also looked through spring docs, but couldn't find anything useful.
Okay, So I took a look at the code previously with the TestConfig class. And I realised by adding the:
#Primary
annotation to the method inside my TestConfig class, it basically forces that to be the primary repository bean. Like so:
#TestConfiguration
class TestConfiguration {
#Bean
#Primary
#Qualifier("clientDatasource")
fun mockDatasource(): Datasource {
return MockClientDataSource()
}
}
and in the test I only imported the test and it just worked. I didn't have to autowire anything
This is my test class:
#SpringBootTest
#AutoConfigureMockMvc
#Import(TestConfiguration::class)
internal class ServiceControllerTest{
#Suppress("SpringJavaInjectionPointsAutowiringInspection")
#Autowired
lateinit var mockMvc: MockMvc
#Test
fun `should return all clients` () {
// when/then
mockMvc.get("/clients")
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON) }
jsonPath("$[0].first_name") {value("John")}
}
}
}

spring boot startup process failed with lateinit var not initialized : DI Issue

I'm buidling a backend that will fetch some data from several externals APIs, to populate DB after some processes, and then expose these data via a rest api. Using JPA and postgresql, in springboot.
I have created the entities, repositories and fetch external apis from a webclient.
However, I have a dependency injection issue. I read tons of articles and issues, but can not make it work. When trying to inject the repository, I got the well known error lateinit var not initialized.
I also tried constructor injection, but still doesn't work. It seems that the repository is not considered to be a bean that could be autowired.
Any help would be appreciated
FIXED SEE SOLUTION BELOW. PB WAS IN THE SERVICE
Application. Running the startuprocess from the context
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val startupProcess = context.getBean(StartupProcesses::class.java)
startupProcess.fetchGroup()
}
startup class as #component :fetches external api and calls a service to save data in db
#Component
class StartupProcesses {
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
SplitDataSourceToEntities().populateGroup(fetch) //<-- call service to populate db
}
}
Service that should populates the DB through repository but error in lateinit repo
#Service
class SplitDataSourceToEntities {
#Autowired
lateinit var repo:IronMaidenGroupRepository // <-- error is here
fun populateGroup(dto: DTOIronMaidenAPi): IronMaidenGroupEntity {
val dbgroup = IronMaidenGroupEntity(
groupId = dto.id!!,
name = dto.name ?: ""
)
return repo.save(dbgroup)
}
}
the repo, extending JPA repository
import com.jerome.springbootkotlin.model.dbentities.ironmaidengroupentity.IronMaidenGroupEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
#Repository
interface IronMaidenGroupRepository:JpaRepository<IronMaidenGroupEntity, String> {
}
SOLUTION
Service should be defined like this (the SplitDataSourceToEntitiesclass should also be late init instead of beeing instantiated)
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities // <--add this lateinit
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource()
splitDataSourceToEntities.populateGroup(fetch) //<-- fix is here
}
}
#Autowired works only when managed by Spring
It is important to note, that #Autowired does only work when the class has been initialized by Spring. In your code, you have an #Autowired annotation in class SplitDataSourceToEntities, but you manually instantiate SplitDataSourceToEntities in StartupProcesses.fetchGroup. That cannot work, because Spring had no possibility to auto-wire the lateinit var.
The problem can easily be solved by using an autowired instance of your service:
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
splitDataSourceToEntities.populateGroup(fetch) //<-- call service to populate db
}
}
Help Spring finding your JpaRepository
Additionally, you probably need to add an #EnableJpaRepositories annotation to your application:
#EnableJpaRepositories(basePackageClasses = [IronMaidenGroupRepository::class])
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
...
Instead of basePackageClasses = ... you can just define the package directly by name, but the package name is not included in your example.
Do you have to use #Autowired at all?
Considering your question in the comments:
There is nothing wrong with your design, because it is not not necessary to "play" with #Component and #Autowired. You could as well get rid of the #Autowired annotations and define those variables as, e.g., constructor parameters.
That could look like this:
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val repo = context.getBean(IronMaidenGroupRepository::class.java)
StartupProcesses(SplitDataSourceToEntities(repo))
}
#Component
class StartupProcesses(
val splitDataSourceToEntities: SplitDataSourceToEntities
) {
...
}
#Service
class SplitDataSourceToEntities(
val repo: IronMaidenGroupRepository
) {
...
}
Now only your Repository is managed by Spring, but on the flip-side you have to manage everything else by yourself which might get very tedious when your classes and their dependencies grow. It is much more comfortable (and in the end leads to better readable code) to let just Spring manage all the dependencies.

Unable to use #Autowired in #Configuration constructor, bean not found

I'm trying to land a very simple project to see how complicated is to use Spring on Scala, but so far I can't really do the same stuff I simply did in Java, probably the way Scala instantiates the objects and such... So, some context regarding my environment.
#SpringBootApplication
object MyRunner {
def main(args: Array[String]): Unit = {
SpringApplication.run(classOf[Clazz], args: _*)
}
}
Not much to say here, dummy class to launch the following class.
#Configuration
#ConfigurationProperties("sample")
#EnableConfigurationProperties
class Clazz #Autowired() (#BeanProperty var innerBean: InnerBean) extends Serializable with ApplicationRunner {
#BeanProperty
var property: String = _
override def run(args: ApplicationArguments): Unit = {
args.getSourceArgs
}
}
And here is where I'm having the issue related to my question. When I try to autowire InnerBean (#Autowired() (#BeanProperty var innerBean: InnerBean)), which is defined this way.
#Component
class InnerBean extends Serializable {
#BeanProperty
var beanValueOne: String = _
#BeanProperty
var beanValueTwo: String = _
}
It will just complain with Parameter 0 of constructor in project.impl.Clazz required a bean of type 'project.impl.InnerBean' that could not be found.. All those three classes are in the same .scala file.
Also, application.properties look like this.
sample.property=readingFromProperties
beanValueOne=readingFromProperties
beanValueTwo=readingFromProperties
I know that #SpringBootApplication already does a #ComponentScan in advance, so any beans within the package and below should be loaded for those to be available across?
The MyRunner object's SpringApplication.run call should reference a class with the #SpringBootApplication (which could be named 'MyRunner', but it's not necessary). Having the annotation on the object is probably where it is going wrong.
Ex:
#SpringBootApplication
class MyRunner
object MyRunner {
def main(args: Array[String]): Unit =
SpringApplication.run(classOf[MyRunner], args: _*)
}
#Configuration declares a source of #Beans
You've declared Clazz with #Configuration then tried to #Autowire in InnerBean which is marked with #Component. #Component to my knowledge is the same as #Bean but one is outside the scope of configuration so perhaps the point of generation is different in the Spring lifecycle.
How to fix...
Remove #Autowired() (#BeanProperty var innerBean: InnerBean) from your original code. Everything should compile and run if all else is well.
Put #Autowired() (#BeanProperty var innerBean: InnerBean) into a new class and create an instance of the class in the #Configuration file - by declaring a method that returns a NewClazz and annotating it with #Bean.

How to get variable from spring application.yaml in kotlin

I have app.name in my application.yaml
How do i access this please, I was hoping for a simple one liner.
#Values doesn't seem to work, it only works in my restcontroller. I've created a configurationproperties and a EnableConfigurationProperties, but no one will tell me how to actually get a value from the properties.
class databaseHandler() {
#Value("\${app.url}")
private val url: String? = null
fun dave(){
print(url)
} ==Null
==
#Component
#ConfigurationProperties("app")
class appConfig(){
lateinit var url: String
}
#EnableConfigurationProperties(appConfig::class)
class dave(){
fun dave(){
print(appConfig().url)
} ==Lateinit url hasn't been initalized
}
Figured out the issue, You need to initialise your sub-class as well and also declare it as a spring component e.g.
#Service
class DatabaseHelper {
#Value("\${app.url}")
val url= ""
}
#RestController
#RequestMapping("/")
class GitHubController{
#Autowired
lateinit var databaseHelper : DatabaseHelper
#GetMapping("")
fun hello() = "hello $databaseHelper.url"
}
Assuming application.yml has the following properties:
app-url: www.demo-url.com
Add#EnableConfigurationProperties to your binding class i.e.
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
class appConfig() {
lateinit var url: String
}
#EnableConfigurationProperties this annotation is used to enable #ConfigurationProperties annotated beans in the Spring application
Please refer: https://www.baeldung.com/spring-yaml

Kotlin spring-boot #ConfigurationProperties

I'm trying to create the following bean AmazonDynamoDBAsyncClientProvider. I've application.properties that defines endpoint and tablePrefix which I'm trying to inject using #ConfigurationProperties
Following is the code snippet for the same. When I run my spring-boot app it doesn't work.
I've tried doing the same ConfigurationProperties class using a regular java class which does set those properties but when it comes to AmazonDynamoDBAsyncClientProvider, the properties are empty. What am I missing here?
#Component
open class AmazonDynamoDBAsyncClientProvider #Autowired constructor(val dynamoDBConfiguration: DynamoDBConfig){
#Bean open fun getAmazonDBAsync() = AmazonDynamoDBAsyncClientBuilder.standard()
.withEndpointConfiguration(
AwsClientBuilder.EndpointConfiguration(dynamoDBConfiguration.endpoint, dynamoDBConfiguration.prefix))
.build()
}
here is the kotlin bean that I'm trying to autowire with configuration
#Component
#ConfigurationProperties(value = "dynamo")
open class DynamoDBConfig(var endpoint: String="", var prefix: String="")
finally heres the regular java bean that does get populated with ConfigurationProperties but when it gets Autowired I see those properties being empty/null
#Component
#ConfigurationProperties("dynamo")
public class DynamoDBConfiguration {
private String endpoint;
private String tablePrefix;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getTablePrefix() {
return tablePrefix;
}
public void setTablePrefix(String tablePrefix) {
this.tablePrefix = tablePrefix;
}
}
Have you tried getting rid of the #Component annotation on your ConfigurationProperties class?
Here is what I have done with Kotlin and Spring, hope it helps.
I am trying to leverage the kotlin-spring and kotlin-allopen gradle plugin
dependencies {
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion"
}
apply plugin: 'kotlin-spring'
apply plugin: 'kotlin-noarg'
noArg {
annotation("your.noarg.annotation.package.NoArg")
}
They do make spring development with kotlin a lot easier.
#ConfigurationProperties("dynamo")
#NoArg
data class DynamoDBConfiguration(var endpoint: String, var prefix: String)
I tried your configuration class and it gets populated. I think your mistake is in the way you are trying to create the bean, the function needs to be in a class annotated with #Configuration, this should work:
#Configuration
class Beans {
#Bean
fun getAmazonDBAsync(config: DynamoDBConfiguration) =
AmazonDynamoDBAsyncClientBuilder.standard()
.withEndpointConfiguration(
AwsClientBuilder.EndpointConfiguration(config.endpoint, config.prefix)
)
.build()
}
Spring will inject the config for you, as long as you annotate the config with #Component, like you did above.
I had a similar problem and fixed it this way:
I defined the configuration properties class with lateinit vars:
#ConfigurationProperties(prefix = "app")
open class ApplicationConfigProperties {
lateinit var publicUrl: String
}
Then configured a bean in my spring boot application:
#SpringBootApplication
open class Application {
#Bean open fun appConfigProperties() = ApplicationConfigProperties()
}

Resources