Is it possible to combine spring boot #Value with javax.validation.constraints? - java

I would like to validate values in application.properties.
I use #Value to inject and #NotBlank to validate the method parameter in a class annotated with #Configuration.
createSslContext(
#Value("#{systemProperties['javax.net.ssl.trustStore']}")
#NotBlank(message = "Truststore is needed")
String path)
I have included the hibernate-validator and the hibernate-validator-annotation-processor properties. Using Spring 2.2.0.RELEASE.
It doesn't work. The value is injected, but not validated. What am I missing?

Add #Validated to your configuration class.
#Configuration
#Validated
public class MyConfig {
#Bean
MyClass createSslContext(
#Value("#{systemProperties['javax.net.ssl.trustStore']}")
#NotBlank(message = "Truststore is needed") final
String path) {
...
}
}

Related

Using #ConstructorBinding with #ConditionalOnProperty in Springboot

I'm using #ConstructorBinding with #ConfigurationProperties like this
#ConstructorBinding
#ConditionalOnProperty(name = "nexus.orchestration.cloud.model", havingValue = "true", matchIfMissing = false)
#ConfigurationProperties(value = "nexus.orchestration.model.cloud-bucket", ignoreUnknownFields = false)
#ToString
public static class ModelCloudBucket {
private final CloudBucket cloudBucket;
public ModelCloudBucket(final CloudProviderEnum provider, final String bucket, final String folder) {
this.cloudBucket = new CloudBucket(provider, bucket, folder);
}
}
I have #EnableConfigurationProperties(FlowCache.ModelCloudBucket.class) on my main application class
However, the #ConditionalOnProperty is not working. If my property is false and I comment out the CloudBucket object in the yaml file, it fails on startup because it can't bind the cloud bucket property. If the property is false, than that object should not be required and then bean should just be null. How can I make this work?
From my understanding, Spring #ConditionalOnProperty and #ConfigurationProperties are used in two different areas:
#ConditionalOnProperty is used to conditional inject beans and configurations.
#ConfigurationProperties is for external configurations, which is from your configuration files like application.properties
From the code, I assume that ModelCloudBucket is a bean you want to inject if nexus.orchestration.cloud.model == true, which takes provider, bucket and folder as its properties.
So I would suggest to the following code:
A property class:
#Configuration
#ConfigurationProperties(prefix = "nexus.orchestration.model.cloud-bucket")
#Data
public class ModelCloudBucketProps {
private CloudProviderEnum testProp;
private String bucket;
private String folder;
}
And the original bean which will be injected with properties:
#Component
#ConditionalOnProperty(name = "nexus.orchestration.cloud.model", havingValue = "true")
public class ModelCloudBucket {
...
public ModelCloudBucket(ModelCloudBucketProps config) {
this.cloudBucket = new CloudBucket(config.getProvider(), ...);
}
}
#EnableConfigurationProperties(FlowCache.ModelCloudBucket.class) can be removed since will be injected through #Configuration.
In this way, you can control the bean injection with condition and decouple it with the external config initialization.

Spring Boot read application.properties / .yml variable without annotations

I want to create a class using new operator and in that class I want to read a property from application.yml file, how to do that in Spring Boot application ?
In other words I cannot use #Value, #Autowire, #Component annotations.
You need to do something like this:
#Configuration
#ConfigurationProperties(prefix = "test")
public class TestProperties{
private String testProperty1;
public String getTestProperty1() {
return testProperty1;
}
public void setTestProperty1(String testProperty1) {
this.testProperty1= testProperty1;
}
}
next in Your application.properties:
test.testPropety1 = yourProperty
You can implement this properties class as bean because of #Configuration annotation. Hope that helps in Your problem :)

Interface Annotation does not accept application.properties value

I have developed a simple Annotation Interface
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomAnnotation {
String foo() default "foo";
}
then I test it annotating a Class
#CustomAnnotation
public class AnnotatedClass {
}
and call it using a method
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
logger.info(customAnnotation.foo());
}
and all works fine because it logs foo. I try also change the annotated class to #CustomAnnotation(foo = "123") and all works fine too, becuase it logs 123.
Now I want that the value passed to the annotation is retrieved by the application.properties, so I have changed my annotated class to
#CustomAnnotation(foo = "${my.value}")
public class AnnotatedClass {
}
but now the log returns the String ${my.vlaue} and not the value in application.properties.
I know that is possible use ${} instruction in annotation because I always use a #RestController like this #GetMapping(path = "${path.value:/}") and all works fine.
My solution on Github repository: https://github.com/federicogatti/annotatedexample
Spring Core-based approach
First off, I want to show you a standalone application that doesn't utilise Spring Boot auto-configurable facilities. I hope you will appreciate how much Spring does for us.
The idea is to have a ConfigurableBeanFactory set up with StringValueResolver which will be aware of our context (particularly, of the application.yaml properties).
class Application {
public static void main(String[] args) {
// read a placeholder from CustomAnnotation#foo
// foo = "${my.value}"
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String foo = customAnnotation.foo();
// create a placeholder configurer which also is a properties loader
// load application.properties from the classpath
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("application.properties"));
// create a factory which is up to resolve embedded values
// configure it with our placeholder configurer
ConfigurableListableBeanFactory factory = new DefaultListableBeanFactory();
configurer.postProcessBeanFactory(factory);
// resolve the value and print it out
String value = factory.resolveEmbeddedValue(foo);
System.out.println(value);
}
}
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#interface CustomAnnotation {
String foo() default "foo";
}
#CustomAnnotation(foo = "${my.value}")
class AnnotatedClass {}
Spring Boot-based approach
Now, I will demonstrate how to do it within your Spring Boot application.
We are going to inject ConfigurableBeanFactory (which has already been configured) and resolve the value similarly to the previous snippet.
#RestController
#RequestMapping("api")
public class MyController {
// inject the factory by using the constructor
private ConfigurableBeanFactory factory;
public MyController(ConfigurableBeanFactory factory) {
this.factory = factory;
}
#GetMapping(path = "/foo")
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String foo = customAnnotation.foo();
// resolve the value and print it out
String value = factory.resolveEmbeddedValue(foo);
System.out.println(value);
}
}
I don't like mixing up low-level Spring components, such as BeanFactory, in business logic code, so I strongly suggest we narrow the type to StringValueResolver and inject it instead.
#Bean
public StringValueResolver getStringValueResolver(ConfigurableBeanFactory factory) {
return new EmbeddedValueResolver(factory);
}
The method to call is resolveStringValue:
// ...
String value = resolver.resolveStringValue(foo);
System.out.println(value);
Proxy-based approach
We could write a method that generates a proxy based on the interface type; its methods would return resolved values.
Here's a simplified version of the service.
#Service
class CustomAnnotationService {
#Autowired
private StringValueResolver resolver;
public <T extends Annotation> T getAnnotationFromType(Class<T> annotation, Class<?> type) {
return annotation.cast(Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{annotation},
((proxy, method, args) -> {
T originalAnnotation = type.getAnnotation(annotation);
Object originalValue = method.invoke(originalAnnotation);
return resolver.resolveStringValue(originalValue.toString());
})));
}
}
Inject the service and use it as follows:
CustomAnnotation customAnnotation = service.getAnnotationFromType(CustomAnnotation.class, AnnotatedClass.class);
System.out.println(customAnnotation.foo());
You can't do something like directly as an annotation attribute's value must be a constant expression.
What you can do is, you can pass foo value as string like #CustomAnnotation(foo = "my.value") and create advice AOP to get annotation string value and lookup in application properties.
create AOP with #Pointcut, #AfterReturn or provided others to match #annotation, method etc and write your logic to lookup property for corresponding string.
Configure #EnableAspectJAutoProxy on main application or setting up by configuration class.
Add aop dependency: spring-boot-starter-aop
Create #Aspect with pointcut .
#Aspect
public class CustomAnnotationAOP {
#Pointcut("#annotation(it.federicogatti.annotationexample.annotationexample.annotation.CustomAnnotation)")
//define your method with logic to lookup application.properties
Look more in official guide : Aspect Oriented Programming with Spring
Make sure Annotated Class has #Component annotation along with #CustomAnnotation(foo = "${my.value}"), then Spring will recognize this class as Spring component and makes the necessary configurations to insert the value in.
You can use ConfigurableBeanFactory.resolveEmbeddedValue to resolve ${my.value} into the value in application.properties.
#CustomAnnotation(foo="${my.value}")
#lombok.extern.slf4j.Slf4j
#Service
public class AnnotatedClass {
#Autowired
private ConfigurableBeanFactory beanFactory;
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String fooValue = customAnnotation.foo().toString();
String value = beanFactory.resolveEmbeddedValue(fooValue);
log.info(value);
}
}
If you also want to resolve expressions you should consider using EmbeddedValueResolver.
EmbeddedValueResolver resolver = new EmbeddedValueResolver(beanFactory);
final String value = resolver.resolveStringValue(fooValue);
You can look at Spring's RequestMappingHandlerMapping to see how they do it, which is using a EmbeddedValueResolver. You can inject the bean factory into any spring component and then use it to build your own resolver:
#Autowired
public void setBeanFactory(ConfigurableBeanFactory beanFactory)
{
this.embeddedValueResolver = new EmbeddedValueResolver(beanFactory);
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String fooValue = customAnnotation.foo();
System.out.println("fooValue = " + fooValue);
String resolvedValue = embeddedValueResolver.resolveStringValue(fooValue);
System.out.println("resolvedValue = " + resolvedValue);
}
Assuming you set foo.value=hello in your properties, the output would look something like:
fooValue = ${foo.value}
resolvedValue = hello
I tested this with Spring Boot 2.0.2 and it worked as expected.
Keep in mind this is a minimal example. You would want to handle the error cases of missing annotations on the class and missing resolved value (if the value isn't set and there's no default).
To read property from application.propertie, one need to define PropertyPlaceholderConfigurer and map it with properties file.
XML based configuration:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations" value="classpath:application.properties" />
</bean>
For annotation based: one can use as below:
#Configuration
#PropertySource(
value{"classpath:properties/application.properties"},ignoreResourceNotFound=true)
public class Config {
/**
* Property placeholder configurer needed to process #Value annotations
*/
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}

Spring Boot read values from application properties

I'm not sure if I understand it correctly, but from what I got, is that I can use #Value annotations to read values from my application.properties.
As I figured out this works only for Beans.
I defined such a bean like this
#Service
public class DBConfigBean {
#Value("${spring.datasource.username}")
private String userName;
#Bean
public String getName() {
return this.userName;
}
}
When the application starts I'm able to retrieve the username, however - how can I access this value at runtime?
Whenever I do
DBConfigBean conf = new DBConfigBean()
conf.getName();
* EDIT *
Due to the comments I'm able to use this config DBConfigBean - but my initial problem still remains, when I want to use it in another class
#Configurable
public SomeOtherClass {
#Autowired
private DBConfigBean dbConfig; // IS NULL
public void DoStuff() {
// read the config value from dbConfig
}
}
How can I read the DBConfig in a some helper class which I can define as a bean
Thanks
As Eirini already mentioned you must inject your beans.
The #Value annotation only works on Spring beans.
There is another way of accessing configuration with #ConfigurationProperties.
There you define a class that holds the configuration.
The main advantage is, that this is typesafe and the configuration is in one place.
Read more about this:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-vs-value
You shouldn't instantiate your service with the new operator. You should inject it, for example
#Autowired
private DBConfigBean dbConfig;
and then dbConfig.getName();
Also you don't need any #Bean decorator in your getName() method
You just need to tell spring where to search for your annotated beans. So in your configuration you could add the following:
#ComponentScan(basePackages = {"a.package.containing.the.service",
"another.package.containing.the.service"})
EDIT
The #Value, #Autowired etc annotations can only work with beans, that spring is aware of.
Declare your SomeOtherClass as a bean and add the package config in your #Configuration class
#Bean
private SomeOtherClass someOtherClass;
and then
#Configuration
#ComponentScan(basePackages = {"a.package.containing.the.service"
"some.other.class.package"})
public class AppConfiguration {
//By the way you can also define beans like:
#Bean
public AwesomeService service() {
return new AwesomeService();
}
}
Wrap your DBConfig with #Component annotation and inject it using #Autowired :
#Autowired
private DBConfig dbConfig;
Just add below annotation to your DBConfigBean class:
#PropertySource(value = {"classpath:application.properties"})

Getting null values when using ConfigurationProperties on Spring boot

I'm new to the Java world and Spring boot, and I'm trying to access some configuration values located in a YAML file through the ConfigurationProperties annotation.
But whenever I try to access a configuration value anywhere in a service, I get a null value.
Here's the application.yml file:
my_config:
test: "plop"
Here's the ValidationProperties configuration class:
#Configuration
#ConfigurationProperties(prefix = "my_config")
public class ValidationProperties {
#NotNull
private String test;
public void setTest(String test) {
this.test = test;
}
public String getTest() {
return this.test;
}
}
A validator service that uses it:
#Service
public class MyValidator implements ConstraintValidator<MyConstraint, MyEntity> {
#Autowired
private ValidationProperties validationProperties;
#Value("${my_config.test}")
private String test;
#Override
public boolean isValid(MyEntity entity, ConstraintValidatorContext context) {
System.out.println(this.test); // null value, why?
}
}
I also added the #EnableConfigurationProperties annotation in my main class.
I'm not sure which annotation is supposed to do what, but I'm obviously missing something here. Also, if I try to access the value from the getter of the configuration file, I get an exception:
System.out.println(this.validationProperties.getTest());
will get me HV000028: Unexpected exception during isValid call.
Try adding #EnableConfigurationProperties(ValidationProperties.class)
on your main application class or any other #Configuration class.
Also putting #Component annotation on ValidationProperties should work.
No need to inject value via #Value annotation, just access it via getter of injected validationProperties object
I got this error running a JUnit test on a SpringBoot app, what I was missing is the annotation #SpringBootTest

Categories

Resources