Why SpringBoot validation not work with single #PropertySource - spring

My Source code:
Person.java
#Component
#Validated
#PropertySource(value = "classpath:person.yaml")
#Data
public class Person {
#NotNull(message = "test can't be null")
private String test;
#Value("${name}")
private String name;
#Autowired
private Cat pet;
#Max(value=120,message="invalid age")
#Value("${age}")
private int age;
#Email(message = "invalid email address")
#Value("${email}")
private String email;
}
person.yaml
name: "test"
email: "abcdefg"
age: 11111
I use my Person class on SpringBootApplication with invalid email address and age, but it ran successfully without any validation exception.
But If I replace the #PropertySource(value = "classpath:person.yaml") to #ConfigurationProperties(prefix = "person") or use both #PropertySource and #ConfigurationProperties
application.yaml
person:
name: "test"
email: "abcdefg"
age: 11111
My SpringBoot throw an validation exception successfully.

From #PropertySource documantation:
Annotation providing a convenient and declarative mechanism for adding a PropertySource to Spring's Environment. To be used in conjunction with #Configuration classes.
You don't have #Configuration anotation under class declaration.
Your second example has #ConfigurationProperties annotation and this is from documentation:
Annotation for externalized configuration. Add this to a class definition or a #Bean method in a #Configuration class if you want to bind and validate some external Properties (e.g. from a .properties file).
In a nutshell: #PropertySource need #Configuration but #ConfigurationProperties not.

Related

Why setters are mandatory for fields in a class which reads properties from application.yml file in springboot?

Following is my code:
Why setter is mandatory. Without it, the class does not
read the property from the
application.yml file
correctly.
Thank you.
#Getter
#Setter
#NoArgsConstructor
#Configuration
#ConfigurationProperties(prefix = "test")
#EnableConfigurationProperties
public class KafkaTopicConfig {
private String bootstrapAddress;
#Value(value = "${test.bootstrapAddress}")
private String bootstrapAddressFromVariable;
should only use #Value in encapsulated components/services (we can call them configuration services).
This way, we will have all our configurations in one place, and that component will only have the responsibility of loading and providing them to other components.
https://stackabuse.com/the-value-annotation-in-spring
From baeldung.com... The Spring framework uses standard Java bean setters, so we must declare setters for each of the properties.
So it looks like you're using Lombok so I would make my class look more like this:
#ConfigurationProperties(prefix = "test")
#Data
public class KafkaTopicConfig {
private String bootstrapAddress;
}
Then in the main spring boot application class or a #Configuration class I would do:
#Configuration
#EnableConfigurationProperties({KafkaTopicConfig.class})
public MyApplicationConfig{
}
Then to use my configuration properties I would autowire it into the #Component where I wished to use it.
e.g.
#Component
public MyComponent{
private final KafkaTopicConfig config;
public MyComponent(KafkaTopicConfig config) {
this.config = config;
}
public void doStuff() {
if ("some address".equals(config.getBootstrapAddress())) {
blah();
}
}
}
Using the #Value inside the configuration properties feels confusing to me, and defeats the point of using configuration properties in the first place.

javax validation api not working for pojo validation

I have a POJO class where the class variables are getting injected by #Value annotation. I am trying to validate my class variables using javax validation api & so I have tried #NotNull, #NotEmpty and #NotBlank, but all of them seem not to be validating or throwing any kind of exception even when a blank/null value is present in the application.yml file. Any idea as to how can I validate my POJO here using the javax validation api?
PS: I am using lombok to generate my getter/setter.
Below is my sample code!
POJO Class:
#Component
#Getter
#Setter
public class Credentials {
#NotBlank
#Value("${app.username}")
private String user_name;
#NotBlank
#Value("${app.password}")
private String password;
}
Below is the application.yml file:
app:
username: '#{null}'
password: passWord
Even if I provide a blank value, I don't get any exception when I try to print these values in the console.
I think this can be applied.
#Data
#RequiredArgsConstructor
public class Credentials {
private final String user_name;
private final String password;
}
#Configuration
#Validated
public class CredentialsConfiguration {
#Bean
public Credentials credentials(
#NotBlank #Value("${app.username}") final String user_name,
#NotBlank #Value("${app.password}") final String password) {
return new Credentials(user_name, password);
}
}
Validation will only work for #ConfigurationProperties annotated classes combined with using #EnableConfigurationProperties.
The reason you don't get any exception is that #Value only looks for presence of the attribute in the properties, it doesn't care what the value of that attribute is, unless you are assigning a mis-matching datatype value.

spring boot 2: load application.properties manually

is it possible to access application.properties values while creating datasource in SpringApplication.run?
I have tried to inject value with #Value but when i need the values, the bean with #ConfigurationProperties is not created yet.
if you are referring to data source url,username and password you can use these keys :
spring.datasource.url=your_database_url_and_port_number
spring.datasource.username=your_database_username
spring.datasource.password=your_username_passsword
You need to add #EnableConfigurationProperties annotation mentioning the properties POJO name.
#SpringBootApplication
#EnableConfigurationProperties(ConfigProperties.class)
Then in the ConfigProperties class you need to add the below annotation.Prefix is what you will set in the properties file.
#ConfigurationProperties(prefix = "mail")
public class ConfigProperties {
private String hostName;
private int port;
private String from;
// standard getters and setters
}
Values in properties file.
#Simple properties
mail.hostname=host#mail.com
mail.port=9000
mail.from=mailer#mail.com

Spring bind different properties files to different beans

It's my MySQL properties file
mysql.properties
dialect=org.hibernate.dialect.MySQL57Dialect
hbm2ddl_method=validate
show_sql=true
format_sql=false
pool_name=testpool
jdbc_url=jdbc:mysql://localhost:3306/testdb
minimum_idle=2
uname=root
password=testpsw
cache_prep_stmts=true
prep_stmt_cache_size=256
prep_stmt_cache_sql_limit=2048
use_server_prep_stmts=true
maximum_pool_size=30
driver_class_name=com.mysql.jdbc.Driver
and Oracle properties file for datasource configuration.
dialect=org.hibernate.dialect.Oracle10gDialect
hbm2ddl_method=validate
show_sql=true
format_sql=false
pool_name=testpool
jdbc_url=jdbc:oracle:thin:#localhost:1521:testdb
minimum_idle=2
uname=barn_act
password=testpsw
cache_prep_stmts=true
prep_stmt_cache_size=256
prep_stmt_cache_sql_limit=2048
use_server_prep_stmts=true
maximum_pool_size=30
driver_class_name=oracle.jdbc.OracleDriver
I created two classes like this to bind properties into fields.
#Component("mysql_props")
#PropertySource(value = "classpath:/mysql.properties")
#ConfigurationProperties
#Getter
#Setter
public class HibernateMySQLProperties {
private String dialect;
//other props
}
#Component("oracle_props")
#PropertySource(value = "classpath:/oracle.properties")
#ConfigurationProperties
#Getter
#Setter
public class HibernateOracleProperties {
//same fileds as mysql
}
When I inject these two beans to PersistenceConfiguration class same propety fields are injected.
#Configuration
#EnableConfigurationProperties({ HibernateOracleProperties.class, HibernateMySQLProperties.class })
public class PersistenceConfig {
#Autowired
#Qualifier("oracle_props")
private HibernateOracleProperties oracleProps;
#Autowired
#Qualifier("mysql_props")
private HibernateMySQLProperties mysqlProps;
}
How to solve this problem ?
This is a known problem/behavior in spring prior to spring-boot. In spring property placeholder can understand unique key. Your both properties file has same name of keys.
So the solution would be like below with fewer changes.
Change the property file like below.
mysql.properties
mysql.dialect=org.hibernate.dialect.MySQL57Dialect
****** all otheres same start with mysql.
Oracle properties file
oracle.dialect=org.hibernate.dialect.Oracle10gDialect
****** all otheres same start with oracle.
Now change your Hibernate*Properties.java #ConfigurationProperties annotation.
#Component("oracle_props")
#PropertySource(value = "classpath:/oracle.properties")
#ConfigurationProperties(prefix = "oracle")
#Component("mysql_props")
#PropertySource(value = "classpath:/mysql.properties")
#ConfigurationProperties(prefix = "mysql")
No need of any change in PersistenceConfig.java file.

Instantiate a service according to a property on Spring Boot

A good practice is defining a service as an interface and its implementation on a class.
Assuming I have 2 classes which implement the same interface, and I'd like to differentiate them according a property (not to a profile). I mean, if I have #Autowire private MyServiceInterface myService; I'd like to receive an instance of PotatoServiceImpl if I have myproperty=potato or an instance of TomatoServiceImpl if I have myproperty=tomato.
I'm not using profiles.
P.S. When I say a property,I mean a property in application.properties
Look:
public interface MyInterface {
}
#Component
#ConditionalOnProperty(prefix = "myproperty" havingValue = "potato", matchIfMissing = false)
public class MyPotatoImpl implements MyInterface {
}
#Component
#ConditionalOnProperty(prefix = "myproperty" havingValue = "tomato", matchIfMissing = false)
public class MyTomatoImpl implements Myinterface {
}
#Component
public class Consumer {
#Autowire
private MyInterface tomatoOrPotato; //depending on property myproperty value
}
This is for me a very elegant solution to implement the strategy creational design pattern spring styled.
Look here for docs about #ConditionalOnProperty annotation.

Resources