Spring calls noargs constructor on java record for #ConfigurationProperties - spring

I'm pulling my hair out here. I want to use a Java record for my #ConfigurationProperties, providing default values to unspecified config properties. Here is a very simple example:
#ConfigurationProperties(prefix = "myconfig")
public record LoggingProperties (
String whatever,
String somethingToDefault
) {
public LoggingProperties(String whatever, String somethingToDefault) {
this.whatever = whatever;
this.somethingToDefault = somethingToDefault;
}
public LoggingProperties(String whatever) {
this(whatever, "whatever was specified, but not somethingToDefault");
}
public LoggingProperties() {
this("neither was specified", "neither was specified");
}
}
It seems, if I declare a noargs constructor, spring always calls that, regardless of what I actually have in my config file (application.yml)
The above will yield an instance, that when logged shows:
LoggingProperties[whatever=neither was specified, somethingToDefault=neither was specified], despite the fact that my config WAS specified.
If I delete the no-args constructor, I get an exception about No default constructor found;
If I add #ConstructorBinding to the allargs constructor I get:
LoggingProperties[whatever=value from file, somethingToDefault=null]. i.e. it just called the annotated constructor, ignoring the one with 1 arg (despite that prop being declared in my yml).
I'm at a loss... Is this even possible?
EDIT: in my application.yml I have:
myconfig:
whatever: "value from file"

Praise the lord for documentation (it pays to read it)
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.constructor-binding
Default values can be specified using #DefaultValue on a constructor parameter or, when using Java 16 or later, a record component. The conversion service will be applied to coerce the String value to the target type of a missing property.
So it seems I can skip the constructor mess, and just annotate the record fields with a #DefaultValue(value = "whatever default"), like so:
#ConfigurationProperties(prefix = "someprefix")
#ConstructorBinding
public record MyRecord (
#DefaultValue(value = "true")
boolean someProperty,
) {}

Related

In a spring.config.import factory how do you access properties in the invoking document?

I am creating a spring.config.import factory and can't see how to access properties from the invoking(parent) document
# mish-mash of properties without a common prefix
key1 = value1
key2 = value2
# now I want to read key1, key2 inside the factory
spring.config.import = myfactory:
Inside my implementation
#Configuration
public class MyFactoryResolver implements ConfigDataLocationResolver<MyResource>, Ordered {
#Override
public List<MyResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
// how do I get the values or key1 and key2
// tried
binder.bind("key1", String.class).get(); // FAIL
// key1 needs to be in a higher source like environment KEY1=VALUE1
}
// tried as field
#Value("${key1}")
String key1; // always null
// cannot inject; too early in life-cycle ?
}
Now spring-cloud-client can do this
spring.cloud.config.username = johndoe
spring.cloud.config.password = john's password
spring.config.import = configserver:http://myserver.com
— so clearly it is possible to read a property value from the parent doc.
But I can't see how this code works — this code is baroque/mysterious to me:
it doesn't read a property like spring.cloud.config.username simply
instead it creates a sidekick #ConfigurationProperties ClientConfigProperties bean (how?) that wraps all spring.cloud.config.* key/values. This bean can't be injected (too early in the life-cycle??), so it is retrieved from a Binder and then all the properties are available; so if these properties can be
read off a sidekick, why I can't I read them easily...
my properties don't have a standard prefix so it is not easy to create a #ConfigurationProperties sidekick
...also in the code of spring-cloud-config-client you sometimes see new ClientConfigProperties(...) — I always thought this was forbidden in DI-land as the container won't be able to manage it for you.
TL;DR - what I am looking for is a way to read property/key values from the context(binder?) of the document or a sister document that invoked the factory; without having to create a side-kick bean and forcing all properties to confirm to prefix-naming. (This is a legacy application where property names were not enforced...).
Update: I attemped to duplicate the sidekick pattern — standardised property names to a prefix and one segment, created a holder #ConfiguationProperties bean and added it as an EnableAutoConfiguration factory (copy-pasta from spring-cloud-config). Copied code:
private MyProperties resolveHook(ConfigDataLocationResolverContext context) {
// TODO Auto-generated method stub
boolean registered = context.getBootstrapContext().isRegistered(MyProperties.class);
System.out.println("RESOLVER: MyProperties is registered = " + registered);
if (registered) {
return context.getBootstrapContext().get(MyProperties.class);
}
Binder localBinder = context.getBinder();
BindHandler localHandler = context.getBootstrapContext().getOrElse(BindHandler.class, null);
System.out.println("RESOLVER: BindHandler is null? " + (localHandler == null));
BindResult<MyProperties> object = localBinder.bind(MyProperties.PREFIX,
Bindable.of(MyProperties.class), localHandler);
System.out.println("RESOLVER: object is bound? " + (object.isBound()));
if (object.isBound()) {
MyProperties properties = object.get();
context.getBootstrapContext().registerIfAbsent(MyProperties.class, InstanceSupplier.of(properties));
System.out.println(
"RESOLVER: register object of type " + (properties.getClass().getName()) + " " + properties);
return properties;
}
return null;
}
Holy cow - this actually works - the sidekick bean is created and all the fields are injected from the parent document. Then the resolver can read off the property values — however this is surely the most obscure and round-about way of doing things, and there has to be a simpler method?
Credit for the answer goes to the Spring Team (philwebb at GH) when I posted this as a question to the Spring repository https://github.com/spring-projects/spring-boot/issues/32854:
I mistakenly used the binder that can be constructor injected when implementing a ConfigDataLocationResolver. Instead one should use the binder from the first parameter of the overriden methods:
// Constructor
public MyResolver(Log log, Binder binder) {
super();
this.log = log;
// don't use this object to access properties
// from the invoking properties file
this.binder = binder;
}
#Override
boolean isResolvable(ConfigDataLocationResolverContext context...) {
Binder localBinder = context.getBinder();
// this object has access to properties defined in the invoking
// parent application.properties
}
TL;DR there are number of binder objects one can access (constructor injection, from a context) — one should be using the Binder from the context and not the constructor.

Spring boot application create an object using properties from application properties

Values from application.properties can only be certain types, like String, int and so on. But often one needs an object, that is generated based on those values. In the following example, timeout is read from application.properties, but a Duration type property is actually used. Therefore, I made it into a bean, and inject it whenever needed. Is there another method to make value transfered from certain types to other types? (other types are actually used)
#Configuration
#ConfigurationProperties(prefix = "my.app")
#RefreshScope
public class AiplatformFaceProperties {
int timeout;
#Lazy
#Bean(name = "timeoutDuration")
Duration createTimeoutDuration() {
return Duration.ofSeconds(timeout);
}
}
#Autowired
#Qualifier("timeoutDuration")
private Duration timeoutDuration;
Moreover, by using a getter like this, I can get a transformed Duraiton, but it is constructed everytime needed, I would like to have a constructed-once property, like those certain tayed values.
Duration getTimeoutDuration() {
return Duration.ofSeconds(timeout);
}

spring boot component with string parameters

i have a component that reads a configuration value from application.properties and accepts a string parameter in its constructor as such..
#Component
public class Person
{
#Value("${greeting}")
String greeting;
String name;
public Person(String name)
{
this.name = name;
onGreet( greeting + ", " + name );
}
public void onGreet(String message)
{
}
}
I need to instantiate this component as follows and override its "onGreet" event in the calling code as follows:
Person jack = new Person("jack")
{
public void onGreet(String message)
{
System.out.println( message );
}
};
However I end up getting this..
Parameter 0 of constructor in demo11.Person required a bean of type 'java.lang.String' that could not be found.
My application.properties is as follows:
greeting=hello
What am I missing here? Thank you.
It is literally telling you that the only constructor that you have requires a parameter that Spring knows nothing about.
Add a #Value to that String name in the constructor (right before the parameter) like so public Person(#Value("${name}") String name) if you want Spring to initalize it or remove that constructor
EDIT: some more explanation:
Spring is a dependency injection container. Meaning you define beans and let Spring create and inject them for you. Defining beans can be done in several ways (Java configuration, annotations or xml) here you are using annotation way via #Component.
Now that you have defined your bean (aka component) for Spring it will create it. For it to create it it needs to call a constructor. For that you need to provide it with all information necessary for constructor call - meaning all parameters. If parameters are other classes they need to be defined as beans as well (For example via #Component) if they are simple types like String you need to provide #Value for them.
Lastly if you ever use new ... to define Spring managed beans then the whole Spring magic disappears since Spring doesnt know about this bean instantiation anymore and will not autowire anything into it. For all intenses and purposes Spring is not aware of any objects you create with new.

How do you make Spring fail fast when using placeholders with #ConfiguraitonProperties and an environment variable is not set?

When using placeholders to externalise configuration in an application.yaml file, and an associated properties class, how do you make sure Spring fails during startup when it can't resolve a placeholder, instead of just using the placeholder itself as the verbatim value?
For example, given this application.yaml file:
example.key: ${MY_ENV_VAR}
and this properties POJO:
#ConfigurationProperties(prefix="example")
public class AcmeProperties {
public String key;
// Getters, setters, constructors omitted...
}
if MY_ENV_VAR is not set on the system, how do you make Spring throw an exception at startup, instead of setting key to literally ${MY_ENV_VAR}?
Note, Spring doesn't return an empty String, which we could force by defining a default with ${MY_ENV_VAR:defaultValue}, or null (SpEL is not evaluated in #ConfigurationProperties, so this can not be defined as the default, and the behaviour of System.getenv("MY_ENV_VAR") returning null in the case of an undefined environment variable isn't mirrored), it literally just uses the placeholder as the value. We'd rather Spring stopped launching the app altogether instead, for all properties in AcmeProperties. Just using #Validated with Hibernate Validator on the classpath doesn't do it, neither does #ConfigurationProperties(prefix="example", ignoreInvalidFields=false) (which is the default anyway).
A manual check for the value containing ${...} could of course be added to all String properties in the POJO, but this is more error-prone, and would also not work if the actual environment variable was set to a string containing that character sequence. Is there a way to check if the placeholder can be resolved, instead of if the value after resolution is complete?
You can have a custom validator which could look like:
object UnresolvedPropertiesValidator : Validator {
override fun supports(clazz: Class<*>): Boolean {
return clazz == String::class.java
}
override fun validate(target: Any, errors: Errors) {
val stringVal = target as String
if (stringVal.startsWith(SystemPropertyUtils.PLACEHOLDER_PREFIX) && stringVal.endsWith(SystemPropertyUtils.PLACEHOLDER_SUFFIX)) {
errors.reject(
"prop.validation",
"Could not resolve placeholder $target"
)
}
}
}
And then create a bean with the name configurationPropertiesValidator:
class UnresolvedPropertiesValidatorAutoConfiguration {
#Bean
fun configurationPropertiesValidator(): UnresolvedPropertiesValidator {
return UnresolvedPropertiesValidator
}
}
As you can see from these https://github.com/spring-projects/spring-boot/blob/24a52aa66ddb92cd14acb2b41d9f55b957a44829/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationProperties.java#L47
https://github.com/spring-projects/spring-boot/blob/9630f853be3183f4872428e2e65b6ef4be7a9b7a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java#L105,
the validator with the name above will be applied after resolving the prop, and it simply ensures the final value doesn't start with ${ and end with }.
This works for us. But hopefully, the issue will be resolved soon, and we will remove this workaround.

Using #Value annotation with Spring and SPeL

I am trying to find a way to do the following in my spring boot 1.5 application.
I have a variable who's value is dynamic meaning it comes in from an external system.
String name = "abc"; //gets set externally
I want to try and use the name's value to lookup my property file and see if there is a matching property defined. something like..
#Value("#{myClassName.name.concat('something')}")
String propertyValue;
Now my application.property file has the following property set
assume name has the value "abc"
property file contents:
abc.something:abcValue
Now, when i try to access the value of the variable propertyValue it gets set to the value abc.something and not abcValue.
I probably think I cannot use #Value with #{} to get to that, I was wondering if there was a way to to use #{} inside ${} so that I goes and fetches the property value after calculating the name of the property using #{}.
Let me know if you need more details please.
A bean life-cycle requires properties to be resolved at compile time. So, #Value requires constant parameter.
You can use Environment bean to access your properties programmatically.
import org.springframework.core.env.Environment;
#Service
public class Serivce {
#Autowired
private Environment environment;
public String getProperty(final String keyPart) {
String key = "build.your." + keyPart;
return environment.getProperty(key)
}
}
By the way you can use #('${spring.some.property}') in SpEL to access placeholder.
// This is valid access to property
#Value("#('${spring.some.property}')")
private String property;

Resources