Unresolved Placeholder Validation for Spring Boot Configuration Properties - spring

Given some application configuration with an unresolvable placeholder, like the following application.yml
my:
thing: ${missing-placeholder}/whatever
When I use #Value annotations, the placeholders in the configuration file are validated, so in this case:
package com.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class PropValues {
#Value("${my.thing}") String thing;
public String getThing() { return thing; }
}
I get an IllegalArgumentException: Could not resolve placeholder 'missing-placeholder' in value "${missing-placeholder}/whatever". This is because the value is being set directly by AbstractBeanFactory.resolveEmbeddedValue and there is nothing to catch the exception thrown by PropertyPlaceholderHelper.parseStringValue
However, looking to move to #ConfigurationProperties style I noticed that this validation is missing, for example in this case:
package com.test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
#ConfigurationProperties(prefix = "my")
public class Props {
private String thing;
public String getThing() { return thing; }
public void setThing(String thing) { this.thing = thing; }
}
there is no exception. I can see PropertySourcesPropertyValues.getEnumerableProperty catches the exception with the comment // Probably could not resolve placeholders, ignore it here and gathers the invalid value into its internal map. Subsequent data binding does not check for unresolved placeholders.
I checked that simply applying the #Validated and #Valid annotations to the class and field do not help.
Is there any way to preserve the behaviour of throwing an exception on unresolved placeholders with ConfigurationProperties binding?

Apparently there are no better solutions. At least this is kind of nicer than afterPropertiesSet().
#Data
#Validated // enables javax.validation JSR-303
#ConfigurationProperties("my.config")
public static class ConfigProperties {
// with #ConfigurationProperties (differently than #Value) there is no exception if a placeholder is NOT RESOLVED. So manual validation is required!
#Pattern(regexp = ".*\$\{.*", message = "unresolved placeholder")
private String uri;
// ...
}
UPDATE: I got the regex wrong the first time. It as to match the entire input (not just java.util.regex.Matcher#find()).

The correct regex to pass in #Pattern annotation is ^(?!\\$\\{).+
#Validated
#ConfigurationProperties("my.config")
public class ConfigProperties {
#Pattern(regexp = "^(?!\\$\\{).+", message = "unresolved placeholder")
private String uri;
// ...
}

I had the same issue exactly 10 minutes ago!
Try to add this bean in your configuration:
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertySourcesPlaceholderConfigurer;
}

Related

Custom YML configuration file String conversion Enum

In Spring Boot project, the configuration in the YML file can be automatically converted to an #ConfigurationProperties annotated bean, I found from the official documents and source code in ApplicationConversionService#addApplicationConverters() method to add A default LenientStringToEnumConverterFactory to handle all the String conversion to Enum, it through the Enum.valueOf() implementation,But I want to use other rules to turn strings into examples of enum,Just like the fromAlias() method below,
#ConfigurationProperties(prefix = "head")
#Component
#Data
public class HeadProperties {
private PayType payType;
private Integer cast;
#Getter
#RequiredArgsConstructor
enum PayType {
GOLD("GOLD", "金币"), DIAMOND("DIAMOND", "钻石"), VIP_FREE("VIP_FREE", "会员免费");
private final String val;
private final String alias;
static PayType fromAlias(String alias) {
return Arrays.stream(values())
.filter(type -> alias.equals(type.getAlias()))
.findAny()
.orElse(null);
}
}
}
The following is the YML file configuration
head:
payType: "金币"
cast: 10
I don't know where the entry is, so I get an error as soon as the program runs
code:
#SpringBootApplication
#Slf4j
public class DemoApplication{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
ApplicationRunner runner(HeadProperties headConfig) {
return arg -> log.info("head config:{}", headConfig);
}
}
the following is error message:
APPLICATION FAILED TO START
***************************
Description:
Failed to bind properties under 'head.pay-type' to com.example.demo.HeadProperties$PayType:
Property: head.pay-type
Value: 金币
Origin: class path resource [application.yml] - 2:12
Reason: failed to convert java.lang.String to com.example.demo.HeadProperties$PayType (caused by java.lang.IllegalArgumentException: No enum constant com.example.demo.HeadProperties.PayType.金币)
Action:
Update your application's configuration. The following values are valid:
DIAMOND
GOLD
VIP_FREE
I tried injecting various converters like the one below into the container,but it still didn't work.
#Component
public class PayTypeConverter implements Converter<String, HeadProperties.PayType> {
#Override
public HeadProperties.PayType convert(String source) {
return HeadProperties.PayType.fromAlias(source);
}
}
#Component
public class PayTypeConverter implements Converter<String, Enum<HeadProperties.PayType>> {
#Override
public Enum<HeadProperties.PayType> convert(String source) {
return HeadProperties.PayType.fromAlias(source);
}
}
How can this requirement be fulfilled?
The converters that are used for #ConfigurationProperties binding need a special qualifier that tells Spring that they are to be used for that purpose.
An annotation exists for this- #ConfigurationPropertiesBinding. The Javadoc is as follows:
Qualifier for beans that are needed to configure the binding of #ConfigurationProperties (e.g. Converters).
So all that's needed is to add that annotation to your converter, then Spring will use it during the binding process:
#Component
#ConfigurationPropertiesBinding
public class PayTypeConverter implements Converter<String, HeadProperties.PayType> {
#Override
public HeadProperties.PayType convert(String source) {
return HeadProperties.PayType.fromAlias(source);
}
}
That then produces the expected output:
head config:HeadProperties(payType=GOLD, cast=10)
And a minor note, when writing custom converters, be aware that returning null will not trigger an error (assuming there are no other measures configured to prevent that). That means that unlike the out-of-the-box enum converter, your custom one does not produce an error if no matching enum can be found. You can remedy this by instead throwing an exception instead of returning null.

SpringBoot Failed to bind properties under app

I have a SpringBoot 2.1.7.RELEASE project with gradle. I'm getting an error when I try to use #ConfigurationProperties
The property that I'm trying to bind is existing in my application-default.properties and if I run the project using Itellij I can see that the property is ingested in my component.
If I enable #EnableConfigurationProperties I got an error.
My application-default.properties
app.forwarding-endpoint=localhost:8080
My AppProperties.java
#ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
#Validated
#Data
public class AppProperties {
#NotBlank
#Pattern(regexp = "^(.+):\\d+$")
private String forwardingEndpoint;
}
My Application.java
#SpringBootApplication
#EnableConfigurationProperties(AppProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
}
My component that is using the property:
public MyComponent(#Value("${app.forwarding-endpoint}") String forwardingEndpoint) {
log.info("Forwarding endpoint {}", forwardingEndpoint);
}
The error that I get is:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'app' to com.config.AppProperties failed:
Property: app.forwardingEndpoint
Value: null
Reason: must not be blank
What am I missing?
The cause is in the order of initialization.
You did not fill AppProperties but start to use it in components. You need to annotate this class also as a component but it's not a good approach from point of view of an architecture.
The concept of #ConfigurationProperties is quite raw for Spring and without some manipulations, you will quite difficult to force it to work correctly. I propose a simple 'trick' (or 'another approach'):
#Data
public class AppProperties {
#NotBlank
#Pattern(regexp = "^(.+):\\d+$")
private String forwardingEndpoint;
}
(I think the place of #validated is not in the entitity/DO).
And place in your #Configuration next code:
#Bean
#ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
public AppProperties setAppProperties() {
return new AppProperties();
}
And next, you can inject AppProperties bean in any component.

Spring Boot #Value Properties

I have a Spring Boot application and in one of the classes, I try to reference a property from the application.properties file using #Value. But, the property does not get resolved. I have looked at similar posts and tried following the suggestions, but that didn't help. The class is:
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class PrintProperty {
#Value("${file.directory}")
private String fileDirectory;
public void print() {
System.out.println(fileDirectory);
}
}
I have the property file.directory in application.properties. I have other fields as well.
I had the same problem like you. Here's my error code.
#Component
public class GetExprsAndEnvId {
#Value("hello")
private String Mysecret;
public GetExprsAndEnvId() {
System.out.println("construct");
}
public void print(){
System.out.println(this.Mysecret);
}
public String getMysecret() {
return Mysecret;
}
public void setMysecret(String mysecret) {
Mysecret = mysecret;
}
}
This is no problem like this, but
we need to use it like this:
#Autowired
private GetExprsAndEnvId getExprsAndEnvId;
not like this:
getExprsAndEnvId = new GetExprsAndEnvId();
Here, the field annotated with #Value is null because Spring doesn't know about the copy of GetExprsAndEnvId that is created with new and didn't know to how to inject values in it.
Make sure your application.properties file is under src/main/resources/application.properties. Is one way to go. Then add #PostConstruct as follows
Sample Application.properties
file.directory = somePlaceOverHere
Sample Java Class
#ComponentScan
public class PrintProperty {
#Value("${file.directory}")
private String fileDirectory;
#PostConstruct
public void print() {
System.out.println(fileDirectory);
}
}
Code above will print out "somePlaceOverhere"
I´d like to mention, that I used spring boot version 1.4.0 and since this version you can only write:
#Component
public class MongoConnection {
#Value("${spring.data.mongodb.host}")
private String mongoHost;
#Value("${spring.data.mongodb.port}")
private int mongoPort;
#Value("${spring.data.mongodb.database}")
private String mongoDB;
}
Then inject class whenever you want.
EDIT:
From nowadays I would use #ConfigurationProperties because you are able to inject property values in your POJOs. Keep hierarchical sort above your properties. Moreover, you can put validations above POJOs attributes and so on. Take a look at the link
To read the values from application.properties we need to just annotate our main class with #SpringBootApplication and the class where you are reading with #Component or variety of it. Below is the sample where I have read the values from application.properties and it is working fine when web service is invoked. If you deploy the same code as is and try to access from http://localhost:8080/hello you will get the value you have stored in application.properties for the key message.
package com.example;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
public class DemoApplication {
#Value("${message}")
private String message;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#RequestMapping("/hello")
String home() {
return message;
}
}
Try and let me know
You haven't included package declarations in the OP but it is possible that neither #SpringBootApplication nor #ComponentScan are scanning for your #Component.
The #ComponentScan Javadoc states:
Either basePackageClasses or basePackages (or its alias value) may be
specified to define specific packages to scan. If specific packages
are not defined, scanning will occur from the package of the class
that declares this annotation.
ISTR wasting a lot of time on this before and found it easiest to simply move my application class to the highest package in my app's package tree.
More recently I encountered a gotcha were the property was being read before the value insertion had been done. Jesse's answer helped as #PostConstruct seems to be the earliest you can read the inserted values, and of course you should let Spring call this.
I had the similar issue and the above examples doesn't help me to read properties. I have posted the complete class which will help you to read properties values from application.properties file in SpringBoot application in the below link.
Spring Boot - Environment #Autowired throws NullPointerException
Your problem is that you need a static PropertySourcesPlaceholderConfigurer Bean definition in your configuration. I say static with emphasis, because I had a non-static one and it didn't work.
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
I had the same issue get value for my property in my service class. I resolved it by using #ConfigurationProperties instead of #Value.
create a class like this:
import org.springframework.boot.context.properties.ConfigurationProperties;
#ConfigurationProperties(prefix = "file")
public class FileProperties {
private String directory;
public String getDirectory() {
return directory;
}
public void setDirectory(String dir) {
this.directory = dir;
}
}
add the following to your BootApplication class:
#EnableConfigurationProperties({
FileProperties.class
})
Inject FileProperties to your PrintProperty class, then you can get hold of the property through the getter method.

Spring #Required properties when creating #Bean annotated beans

I'm developing a Spring Boot application and am trying out using Java annotation-based bean creation (using #Configuration and #Bean) rather than the familiar old XML-based bean creation. I'm puzzled though. If I attempt to create a bean in XML but fail to set an #Required property I get a BeanInitializationException when the application context is created. In my trials so far with annotation-based bean creation though this does not seem to be the case.
For example:
public class MyClass {
...
#Required
public void setSomeProp(String val){
}
}
Then in Spring XML:
<bean class="MyClass"/>
This will blow up during application startup (and IntelliJ flags it) because the required property is not set. But the same does not seem to be true of this:
#Configuration
public class MyConfig {
#Bean
public MyClass myClass() {
return new MyClass();
}
}
This application starts up just fine even though the required property is not ever set. I must be missing something here, because this seems like a pretty key feature in Spring.
UPDATE
I did some digging & debugging and it turns out that the bean definition is somehow being flagged to skip checking that #Required fields are set. In the Spring class 'RequiredAnnotationBeanPostProcessor' the boolean method 'shouldSkip()' is returning true for beans created this way. When I used the debugger to force that method to return false bean creation did indeed blow up with the expected exception.
Seeing as I'm making a pretty basic Spring Boot application I'm inclined (as Zergleb suggests) to submit this as a bug.
UPDATE 2
Some further debugging has revealed that even if the field is getting set forcing the check still throws the same exception, as if it hadn't been set. So perhaps dunni is correct and there is no way for this to work with #Bean notation.
As you said I also could not get #Required to run as expected this may be a bug and needs to be reported. I have a few other suggestions that did work for me.
Class annotated with #Configuration
//With the bean set up as usual These all worked
#Bean
public MyClass myClass() {
return new MyClass();
}
When you annotate the class #Component and load using component scanning works as expected.(The component scanning part is important you either need your #Configuration class to either have #ComponentScan or perhaps remove #Configuration and replace with #SpringBootApplication and this will enable scanning for components without needing to wire them up using #Bean configs)
#Component // Added this
public class MyClass {
...
#Required //Failed as expected
public void setSomeProp(String val){
}
}
Use #Autowired(required=true) //Fails with BeanCreationException //No qualifying bean of type [java.lang.String] found for dependency
//No more #Component
public class MyClass {
...
#Autowired(required=true) //Fails
public void setSomeProp(String val){
}
}
#Autowired required=false //Does not crash
public class MyClass {
...
#Autowired(required=false) //Simply never gets called if missing
public void setSomeProp(String val){
}
}
#Value //Does not work if test.property is missing // Could not resolve placeholder 'test.property' in string value "${test.property}
public class MyClass {
#Value("${test.property}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp;
}
}
#Value with default value//Does not crash // When getSomeProp is called it returns "My Default Value"(Unless you have test.property=Anything in your application.properties file then it returns "Anything"
public class MyClass {
#Value("${test.property:My Default Value}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp; //Returns "My Default Value"
}
}
Inside your #Configuration file also fails if it cannot find anything to populate String someProp in the myClass method
#Bean
public MyClass myClass(String someProp) { //Fails being unable to populate this arg
MyClass myObj = new MyClass();
myObj.setSomeProp(someProp);
return ;
}
If course this won't work, since you create the object of MyClass yourself (new MyClass()), thus the annotations are not evaluated. If you create a bean with a #Bean method, the container will only make sure, that all dependencies are there (method parameters) and that the bean scope is adhered to, meaning if it's a singleton bean, only one bean is created per application context. The creation of the bean/object itself is solely the responsibility of the developer.
The equivalent of the xml <bean> tag is annotating the class with #Component, where the bean is created completely by the container, thus the annotations are evaluated.
As it is being said that when you are having your own #Configuration class where you are creating the bean by itself, #Required doesn't apply there.
When you already have a #Component, let Spring Boot do the component scan and at the required setter property you can add #Autowired and it will work fine.
Found this link on web- https://www.boraji.com/spring-required-annotation-example
For example:
I have a Component called Employee having Id and Name.
#Component
public class Employee {
int id;
String name;
public int getId() {
return id;
}
#Autowired
#Required
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I have a Configuration class called AppConfig.java
#Configuration
public class AppConfig {
#Bean
public int getId() {
return 1;
}
}
So now we see, that component Employee needs an Id property for binding during startup, so I wrote bean method of type Integer, which will get autowired during runtime. If you do not write a bean of type Integer, it will result a BeanCreationException.
And here is my main class file.
#SpringBootApplication
public class SingletonApplication {
public static void main(String[] args) {
ApplicationContext ctx =
SpringApplication.run(SingletonApplication.class, args);
Employee emp = (Employee)ctx.getBean(Employee.class);
System.out.println(emp.getId());
}
}

How can I find all beans with the custom annotation #Foo?

I have this spring configuration:
#Lazy
#Configuration
public class MyAppConfig {
#Foo #Bean
public IFooService service1() { return new SpecialFooServiceImpl(); }
}
How can I get a list of all beans that are annotated with #Foo?
Note: #Foo is a custom annotation defined by me. It's not one of the "official" Spring annotations.
[EDIT] Following the suggestions of Avinash T., I wrote this test case:
import static org.junit.Assert.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.reflect.Method;
import java.util.Map;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
public class CustomAnnotationsTest {
#Test
public void testFindByAnnotation() throws Exception {
AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext( CustomAnnotationsSpringCfg.class );
Method m = CustomAnnotationsSpringCfg.class.getMethod( "a" );
assertNotNull( m );
assertNotNull( m.getAnnotation( Foo.class ) );
BeanDefinition bdf = appContext.getBeanFactory().getBeanDefinition( "a" );
// Is there a way to list all annotations of bdf?
Map<String, Object> beans = appContext.getBeansWithAnnotation( Foo.class );
assertEquals( "[a]", beans.keySet().toString() );
}
#Retention( RetentionPolicy.RUNTIME )
#Target( ElementType.METHOD )
public static #interface Foo {
}
public static class Named {
private final String name;
public Named( String name ) {
this.name = name;
}
#Override
public String toString() {
return name;
}
}
#Lazy
#Configuration
public static class CustomAnnotationsSpringCfg {
#Foo #Bean public Named a() { return new Named( "a" ); }
#Bean public Named b() { return new Named( "b" ); }
}
}
but it fails with org.junit.ComparisonFailure: expected:<[[a]]> but was:<[[]]>. Why?
Use getBeansWithAnnotation() method to get beans with annotation.
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Foo.class);
Here is similar discussion.
UPDATE: Spring 5.2 changed the behavior of context.getBeansWithAnnotation(...) and it now correctly handles beans created via factory methods. So simply use that.
Original answer
While the accepted answer and Grzegorz's answer contain approaches that will work in all cases, I found a much much simpler one that worked equally well for the most common cases.
Meta-annotate #Foo with #Qualifier:
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Foo {
}
Sprinkle #Foo onto the factory methods, as described in the question:
#Foo
#Bean
public IFooService service1() {
return new SpecialFooServiceImpl();
}
But it will also work on the type level:
#Foo
#Component
public class EvenMoreSpecialFooServiceImpl { ... }
Then, inject all the instances qualified by #Foo, regardless of their type and creation method:
#Autowired
#Foo
List<Object> fooBeans;
fooBeans will then contain all the instances produced by a #Foo-annotated method (as required in the question), or created from a discovered #Foo annotated class.
The list can additionally be filtered by type if needed:
#Autowired
#Foo
List<SpecialFooServiceImpl> fooBeans;
The good part is that it will not interfere with any other #Qualifier (meta)annotations on the methods, nor #Component and others on the type level. Nor does it enforce any particular name or type on the target beans.
With the help of a couple of Spring experts, I found a solution: The source property of a BeanDefinition can be AnnotatedTypeMetadata. This interface has a method getAnnotationAttributes() which I can use to get the annotations of a bean method:
public List<String> getBeansWithAnnotation( Class<? extends Annotation> type, Predicate<Map<String, Object>> attributeFilter ) {
List<String> result = Lists.newArrayList();
ConfigurableListableBeanFactory factory = applicationContext.getBeanFactory();
for( String name : factory.getBeanDefinitionNames() ) {
BeanDefinition bd = factory.getBeanDefinition( name );
if( bd.getSource() instanceof AnnotatedTypeMetadata ) {
AnnotatedTypeMetadata metadata = (AnnotatedTypeMetadata) bd.getSource();
Map<String, Object> attributes = metadata.getAnnotationAttributes( type.getName() );
if( null == attributes ) {
continue;
}
if( attributeFilter.apply( attributes ) ) {
result.add( name );
}
}
}
return result;
}
gist with full code of helper class and test case
Short story
It is not enough to put #Foo on the a() method in order to make the a bean annotated with #Foo.
Long story
I didn't realize it before I started debugging Spring code, a breakpoint at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(String, Class<A>) helped me understand it.
Of course, if you moved your annotation to the Named class:
#Foo
public static class Named {
...
and fixed some minor details of your test (annotation target, etc.) the test works.
After giving it a second thought, it's quite natural. When getBeansWithAnnotation() is called, the only information Spring has are the beans. And beans are objects, objects have classes. And Spring doesn't seem to need to store any additional information, incl. what was the factory method used to create the bean annotated with, etc.
EDIT There is an issue which requests to preserve annotations for #Bean methods: https://jira.springsource.org/browse/SPR-5611
It has been closed as "Won't fix" with the following workaround:
Employ a BeanPostProcessor
Use the beanName provided to the BPP methods to look up the associated BeanDefinition from the enclosing BeanFactory
Query that BeanDefinition for its factoryBeanName (the #Configuration bean) and factoryMethodName (the #Bean name)
use reflection to get hold of the Method the bean originated from
use reflection to interrogate any custom annotations from that method
To get all annotated beans:
context.getBeansWithAnnotation(Foo.class)
This returns a Map<String, Object> where the key is the bean name.
To get the annotation class:
context.findAnnotationOnBean(beanName, Foo.class);
This can be helpful when the annotation has values (#Foo(weight=100)).
This is how to get annotated beans.
#Autowired
private ApplicationContext ctx;
public void processAnnotation() {
// Getting annotated beans with names
Map<String, Object> allBeansWithNames = ctx.getBeansWithAnnotation(TestDetails.class);
//If you want the annotated data
allBeansWithNames.forEach((beanName, bean) -> {
TestDetails testDetails = (TestDetails) ctx.findAnnotationOnBean(beanName, TestDetails.class);
LOGGER.info("testDetails: {}", testDetails);
});
}
In my case getBeansWithAnnotation was returning an empty list. My mistake was to not adding retention and target on my custom annotation.
Adding these lines no top of my annotation fixed it.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)

Resources