Why I cannot load property POJO using YAML in Spring Boot? - spring

I have read numerous posts, questions, examples and all that it should work but for whatever reasons it does not....
Can anyone tell me why this does not work??
#Configuration()
#PropertySource("users.yml")
#Data
#ConfigurationProperties
#EnableConfigurationProperties
public class UserConfig {
public String test;
public List<String> states;
public List<User> users;
#PostConstruct
public void print() {
System.out.println(this);
System.exit(0);
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
private String username;
private String pw;
private List<String> roles;
}
And the output is UserConfig(test='yeah im tested', states=[], users=[])
for given YAML
test: 'yeah im tested'
states:
- 'Up'
- 'Down'
users:
-
username: 'Gienek'
pw: 'niegienek'
roles:
- 'rola1'
- 'rola2'
Note:
using states: 'Up','Down' fills states list.
Please note also that it works partially.

To be more precise, it is a "known" fact that Spring Boot does not want to support #PropertySource to support yml files properly, as this not a "standard" to use #Propertysource in a Spring boot project... Here is the link of the discussion:
https://github.com/spring-projects/spring-framework/issues/18486.
You can also try using, like recommended, a EnvironmentPostProcessor : https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-environment-or-application-context
I'd recommend any to give a try to application.yml, unless you have an exploitation constraint.
One solution is to copy the content of your users.yml into application.yml which will definitly fits your needs.
src/main/resources/application.yml
test: 'yeah im tested'
states:
- 'Up'
- 'Down'
users:
-
username: 'Gienek'
pw: 'niegienek'
roles:
- 'rola1'
- 'rola2'
UserConfig.java
#Data
#Configuration
#ConfigurationProperties
#EnableConfigurationProperties
public class UserConfig {
...
}

It seams that this is simply limitation of how Spring hadles yaml files and I am out of luck without custom implementation.
https://github.com/spring-projects/spring-framework/issues/16381

Related

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

What's the best way to load a yaml file to Map(not an environment configuration file) in spring boot web project?

In my data framework layer, I'd like to read an yaml from src/main/resources.
The file name is mapconfigure.yaml. It's associated with the business data, not just environment configuration data.
Its content's like:
person1:
name: aaa
addresses:
na: jiang
sb: su
person2:
name: bbb
addresses:
to: jiang
bit: su
I want to store this information into a HashMap.
Does it mean to use some spring annotation like #ConfigurationProperties?
How to achieve this in details?
In addition, I can't change the file name. It means I have to use mapconfigure.yaml as the file name, not application.yml or application.properties.
The structure of my HashMap is as follows:
HashMap<String, Setting>
#Data
public class Setting{
private String name;
private HashMap<String, String> addresses
}
My expected HashMap's as follows:
{person1={name=aaa, addresses={na=jiang, sb=su}}, person2={name=bbb, addresses={to=jiang, bit=su}}}
I'm not sure if I can use YamlMapFactoryBean class to do this.
The return type of the getObject method in YamlMapFactoryBean class is Map<String, Object>, not a generic type, like Map<String, T>.
Spring boot doc just said
Spring Framework provides two convenient classes that can be used to load YAML documents. The YamlPropertiesFactoryBean will load YAML as Properties and the YamlMapFactoryBean will load YAML as a Map.
But there isn't a detailed example.
UPDATE:
In github, I created a sample. It's Here.
In this sample, I want to load myconfig.yaml to theMapProperties object in SamplePropertyLoadingTest class. Spring boot version is 1.5.1, so I can't use location attribute of #ConfigurationProperties.
How to do this?
You can indeed achieve this with #ConfigurationProperties.
From Spring Boot 1.5.x onwards (lack of #ConfigurationProperies locations attr.):
new SpringApplicationBuilder(Application.class)
.properties("spring.config.name=application,your-filename")
.run(args);
#Component
#ConfigurationProperties
public class TheProperties {
private Map<String, Person> people;
// getters and setters are omitted for brevity
}
In Spring Boot 1.3.x:
#Component
#ConfigurationProperties(locations = "classpath:your-filename.yml")
public class TheProperties {
private Map<String, Person> people;
// getters and setters are omitted for brevity
}
The Person class for above examples looks like this:
public class Person {
private String name;
private Map<String, String> addresses;
// getters and setters are omitted for brevity
}
I have tested the code with the following file: your-filename.yml
defined in src/main/resources, the contents:
people:
person1:
name: "aaa"
addresses:
na: "jiang"
sb: "su"
person2:
name: "bbb"
addresses:
to: "jiang"
bit: "su"
Please let me know if you need any further assistance.
try this
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
PropertySource<?> applicationYamlPropertySource = loader.load(
"properties", new ClassPathResource("application.yml"), null);// null indicated common properties for all profiles.
Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
Properties properties = new Properties();
properties.putAll(source);
return properties;
} catch (IOException e) {
LOG.error("application.yml file cannot be found.");
}

Spring Data Key Value Implementation for Oracle KV

I would like to use Oracle NoSQL database together with Spring data. The aim is to access the data over spring data repositories and even use spring data rest on top of it.
So I think the spring-data-keyvalue project would help me, to implement an adapter for Oracle NoSQL KV.
I tried to understand the documentation of spring-data-keyvalue (http://docs.spring.io/spring-data/keyvalue/docs/current/reference/html/#key-value.core-concepts), but didn't get the idea.
An example/tutorial about how to implement an adapter from scratch would be very helpful.
What I have is this configuration class where I provide a custom KeyValueAdapter. Now if I use CrudRepository methods it uses my custom adapter.
#Configuration
#EnableMapRepositories
public class KeyValueConfig {
#Bean
public KeyValueOperations keyValueTemplate() {
return new KeyValueTemplate(new OracleKeyValueAdapter());
}
}
The OracleKeyValueAdapter is an implementation of KeyValueAdapter. I got this from the spring-data-keyvalue-redis project (https://github.com/christophstrobl/spring-data-keyvalue-redis/blob/master/src/main/java/org/springframework/data/keyvalue/redis/RedisKeyValueAdapter.java)
public class OracleKeyValueAdapter extends AbstractKeyValueAdapter {
private KVStore store;
public OracleKeyValueAdapter() {
String storeName = "kvstore";
String hostName = "localhost";
String hostPort = "5000";
store = KVStoreFactory.getStore
(new KVStoreConfig(storeName, hostName + ":" + hostPort));
}
//Custom implementations:
#Override
public Object put(Serializable serializable, Object o, Serializable
serializable1) {
return null;
}
#Override
public boolean contains(Serializable serializable, Serializable
serializable1) {
return false;
}
.
.
.
Now I'm trying to implement this OracleKeyValueAdapter, but i don't know if that does even make sense.
Can you help me?
You might want to start with how spring-data-keyvalue is implemented over Redis, the link here should be a good starting point - http://docs.spring.io/spring-data/data-keyvalue/docs/1.0.0.BUILD-SNAPSHOT/reference/redis.html
Let me know how that goes, I am interested in what you are trying to accomplish.
The following configuration should work (tested on v2.4.3)
#Configuration
#EnableMapRepositories
public class Configuration {
#Bean
public KeyValueOperations mapKeyValueTemplate() {
return new KeyValueTemplate(keyValueAdapter());
}
#Bean
public KeyValueAdapter keyValueAdapter() {
return new YourKeyValueAdapter();
}
}
The name (mapKeyValueTemplate) of the KeyValueOperations bean is important here but it can also be changed as followed:
#Configuration
#EnableMapRepositories(keyValueTemplateRef = "foo")
public class Configuration {
#Bean
public KeyValueOperations foo() {
return new KeyValueTemplate(keyValueAdapter());
}
#Bean
public KeyValueAdapter keyValueAdapter() {
return new YourKeyValueAdapter();
}
}
I saw sources of Spring KeyValue Repository:
https://github.com/spring-projects/spring-data-keyvalue
I recomend to understand, how Spring Repository work inside.
If you want to realise own repository (CustomKeyValueRepository), you must create at least 6 classes:
EnableCustomKeyValueRepositories - annotation to enable repository type in your project.
CustomKeyValueRepositoriesRegistrar - registrator for this annotaion.
CustomKeyValueRepository - repository
CustomKeyValueRepositoryConfigurationExtension - implementation of Spring ConfigurationExtension.
CustomKeyValueAdapter - implementation of custom adapter for your data store.
CustomKeyValueConfiguration - configuration of beans Adapter and Template.
I code Infinispan KeyValue Repository by this way:
https://github.com/OsokinAlexander/infinispan-spring-repository
I also write article about this:
https://habr.com/ru/post/535218/
In Chrome you can translate it to your language.
The simplest way you can try implement only CustomKeyValueAdapter and Configuration. In Configuration you must redefine Spring KeyValueAdapter bean and KeyValueTemplate (it is very important that the name of the bean is with a lowercase letter, that's the only way it worked for me):
#Configuration
public class CustomKeyValueConfiguration extends CachingConfigurerSupport {
#Autowired
private ApplicationContext applicationContext;
#Bean
public CustomKeyValueAdapter getKeyValueAdapter() {
return new CustomKeyValueAdapter();
}
#Bean("keyValueTemplate")
public KeyValueTemplate getKeyValueTemplate() {
return new KeyValueTemplate(getKeyValueAdapter());
}
}

how to load property file in to spring boot project with annotations?

I have written queries in property file. I want to read the property file in to one class with annotations in spring boot. How can i read it? And is there any better approach for writing queries in spring boot project?
If you add your properties in application.properties file, you can read them inside the spring boot classes like:
#Service
public class TwitterService {
private final String consumerKey;
private final String consumerKeySecret;
#Autowired
public TwitterService(#Value("${spring.social.twitter.appId}") String consumerKey, #Value("${spring.social.twitter.appSecret}") String consumerKeySecret) {
this.consumerKey = consumerKey;
this.consumerKeySecret = consumerKeySecret;
} ...
You can annotate fields in your components by #Value("${property.name}")
Else, you can use Properties Object in java.util package.
For example, i have a mode property, which values are dev or prod, i can use it in my beans as follow :
#Value("${mode:dev}")
private String mode;
The other approach is by using :
Properties pro = new Properties();
pro.load(this.getClass().getClassLoader().getResourceAsStream());
You can use #PropertySource to read the properties from a file and then pass them to a bean. If you have a file called "queries.properties" that has a property like:
query1: select 1 from foo
Then your config might look like:
#PropertySource("classpath:queries.properties")
#Configuration
public class MyConfig {
#Bean
public DbBean dbBean(#Value("${queries.query1}") String query) {
return new DbBean(query);
}
}

How to access a Spring Boot Application's name programmatically?

I've defined an application name using the bootstrap.yml file in my spring boot application.
spring:
application:
name: abc
How can i get this application name during runtime/programmatically ?
You should be able to use the #Value annotation to access any property you set in a properties/YAML file:
#Value("${spring.application.name}")
private String appName;
#Autowired
private ApplicationContext applicationContext;
...
this.applicationContext.getId();
Please, find this:
# IDENTITY (ContextIdApplicationContextInitializer)
spring.application.name=
spring.application.index=
In Spring Boot Reference Manual.
And follow with source code for that ContextIdApplicationContextInitializer class:
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setId(getApplicationId(applicationContext.getEnvironment()));
}
Where the default behavior is with this:
/**
* Placeholder pattern to resolve for application name
*/
private static final String NAME_PATTERN = "${vcap.application.name:${spring.application.name:${spring.config.name:application}}}";
Since the #Value annotation is discouraged in Spring Boot when referencing configuration properties, and because applicationContext.getId(); doesn't always return the value of spring.application.name another way is to get the value from the Environment directly
private final Environment environment;
...
public MyBean(final Environment environment) {
this.environment = environment;
}
...
private getApplicationName() {
return this.environment.get("spring.application.name");
}
Another possible way would be to create your own ConfigurationProperties class to get access to the value.
I'm not saying these are the best ways, and I hope/wish that there is a better way, but it is a way.
Note! If your using a SpringBootTest, you need to suplly the properties/yml.
Otherwise, the environment/appcontext does not load the config files.
The, your app name is not set.
Like so:
#PropertySource("classpath:application.properties")
#RunWith(SpringRunner.class)
#SpringBootTest
....
This post is aged but I hate unanswered questions.
So use the following snippet:
#Value("${spring.application.name [: defaultValue]}")
private String appName;
What is between [] is optional.
So I found a really ugly way to do this, but it works so I'm not searching further. Maybe this will help someone.
The basic premise is that spring Environment stores the value inside a propertySource.. It appears that bootstrap config is stored in the ResourcePropertySource and so you can get it from that. For me it is currently throwing an exception, but then I can get the value out of the exception, so I haven't looked any further:
try {
this.environment.getProperty("name", ResourcePropertySource.class);
} catch (ConversionFailedException e) {
String res = (String)e.getValue();
}
And then you can just do this for every property you are interested in.
Like I said ugly, but it works.

Resources