Spring Boot Kotlin - Injecting a map from a YAML file - spring-boot

test.yml (location: resources/properties/)
edit:
field1: test
field2: test
field3: test
field4: test
PropertyConfig.kt
#Configuration
#PropertySource("classpath:properties/test.yml")
class PropertyConfig {
#Bean
#ConfigurationProperties(prefix = "edit")
fun testProperty() = mutableMapOf<String, String>()
}
#Service
class EditService(
private val testProperty: Map<String, String>
) {
fun print() {
println(testProperty) // empty
}
}
I want to receive the values below edit as a map.
I tried options for #ConfigurationProperties with prefix and value, but it doesn't work.
If I use properties file, it works well, but not yml file.
What am I missing? Thanks.
kotlinVersion = '1.6'; springBootVersion = '2.6.1'

You are missing the #ContructorBinding annotation (required as of Spring Boot 2.2.0). Please see this answer:
#ConstructorBinding
#ConfigurationProperties("")
data class PropertyConfig(
val edit: Map<String,String>
)
If you wanna use a non-standard yml file (not called application.yml or derivate), like in the example you provided, then you need to add also the #PropertySource annotation to your Configuration data class.
#ConstructorBinding
#ConfigurationProperties("")
#PropertySource(value = "classpath:test.yml")
data class PropertyConfig(
val edit: Map<String,String>
)

Something like this (without #Bean):
#ConfigurationProperties(prefix = "") // blank since "edit:" is root element
#ConstructorBinding
data class EditProperties(
val edit: Map<String, String> // property name must match to relevant root element in YAML
)
#Service
class EditService(private val properties: EditProperties) {
fun print() {
println(properties.edit)
}
}
Output:
{field1=test, field2=test, field3=test, field4=test}

Related

Retrieve YML / YAML properties

I have a external yaml properties files that I have loaded and I want to retrieve. But the suggested way to get YAML is like so:
#Value("${some.var}");
and this isn't working.
I am loading the files in like so:
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
String userHome = System.getProperty('user.home');
ArrayList<String> locations = new ArrayList<String>(
Arrays.asList(
"${userHome}/.boot/beapi_server.yml",
"${userHome}/.boot/beapi.yml",
"${userHome}/.boot/beapi_db.yml",
"${userHome}/.boot/beapi_api.yml"
)
);
Collections.reverse(locations);
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
for (String location : locations) {
String finalLocation = location.toString();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new FileSystemResource(finalLocation));
propertySourcesPlaceholderConfigurer.setProperties(Objects.requireNonNull(yaml.getObject()));
}
return propertySourcesPlaceholderConfigurer;
}
The actuator/configprops also don't show the properties.
What am I doing wrong? A bit frustrated.
Ok so I figured it out and wanted to post the answer for others to see.
After loading the external files into your PropertySources, you can now access them through #ConfigurationProperties annotation by referencing the YML structure prefix head and then just access the properties directly through assignment
// 'tomcat' if the top level prefix in my yml file
#ConfigurationProperties(prefix="tomcat")
class something{
ArrayList jvmArgs
}
Now lets look at my yml:
tomcat:
jvmArgs:
-'-Xms1536m'
-'-Xmx2048m'
-'-XX:PermSize=256m'
-'-XX:MaxPermSize=512m'
-'-XX:MaxNewSize=256m'
-'-XX:NewSize=256m',
-'-XX:+CMSClassUnloadingEnabled'
-'-XX:+UseConcMarkSweepGC'
-'-XX:+CMSIncrementalMode'
-'-XX:+CMSIncrementalPacing'
-'-XX:CMSIncrementalDutyCycle=10'
-'-XX:+UseParNewGC'
-'-XX:MaxGCPauseMillis=200'
-'-XX:MaxGCMinorPauseMillis=50'
-'-XX:SurvivorRatio=128'
-'-XX:MaxTenuringThreshold=0'
-'-server'
-'-noverify'
-'-Xshare:off'
-'-Djava.net.preferIPv4Stack=true'
-'-XX:+EliminateLocks'
-'-XX:+ExplicitGCInvokesConcurrent'
-'-XX:+UseBiasedLocking'
-'-XX:+UseTLAB'
And we see this will directly assign the value jvmArgs to the ArrayList JvmArgs in the class above. Simple.
So, rather simple and elegant solution once you know how... but knowing how is the trick isn't it :)
i'm using yaml configuration too. in my case, you have to create a configuration class then you can autowired it.
this is my code:
PdfConfig.java
#Component
#ConfigurationProperties(prefix = "pdf")
#EnableConfigurationProperties
public class PdfConfig {
private String licensePath;
private Watermark watermark;
// setter getter goes here
}
PdfServiceImpl.java
#Service
public class PdfServiceImpl implements PdfService {
#Autowired
private PdfConfig pdfConfig;
}
application-local.yml
#### Pdf Config ####
pdf:
license-path: classpath:license/development/itextkey.xml
watermark:
image-path: classpath:static/img/logo.png
opacity: 0.2f
enabled: true
If you want to learn about setting spring yaml, you can go to this link spring-yaml
Hope this helped!

This annotation is not applicable to target 'local variable

I want to get value from application.yml, but I got "This annotation is not applicable to target 'local variable" for this part,how to solve this problem?
#Value("\${aws.secretsManager.secretName}")
val secretName: String? = ""
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
fun getSecret() {
#Value("\${aws.secretsManager.secretName}")
val secretName: String? = ""
val region = "us-west-2"
val logger: Logger = LoggerFactory.getLogger(GetSecretConfig::class.java)
// Create a Secrets Manager client
val client = AWSSecretsManagerClientBuilder.standard().withRegion(region).build()
val getSecretValueRequest = GetSecretValueRequest().withSecretId(secretName)
var getSecretValueResult: GetSecretValueResult? = try {
client.getSecretValue(getSecretValueRequest)
}
}
application.yml
aws:
secretsManager:
secretName: "test-mvp"
region: "us-west-2"
user: "root"
password: "root"
From the #Value javadoc:
Annotation used at the field or method/constructor parameter level that indicates a default value expression for the annotated element.
The #Value annotation is defined as follow:
#Target(value = {FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
#Retention(value = RUNTIME)
#Documented
public #interface Value
As you see by the #Target, the #Value annotation it's not intended to be used in a LOCAL_VARIABLE.
The solution is to define the secretName variable outside of the function - as a field of the class.
Workaround logic here:
(Using Java 8 though - shouldn't matter anyways)
Create a configuration class annotated with #Configuration like so:
#Configuration
public class ApplicationSecretsConfig {
public ApplicationSecretsConfig(){}
#Value("${aws.secretsManager.secretName}")
private String secretName;
public String getSecretName(){
return secretName;
}
}
Then in your class, autowire the SecretsConfig dependency and get the value of secretName using its getter.
// class initialization done here
...
#Autowired
ApplicationSecretsConfig applicationSecretsConfig
public String getSecret() {
String secret = applicationSecretsConfig.getSecretName();
// continue your logic
...
}
Hopefully that helps someone.
there is no need to do a custom implementation for fetch secrets. Spring provides it, using spring-cloud-starter-aws-secrets-manager-config dependency, just need to do an small config:
spring.config.import=aws-secretsmanager:my-secret
there is working sample on documentation:
https://github.com/awspring/spring-cloud-aws/tree/main/spring-cloud-aws-samples/spring-cloud-aws-parameter-store-sample
and here you could find a db working too:
https://github.com/nekperu15739/aws-secrets-manager

Trouble with Spring #ConfiurationProperties extending Mao

I am trying to load come properties from config using the Spring #Configuration & #ConfigurationProperties combination. I have a POJO that extends HashMap<Integer, String> and has a single variable inside. (See MyConfigPojo below). From the .yaml file, I have the same shape. However, when booting up the app, I get an error when trying to parse the string for the defaultValue into an Integer.
#Configuration
#ConfigurationPropeties(prefix = "my-config")
public class MyConfigPojo extends HashMap<Integer, String> {
private String defaultValue;
private String getValueForIdOrDefault(int id); // this.get(id) OR ELSE defaultValue
}
In config I have this:
myConfig:
1: "my first value"
23: "my 23rd value"
defaultValue: "cheese"
Which results in a
APPLICATION FAILED TO START
Description:
Failed to bind properties under 'myPackage' to MyConfigPojo:
Property: myConfig[1]
Value: cheese
I thought that was weird, so I turned on TRACE logs and found this:
99 common frames omittedCaused by: java.lang.NumberFormatException: For input string: "defaultValue" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
Is there a way to intercept reading this file or to tell Spring how to set the values properly?
You are trying to read a Map from .yml file, for that, you need #ConfigurationProperties annotation enough, don't need #Configuration annotation(if using spring boot). If you are using spring-framework, you need #Configuration also.
Use this example:
myConfig:
map:
1: "my first value"
23: "my 23rd value"
defaultValue: "cheese"
MyConfigPojo.java
package com.org;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
#Configuration
#ConfigurationProperties(prefix = "my-config")
public class MyConfigPojo {
private HashMap<String, String> map;
public HashMap<String, String> getMap() {
return map;
}
public void setMap(HashMap<String, String> map) {
this.map = map;
}
}
Now, you can Autowire this MyConfigPojo class anywhere(for instance, in controller class) and read those map keys and values.
#Autowired
MyConfigPojo pojo;
Now, you have considered keys & Values as String datatype for that Map, you will not get NumberFormatException.

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
}

What's the best way to load a yaml file to Map(not an environment configuration file) in spring boot web project?

In my data framework layer, I'd like to read an yaml from src/main/resources.
The file name is mapconfigure.yaml. It's associated with the business data, not just environment configuration data.
Its content's like:
person1:
name: aaa
addresses:
na: jiang
sb: su
person2:
name: bbb
addresses:
to: jiang
bit: su
I want to store this information into a HashMap.
Does it mean to use some spring annotation like #ConfigurationProperties?
How to achieve this in details?
In addition, I can't change the file name. It means I have to use mapconfigure.yaml as the file name, not application.yml or application.properties.
The structure of my HashMap is as follows:
HashMap<String, Setting>
#Data
public class Setting{
private String name;
private HashMap<String, String> addresses
}
My expected HashMap's as follows:
{person1={name=aaa, addresses={na=jiang, sb=su}}, person2={name=bbb, addresses={to=jiang, bit=su}}}
I'm not sure if I can use YamlMapFactoryBean class to do this.
The return type of the getObject method in YamlMapFactoryBean class is Map<String, Object>, not a generic type, like Map<String, T>.
Spring boot doc just said
Spring Framework provides two convenient classes that can be used to load YAML documents. The YamlPropertiesFactoryBean will load YAML as Properties and the YamlMapFactoryBean will load YAML as a Map.
But there isn't a detailed example.
UPDATE:
In github, I created a sample. It's Here.
In this sample, I want to load myconfig.yaml to theMapProperties object in SamplePropertyLoadingTest class. Spring boot version is 1.5.1, so I can't use location attribute of #ConfigurationProperties.
How to do this?
You can indeed achieve this with #ConfigurationProperties.
From Spring Boot 1.5.x onwards (lack of #ConfigurationProperies locations attr.):
new SpringApplicationBuilder(Application.class)
.properties("spring.config.name=application,your-filename")
.run(args);
#Component
#ConfigurationProperties
public class TheProperties {
private Map<String, Person> people;
// getters and setters are omitted for brevity
}
In Spring Boot 1.3.x:
#Component
#ConfigurationProperties(locations = "classpath:your-filename.yml")
public class TheProperties {
private Map<String, Person> people;
// getters and setters are omitted for brevity
}
The Person class for above examples looks like this:
public class Person {
private String name;
private Map<String, String> addresses;
// getters and setters are omitted for brevity
}
I have tested the code with the following file: your-filename.yml
defined in src/main/resources, the contents:
people:
person1:
name: "aaa"
addresses:
na: "jiang"
sb: "su"
person2:
name: "bbb"
addresses:
to: "jiang"
bit: "su"
Please let me know if you need any further assistance.
try this
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
PropertySource<?> applicationYamlPropertySource = loader.load(
"properties", new ClassPathResource("application.yml"), null);// null indicated common properties for all profiles.
Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
Properties properties = new Properties();
properties.putAll(source);
return properties;
} catch (IOException e) {
LOG.error("application.yml file cannot be found.");
}

Resources