Difficulty in #Value Injection in #PropertySource in Kotlin/SpringBoot - spring-boot

I am new to Kotlin. So I was trying to write a #PropertySource class with #Value injection in Spring Boot, so that it can be used elsewhere.
I have written the class like this:
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.PropertySource
#Configuration
#PropertySource("classpath:app-properties.properties")
class AppProperties {
companion object {
#Value("\${app.storage.types}")
lateinit var appStorageType : String
#Value("\${app.device.supported-protocols}")
lateinit var appDeviceSupportedProtocols : String
.
.
.
.
.
.
}
}
But when I run the application, I get the error in my:
{
"error": {
"message": [
"lateinit property appStorageType has not been initialized"
]
}
}
I am trying to fetch the properties in other classes like:
AppProperties.appStorageType
When I was in Java we had getters and setters to do it. What is the equivalent in Kotlin?
Any help would be appreciated.

Just initialize this properties in app-properties.properties file..
Create app-properties.properties file and put this line
app.storage.types.appStorageType=some value
app.device.supported-protocols.appDeviceSupportedProtocols=other value

Related

How to use variables from application.yaml in SpringBoot kotlin

I need to use variables declared in my applications.yaml file, as an example all it is:
num_error:
value: "error"
result: 1
And I have a class trying to call it like the following:
#ConfigurationProperties(prefix = "num_error")
#Component
class NumError {
companion object {
lateinit var value: String
lateinit var result: Number
}
}
However, when I try and call this class using NumError.value I get an the following error
lateinit property value has not been initialized
kotlin.UninitializedPropertyAccessException: lateinit property value has not been initialized
What have I done wrong, why is this error happening?
You do not need to have companion object, and since Spring boot 2.2 you can have ConstructorBinding to make it work.
#ConstructorBinding
#ConfigurationProperties(prefix = "num_error")
data class NumError(
val value: String, val result: Number
)
Make sure you include following dependency
dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
}
EDIT
For older versions, define the variables directly in the class instead of companion object.
#Configuration
#ConfigurationProperties(prefix = "num_error")
class NumError {
var value: String = "some default value",
var result: Number? = null
}

Spring custom #ConditionalOnProperty annotation can not use #AliasFor

I want to create FeatureFlag annotation for my project to avoid code repetitions.
I created a new annotation called FeatureFlag. I decorated with ConditionalOnProperty annotation with the generic prefix foo.features. I add new fields to the annotation, which is AliasFor the ConditionalOnProperty fields. As far as I know, the following code should work, but it does not. I also tested the aliasing on the Profile annotation and that is working.
import io.kotlintest.shouldBe
import org.junit.jupiter.api.Test
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.AliasFor
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#ConditionalOnProperty(prefix = "foo.features")
annotation class FeatureFlag(
#get:AliasFor(annotation = ConditionalOnProperty::class, value = "name") val feature: String,
#get:AliasFor(annotation = ConditionalOnProperty::class, value = "havingValue") val enabled: String = "true"
)
#SpringBootTest(
properties = ["foo.features.dummy: true"],
classes = [FeatureFlagTest.FeatureFlagTestConfiguration::class]
)
class FeatureFlagTest(private val applicationContext: ApplicationContext) {
#Configuration
class FeatureFlagTestConfiguration {
#Bean
#FeatureFlag(feature = "dummy")
fun positive(): String = "positive"
#Bean
#FeatureFlag(feature = "dummy", enabled = "false")
fun negative(): String = "negative"
}
#Test
fun `test`() {
applicationContext.getBean(String::class.java) shouldBe "positive"
}
}
When I running the test case I get the following error:
java.lang.IllegalStateException: The name or value attribute of #ConditionalOnProperty must be specified
(The FeatureFlag annotation should contain the value of the name field.)
Can you help, what did I wrong? Is it a bug in the framework?
In addition to the prefix attribute, you also need to define the name or value attribute in the ConditionalOnProperty annotation in order for it to work.
Have a look here to see the details of the annotation you're using: https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.html

Spring Validation of JSON - Why do I need to add `#field`

I've finally made some progress on Spring validation (on a JSON object coming in from RabbitMQ).
However there are a couple of things I don't understand:
In the documentation, it states I can just use the annotation #NotBlank then in my method I use the annotation #Valid. However I find this wasn't doing anything. So instead I did #field:NotBlank and it worked together with the following - why did this #field do the trick?
#JsonIgnoreProperties(ignoreUnknown = true)
data class MyModel (
#field:NotBlank(message = "ID cannot be blank")
val id : String = "",
#field:NotBlank(message = "s3FilePath cannot be blank")
val s3FilePath : String = ""
)
Then the function using this model:
#Service
class Listener {
#RabbitListener(queues = ["\${newsong.queue}"])
fun received(data: MyModel) {
val factory = Validation.buildDefaultValidatorFactory()
val validator = factory.validator
val validate = validator.validate(data)
// Then this `validate` will return an array of validation errors
println(validate)
}
}
Correct me if I'm wrong however I assumed just using #Valid and this point fun received(#Valid data: MyModel) it would just throw some exception for me to catch - any idea based on my code why this could have been?
Any advice/help would be appreciated.
Thanks.
Here are the imports:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import javax.validation.*
import org.springframework.amqp.rabbit.core.RabbitTemplate
import org.springframework.amqp.rabbit.annotation.RabbitListener
import javax.validation.constraints.NotBlank
Quoting Kotlin's documentation for annotations:
When you're annotating a property or a primary constructor parameter, there are multiple Java elements which are generated from the corresponding Kotlin element, and therefore multiple possible locations for the annotation in the generated Java bytecode. To specify how exactly the annotation should be generated, use the following syntax:
class Example(#field:Ann val foo, // annotate Java field
#get:Ann val bar, // annotate Java getter
#param:Ann val quux) // annotate Java constructor parameter
So, until explicitly mention what you are annotating (field, getter or something else) in Kotlin class constructor, it won't automatically know where you want to put that annotation.

Inject properties name into class anotation

Is it possible to inject property name into the procedureName?
im using spring boot.
Try to use the next the next construction:
procedureName = "${procedure}" but it doesnt work
Also to write the special PropertySourcesPlaceholderConfigurer i think it not a good idea .
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(name = "test",
procedureName = "${procedure}",
parameters = {
})
})
public class R
try to get property from properties-test.yml
Spring properties used to inject values in bean properties like below,
public class ClassWithInjectedProperty {
#Value("${props.foo}")
private String foo;
}
you case is not valid for value injection.

Using Scala classes as DTOs in Spring MVC

In my project I'm using Spring + Scala.
Some of my Spring MVC controllers uses Spring feature for binding incoming HTTP parameters to DTO object. Like this:
#RequestMapping(value = Array("/", ""), method = Array(RequestMethod.POST))
def saveProduct(dto: MyDto): Iterable[MyDto] = {...}
And MyDto is simple scala class:
class MyDto extends Serializable {
#BeanProperty var id : Long = _
#BeanProperty var name: String = _
}
My problem is that I'm getting exceptions when trying to use Scala Option class for fields in MyDto:
class MyDto extends Serializable {
#BeanProperty var id : Option[Long] = None
#BeanProperty var name: Option[String] = None
}
Exception message is:
Failed to convert property value of type 'java.lang.String' to required type 'scala.Option' for property 'name';
What I can do to use Scala Options as type if fields in MyDto?
I am not a Scala expert, but here is one way:
Create a converter, along these lines:
import org.springframework.core.convert.converter.Converter
class TypeToOptionOfTypeConverter[T] extends Converter[T, Option[T]] {
override def convert(source: T): Option[T] = {
Some(source)
}
}
Register this converter with Spring MVC:
class WebConfig extends WebMvcConfigurerAdapter {
override def addFormatters(registry: FormatterRegistry): Unit = {
registry.addConverter(classOf[String], classOf[Option[String]], new TypeToOptionOfTypeConverter[String])
registry.addConverter(classOf[Long], classOf[Option[Long]], new TypeToOptionOfTypeConverter[Long])
}
}
That should be it, now your DTO should get cleanly mapped.
Spring has support for converting types using converters with its data binding. You will need to implement the converter so that Spring knows how to convert, for example, String to Option[String].
See:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert

Resources