Null properties in RequestScope bean in Spring Boot and Kotlin - spring

Spring Boot 2.7.2
Kotlin: 221-1.7.10-release-333-IJ5591.52
#SpringBootApplication
class AbcApplication
fun main(args: Array<String>) {
runApplication<AbcApplication>(*args)
}
#RestController
class Home(val importantContext: ImportantContext) {
#GetMapping
fun index(): Any {
return "toString: '${importantContext}' VS message: '${importantContext.importantMessage}'";
}
}
#Configuration
class ImportantConfiguration {
#Bean
#RequestScope
fun importantContext(): ImportantContext {
return ImportantContext("important content")
}
}
open class ImportantContext(var importantMessage: String?) {
override fun toString(): String {
return "ImportantContext(importantMessage=$importantMessage)"
}
}
Calling GET http://localhost:8080 returns toString: 'ImportantContext(importantMessage=important content)' VS message: 'null'
where I would expect message to be the same, not null. Maybe it is some proxy related issue. I'm not that good in Kotlin yet to figure this out by myself.
EDIT1
I rewrote ImportantContext in Java and it works correctly (importantMessage field actually has value assigned). Question remains, why it does not work in Kotlin?

Related

Reactive #PreAuthroize on service not evaluated in test

I want to test that access is denied by a service, but the #PreAuthorize is not evaluated. I am probably missing some configuration, but I cannot figure out what.
This is the service
#Service
class FooServiceImpl : FooService {
#PreAuthorize("denyAll")
suspend fun bar() {
println("I am not accessible")
}
}
This is the test where I would expect an AccessDeniedException:
#ExtendWith(SpringExtension::class, MockKExtension::class)
#Import(TestSecurityConfig::class)
internal class FooServiceImplTest {
#InjectMockKs
lateinit var fooService: FooServiceImpl
#Test
fun shouldDeny() {
runBlocking {
assertThrows<Exception> {
fooService.bar()
}
}
}
}
This is my imported test config:
#TestConfiguration
#EnableReactiveMethodSecurity
#EnableWebFluxSecurity
class TestSecurityConfig {
#Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
return http {
csrf { disable() }
formLogin { disable() }
httpBasic { disable() }
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
}
}
The test fails:
Expected java.lang.Exception to be thrown, but nothing was thrown.
I also tried adding #EnableGlobalMethodSecurity(prePostEnabled = true) (but as far as I understood this is not required when using #EnableReactiveMethodSecurity?) and also tried adding the annotations directly on the test class.
I forgot to think about how the service under test is created. In the setup of the question the #InjectMockks of course creates the instance directly instead of in a spring contest. Without the context, no security is interwoven.
#ExtendWith(SpringExtension::class, MockKExtension::class)
#Import(TestSecurityConfig::class)
#ContextConfiguration(classes = [FooServiceImpl::class])
internal class FooServiceImplTest {
#Autowired
lateinit var fooService: FooService
// ...
}
Note 1: The FooServiceImpl now is selected via the #ContextConfiguration and then #Autowired to fooService by refering to it's interface.
Note 2: Previously I was using #MockK for dependencies (not shown in the question) of the FooService. They now must be #MockKBeans.

IntelliJ complaining about Autoworing

I've got a class like this (from Spring Java Config: how do you create a prototype-scoped #Bean with runtime arguments?)
#Configuration
public class ServiceConfig {
#Bean
public Function<DataObj, MyClass> myClassFactory() {
return data-> myClass(data);
}
#Bean
#Scope(value = "prototype")
public MyClass myClass(DataObj data) {
return new MyClass(data);
}
}
However, IntelliJ (2020.3) complains about DataObj data - Could not autowire. No beans of 'DataObj' type found. DataObj is not a bean. It is a runtime object that passes data to the construction of the bean. Even if I leave this IntelliJ error as an error (as opposed to a warning), it still works. Why is IntelliJ complaining about this? Is there a way to suppress the error (without turning off the inspection)?

Switch bean by changing properties in Spring boot

I have one interface MyInterface, and 2 implementation beans: FirstImpl & SeconImpl. I want to switch between using these 2 implementations while program is running without restarting it, by only changing a property in application.properties file, e.g: interface.bean.default=FirstImpl change to interface.bean.default=SecondImpl.
Anyone knows how to do that with Spring boot?
You could try to use #ConditionalOnProperty:
#Configuration
public class MyInterfaceConfiguration {
#Bean
#ConditionalOnProperty(value = "my.interfacte.impl", havingValue="firstImpl")
public MyInterface firstImpl(){
return new FirstImpl();
}
#Bean
#ConditionalOnProperty(value = "my.interfacte.impl", havingValue="secondImpl")
public MyInterface secondImpl(){
return new SecondImpl();
}
}
and when you update your property in application.properties with actuator/refresh to:
my.interfacte.impl=firstImpl
you will have your FirstImpl instance. When you have:
my.interfacte.impl=secondImpl
you will have your SecondImpl.
#Hasan, your update only works if I customize it a little bit as below:
#Configuration
#RefreshScope
public class MyInterfaceConfiguration {
#Value("${my.interfacte.impl}")
String impl;
#Bean
#RefreshScope
public MyInterface getBean(){
if ("firstImpl".equals(impl)) {
return new FirstImpl();
} else if ("secondImpl".equals(impl)) {
return new SecondImpl();
}
return null;
}
}
I have to use 2 #RefreshScope at class level and bean creation method level!

Kotlin with Spring DI: lateinit property has not been initialized

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

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