This annotation is not applicable to target 'local variable - spring

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

Related

Spring Boot Kotlin - Injecting a map from a YAML file

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}

How to interpolate property values provided by custom PropertySource in Spring Boot?

I have my custom FooPropertySources that extends EnumerablePropertySource. I add all of these in the #Configuration class to the ConfigurableEnvironment and they are correctly picked up be application and all the values are resolved.
However, if some values contain placeholders, they're not being interpolated. I thought I should use PropertySourcesPlaceholderConfigurer to solve that problem, but it seems like this configurer is meant to deal with placeholders in beans, rather than in property sources.
So far I tried this:
#Configuration
#ConditionalOnProperty("foo.config.import")
open class FooConfiguration {
#Autowired
private lateinit var env: ConfigurableEnvironment;
#Value("\${foo.config.import}")
private lateinit var locationSpecifier: String;
#PostConstruct
private fun initialize() {
val placeholderConfigurer = PropertySourcePlaceholderConfigurer();
val beanFactory = DefaultListableBeanFactory();
this.resolvePropertySources(this.parseLocationSpecifier())
.forEach(this.env.propertySources::addFirst);
placeholderConfigurer.setEnvironment(this.env);
placeholderConfigurer.postProcessBeanFactory(beanFactory);
}
internal fun resolvePropertySources(path: Path): Set<FooPropertySource> {
//...
return ...;
}
internal fun parseLocationSpecifier(): Path {
//...
return path;
}
}
Now, if an instance of FooPropertySource contains these properties:
firstname = John
lastname = Doe
fullname = ${firstname} ${lastname}
I'd like, in the end, when my application calls to env.getProperty("fullname") it will get the string "John Doe", rather than "${firstname} ${lastname}".
Any hopes to resolve that problem? I'm struggling with it for third day already… :-(
I guess you could create an extension function
fun ConfigurableEnvironment.fullname() = "${getProperty("firstname")} ${getProperty("lastname")}"

Spring auto configuration tries to set some bean values from application.properties while I just want to use that informations somewhere else?

So I use keycloak for my application and I have some values in application.properties like:
keycloak.auth-server-url = http://10.10.10.10:1010/auth
keycloak.resource = test-client
keycloak.credentials.secret = <very-big-secret>
keycloak.realm = test-realm
Spring configure the keycloak connection using these data, but I also use them in my code so I have a config like this:
#Data
#Configuration
#ConfigurationProperties(prefix = "keycloak")
public class KeycloakConfig {
private String authServerUrl;
private String realm;
private String resource;
private Credentials credentials;
}
I have an admin user in keycloak and I want it's credentials in the application.properties like this:
keycloak.admin.username=admin.admin
keycloak.admin.password=changeit
So I tried to change my config class to this:
#Data
#Configuration
#ConfigurationProperties(prefix = "keycloak")
public class KeycloakConfig {
private String authServerUrl;
private String realm;
private String resource;
private Credentials credentials;
private Admin admin;
}
#Data
public class Admin {
private String username;
private String password;
}
But when I try to run the application like this, I think the spring tries to set the values for keycloak (the .admin part) and it does not start:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target [Bindable#1cd5e41 type = org.keycloak.adapters.springboot.KeycloakSpringBootProperties, value = 'provided', annotations = array<Annotation>[#org.springframework.boot.context.properties.ConfigurationProperties(ignoreInvalidFields=false, ignoreUnknownFields=false, prefix=keycloak, value=keycloak)]] failed:
Property: keycloak.admin.password
Value: changeit
Origin: "keycloak.admin.password" from property source "applicationConfig: [classpath:/application.properties]"
Reason: The elements [keycloak.admin.password,keycloak.admin.username] were left unbound.
Property: keycloak.admin.username
Value: admin.admin
Origin: "keycloak.admin.username" from property source "applicationConfig: [classpath:/application.properties]"
Reason: The elements [keycloak.admin.password,keycloak.admin.username] were left unbound.
Action:
Update your application's configuration
Is it possible to have the .admin part under keycloak or I have to make a new class for example:
#Data
#Configuration
#ConfigurationProperties(prefix = "my-keycloak")
public class MyKeycloakConfig {
private Admin admin;
}
And:
my-keycloak.admin.username=admin.admin
my-keycloak.admin.password=changeit
I am not familiar with KeyCloak, but you can inject the bean that initialized by KeyCloak that reads the properties.
Keycloak reads values from application properties using KeycloakSpringBootProperties. Looks like there are no such values as username or password. Probably Keycloak doesn't require those values to work properly.
So you need to specify the properties seperately from keycloak.
No, you cannot customize keycloak.* "domain" in spring-boot (loaded) properties!
Proof: KeycloakSpringBootProperties, which says:
#ConfigurationProperties(prefix = "keycloak", ignoreUnknownFields = false)
So it is definitely the second approach!
By defining (in application.properties):
my-keycloak.admin.username=admin.admin
my-keycloak.admin.password=changeit
a) ... You can just go for:
#Value("${my-keycloak.admin.xxx}")
private String myKeacloakXXX;
b) Or as described by Typesafe Configuration Properties (and implemented by [1] for prefix="keycloak"):
You (just) have to introduce a "pojo" like (depicting your properties structure(type safe)):
#ConfigurationProperties("my-keycloak.admin")
public class MyKeykloakProperties {
private String username, password; // getter, setter/lombok
}
You can have also more structure with "my-keykloak" (prefix, and nesting classes/properties, see exmaple/doc)
To enable them:
#Configuration
// Or:
#EnableConfigurationProperties(MyKeykloakProperties.class)
// OR:
//#ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyKeycloakConfig { ...
see also Enabling.
Then you can "wire" them as you see fit (also in the above config class):
#Autowired
private MyKeykloakProperties properties;
As a decision help, please refer to: #Value vs type safe.
Cheers

How can I inject config properties into a unit test, using SpringBoot2, JUnit5, and Kotlin

My scenario:
I'm building an app that uses Kotlin and SpringBoot 2.0.3. I'm trying to write all my unit tests in JUnit5. All 3 of these are new to me, so I'm struggling a bit.
I'm using a #ConfigurationProperties class (instead of #Value) to inject values from my application.yml into my Spring context.
#Configuration
#ConfigurationProperties(prefix = "amazon.aws.s3")
class AmazonS3Config {
val s3Enabled: Boolean = false
val region: String = ""
val accessKeyId: String = ""
val secretAccessKey: String = ""
val bucketName: String = ""
}
I then have a Kotlin class that is utilizing these properties, following Kotlin/Spring best practice to define the injected class as a constructor parameter.
class VqsS3FileReader(val amazonS3Config: AmazonS3Config) : VqsFileReader {
companion object: mu.KLogging()
override fun getInputStream(filePath: String): InputStream {
val region: String = amazonS3Config.region
val accessKeyId: String = amazonS3Config.accessKeyId
val secretAccessKey: String = amazonS3Config.secretAccessKey
val bucketName: String = amazonS3Config.bucketName
logger.debug { "The configured s3Enabled is: $s3Enabled" }
logger.debug { "The configured region is: $region" }
logger.debug { "The configured accessKeyId is: $accessKeyId" }
logger.debug { "The configured secretAccessKey is: $secretAccessKey" }
logger.debug { "The configured bucketName is: $bucketName" }
val file: File? = File(filePath)
//This method is not yet implemented, just read a file from local disk for now
return file?.inputStream() ?: throw FileNotFoundException("File at $filePath is null")
}
}
I have not completed this implementation, as I'm trying to get the unit test working first. So for the moment, this method doesn't actually reach out to S3, just streams a local file.
My unit test is where I'm getting stuck. I don't know how to inject the properties from my application.yml into the test context. Since the ConfigProperty class is passed as a construction parameter, I have to pass it when I establish my service in my unit test. I've tried various solutions that don't work. I found this piece of info, which was helpful:
If Spring Boot is being used, then #ConfigurationProperties instead of #Value annotations can be used, but currently this only works with lateinit or nullable var properties (the former is recommended) since immutable classes initialized by constructors are not yet supported.
So this means I cannot use class VqsS3FileReaderTest(amazonS3Config: AmazonS3Config): TestBase() { ... } and then pass the config to my service.
This is what I have currently:
#ActiveProfiles("test")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ExtendWith(SpringExtension::class)
#ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])
class VqsS3FileReaderTest(): TestBase() {
#Autowired
private lateinit var amazonS3Config: AmazonS3Config
#Autowired
private lateinit var fileReader: VqsS3FileReader
val filePath: String = "/fileio/sampleLocalFile.txt"
#Test
fun `can get input stream from a valid file path` () {
fileReader = VqsS3FileReader(amazonS3Config)
val sampleLocalFile: File? = getFile(filePath) //getFile is defined in the TestBase class, it just gets a file in my "resources" dir
if (sampleLocalFile != null) {
val inStream: InputStream = fileReader.getInputStream(sampleLocalFile.absolutePath)
val content: String = inStream.readBytes().toString(Charset.defaultCharset())
assert.that(content, startsWith("Lorem Ipsum"))
} else {
fail { "The file at $filePath was not found." }
}
}
}
With this, my test runs, and my context seems to setup properly, but the properties from my application.yml are not being injected. For my debug output, I see the following:
08:46:43.111 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured s3Enabled is: false
08:46:43.111 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured region is:
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured accessKeyId is:
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured secretAccessKey is:
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured bucketName is:
All empty strings, which is the default values. Not the values I have in my application.yml:
amazon.aws.s3:
s3Enabled: true
region: us-west-2
accessKeyId: unknown-at-this-time
secretAccessKey: unknown-at-this-time
bucketName: test-bucket
I see mistake in the following line:
#ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])
Please put configuration classes here (instead of just beans).
Short - hot to fix test
Create class (if missing) like VqsS3Configration in the main module (e.g. in the module, where you have production code)
Create class like VqsS3TestConfigration in the same package with your tests. Content on this file:
#org.springframework.context.annotation.Configuration // mark, that this is configuration class
#org.springframework.context.annotation.Import(VqsS3Configration::class) // it references production configuration from test configuration
#org.springframework.context.annotation.ComponentScan // ask Spring to autoload all files from the package with VqsS3TestConfigration and all child packages
class VqsS3TestConfigration {
/*put test-related beans here in future*/
}
Then go to test and change declaration:
#ContextConfiguration(classes = [VqsS3TestConfigration ::class]) // we ask Spring to load configuration here
I created sample application here: https://github.com/imanushin/spring-boot2-junit5-and-kotlin-integration
Please execude line .\gradlew.bat test or gradlew.bat bootRun in the src folder. Test will check, that we able to read properties. bootRun will print auto-loaded properties
Boring theory
First of all - Spring has Configuration classes - they are needed to load and initialize other classes. Instead of Service or Comonent classes, main purpose of Configuration classes - just create services, components, etc.
If we will simplify algorithm of the Spring application load, then it will be like this:
Find Configuration classes
Read annotation of them, understand list of classes (e.g. reference tree), which should be loaded (and in addition - how they should be loaded)
Load classes with different ways:
3.1. For classes, which are annotated with #ConfigurationProperties - put configuration items here
3.2. For classes, which are annotated with #RestController - register them as rest controllers
3.N. etc...
How does Spring understand, what configuration should be loaded?
Formally is it done by Spring Boot, however I will name it as Spring
Understand several initial configurations - they can be put into the class SpringApplicationBuilder, into the test annotations (see above), into the XML context, etc. For our case we use test annotation and #ContextConfiguration attribute
Recursive get all imported configuration (e.g. Spring reads #Import annotation, then it get children, then it check their imports, etc.)
Use Spring Factories to get configuration automatically from jar
Therefore, in our case, Spring will do actions like this:
Get configuration from test annotation
Get all other configurations by recursive way
Load all classes into the contet
Start test
Okay, it took me all day, but I finally got my application properties to load into my unit test context. I made 2 changes:
First, I added the #Service annotation to my VqsS3FileReader service - which I had originally forgotten. Also, while I had updated my Test to not inject the AmazonS3Config via the constructor, I had neglected to update my service to do the same. So I changed
this:
class VqsS3FileReader(val amazonS3Config: AmazonS3Config) : VqsFileReader {
companion object: mu.KLogging()
...
to this:
#Service
class VqsS3FileReader : VqsFileReader {
companion object: mu.KLogging()
#Resource
private lateinit var amazonS3Config: AmazonS3Config
...
Finally, I modified my Spring annotations on my test.
from this:
#ActiveProfiles("test")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ExtendWith(SpringExtension::class)
#ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])
class VqsS3FileReaderTest(): TestBase() {
...
to this:
#ActiveProfiles("test")
#SpringBootTest
#ComponentScan("com.ilmn.*")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ExtendWith(SpringExtension::class)
#EnableAutoConfiguration
#SpringJUnitConfig(SpringBootContextLoader::class)
class VqsS3FileReaderTest(): TestBase() {
...
It seems like I have an unordinary amount of annotations on my test now... so I will be looking carefully at what each of them really do, and see if I can reduce it. But at least my properties are being injected into my test context now.

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.

Resources