I'm using Spring boot for my project and trying to load yaml files so that I can use the data in the files in my project. For example I have content in my application.yml like below.
currency:
code:
840: 2
484: 2
999: 0
And in my code for reading the content from application.yml I have a class like this.
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#Configuration
#ConfigurationProperties(prefix = "currency")
public class Currency {
private Map<String, String> code;
public Map<String, String> getCode() {
return code;
}
public void setCode(Map<String, String> code) {
this.code = code;
}
}
And If I print it in the test class
public class Test{
#Autowired
Currency currency;
Map<String, String> test = currency.getCode();
for (Map.Entry<String, String> entry : test.entrySet()) {
System.out.println("Key : " + entry.getKey() + " Value : " + entry.getValue());
}
}
I'm getting like below which is perfect.
Key : 840 Value : 2
Key : 484 Value : 2
Key : 999 Value : 0
This is working if I keep the application.yml in my jar itself or I can read it by placing it in git repo aswell.
I tried keeping the content in currency.yml and in my application.yml I tried to use spring.config.location, so that I can read the content from currency.yml directly but it didn't work.
I would like to load files like currency.yml and codes.yml etc... which are custom yml files so that I can read multiple files content and use in my application. Are there any annotations or some approach I can use to load custom.yml files?
In the .yml file of the project add below content.
spring:
profiles:
include:
- currency
Note: You can refer to multiple .yml files this way if you want. You need additional .yml files and Config classes like below example.
You need to have another .yml file which is application-currency.yml
In application-currency.yml you can add currencies, like below
currencies:
mappings:
USD:
fraction: 2
symbol: $
text: "US Dollar"
MXN:
fraction: 2
symbol: $
text: "Mexican Peso"
Your Configuration class would look like below.
package com.configuration;
import com.Currency;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
#Component
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "currencies")
public class CurrencyConfiguration {
private Map<String, Currency> mappings;
public Map<String, Currency> getMappings() {
return mappings;
}
public void setMappings(Map<String, Currency> mappings) {
this.mappings = mappings;
}
}
Wherever you need to use the currency details you can get by calling as shown below.
#Autowired
private CurrencyConfiguration currencyConfiguration;
String currencyCodeAlpha3 = "USD";
Currency currency = currencyConfiguration.getMappings().get(currencyCodeAlpha3);
You can use spring.config.location to specify paths to additional config files as a comma separated list.
java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
Related
I am trying to load a yml file with the following element into an object in a test class
yml file name is application-acl-config.yml and there are more yml files in the folder. I assume spring boot merges all of them into one when the application starts.
yml file in resources of main folder:
logicalClientIdentifiers:
- applicationIdentifier:
applicationId: x
logicalClientIds:
- x
- applicationIdentifier:
applicationId: y
logicalClientIds:
- y
code in main folder for pojo class:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "logicalClientIdentifiers")
public class LogicalClientIdentifiers{
private class ApplicationIdentifier {
private String applicationId;
private List<String> logicalClientIds;
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
public List<String> getLogicalClientIds() {
return logicalClientIds;
}
public void setLogicalClientIds(List<String> logicalClientIds) {
this.logicalClientIds = logicalClientIds;
}
}
private List<ApplicationIdentifier> applicationIdentifiers;
public List<ApplicationIdentifier> getApplicationIdentifiers() {
return applicationIdentifiers;
}
public void setApplicationIdentifiers(List<ApplicationIdentifier> applicationIdentifiers) {
this.applicationIdentifiers = applicationIdentifiers;
}
}
test code in test folder:
#RunWith(value = SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = LogicalClientIdentifiers.class, initializers = ConfigFileApplicationContextInitializer.class)
public class ChannelAuthorizationTest {
#Autowired
LogicalClientIdentifiers applicationAclConfigProps;
#Test
public void test(){
System.out.println();
}
}
I am getting this exception:
`Caused by: org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'logicalClientIdentifiers': Could not bind properties to 'LogicalClientIdentifiers' : prefix=logicalClientIdentifiers, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is org.springframework.boot.context.properties.source.InvalidConfigurationPropertyNameException: Configuration property name 'logicalClientIdentifiers' is not` valid
I believe I am not creating the pojo properly to parse the yml file or the yml file is not being referenced properly.
Your Java class looks like you want to load this YAML:
logicalClientIdentifiers:
applicationIdentifiers:
- applicationId: x
logicalClientIds:
- x
- applicationId: y
logicalClientIds:
- y
You need to be careful that the YAML structure and your class structure match. The YAML you show misses the applicationIdentifiers field in the class LogicalClientIdentifiers and instead contains a sequence of mappings where each contains only a key applicationIdentifier, which does not map to anything in your Java classes.
Your error message occurs because you specify an invalid prefix. As described in the docs,
The prefix value for the annotation must be in kebab case (lowercase and separated by -, such as acme.my-project.person).
so you have to use #ConfigurationProperties(prefix = "logical-client-identifiers").
To read from yaml file i am doing the following
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties("partners")
public class YAMLConfig {
private Map<String,String> partners = new HashMap<>();
public void setPartners(Map<String, String> partners) {
this.partners = partners;
}
public Map<String, String> getPartners() {
return partners;
}
}
yaml file
person2:
name: bbb
addresses:
to: jiang
bit: su
partners:
p1: wallet
p2: wallet
p3: wallet
And in other Java file i am Autowiring to get YamlConfig above
#Service
public class ObjectModificationService {
#Autowired
YAMLConfig yamlConfig;
public JSONObject modify(JSONObject jsonObject ) {
String type = yamlConfig.getPartners().get(partnerName.toLowerCase());
}
}
I am not getting any type above , also checked yamlConfig.getPartners() in debug it is coming as null. I was following this tutorial: https://www.baeldung.com/spring-yaml, but then again IDE is showing error when i put ConfigurationProperties without ConfigurationProperties("partners").
In your yml configuration class, you have the properties annotation set to "partners", this means that the YAMLConfig class will attempt to read the partners section only. The "person2" section will not be read.
To retrieve the dependency injected values, simply call: yamlConfig.getPartners()
Also, be sure that the application.yml is in the resources directory and/or in the same directory as the .jar
you have specified Partners map accepts String values. so, try Map value formatted with quotes ('') in yaml config file
person2:
name: bbb
addresses:
to: jiang
bit: su
partners:
p1: 'wallet'
p2: 'wallet'
p3: 'wallet'
For my Spring Boot application, I am trying to use an environment variable that holds the list of properties.topics in application.yml (see configuration below).
properties:
topics:
- topic-01
- topic-02
- topic-03
I use the configuration file to populate properties bean (see this spring documentation), as shown below
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
#ConfigurationProperties("properties")
public class ApplicationProperties {
private List<String> topics = new ArrayList<>();
public void setTopics(List<String> topics) {
this.topics = topics;
}
public List<String> getTopics() {
return this.topics;
}
}
With the use of environment variable, I can have the list's content change without changing the application.yml. However, all examples that I could find so far only for cases where an environment variable holding only single value, not a collection of values in my case.
Edit:
To make it clear after #vancleff's comment, I do not need the values of the environment variable to be saved to application.yml.
Another edit:
I think by oversimplifying my question, I shoot myself in the foot. #LppEdd answer works well with the example given in my question. However, what happens if instead of a collection of simple string topic names, I need a bit more complex structure. For example, something like
properties:
topics:
-
name: topic-01
id: id-1
-
name: topic-02
id: id-2
-
name: topic-03
id: id-3
a bit late for the show but, I was facing the same problem and this solves it
https://github.com/spring-projects/spring-boot/wiki/Relaxed-Binding-2.0#lists-1
MY_FOO_1_ = my.foo[1]
MY_FOO_1_BAR = my.foo[1].bar
MY_FOO_1_2_ = my.foo[1][2]`
So, for the example in the question:
properties:
topics:
-
name: topic-01
id: id-1
-
name: topic-02
id: id-2
-
name: topic-03
id: id-3
The environment variables should look like this:
PROPERTIES_TOPICS_0_NAME=topic-01
PROPERTIES_TOPICS_0_ID=id-01
PROPERTIES_TOPICS_1_NAME=topic-02
PROPERTIES_TOPICS_1_ID=id-02
PROPERTIES_TOPICS_2_NAME=topic-03
PROPERTIES_TOPICS_2_ID=id-03
Suggestion, don't overcomplicate.
Say you want that list as an Environment variable. You'd set it using
-Dtopics=topic-01,topic-02,topic-03
You then can recover it using the injected Environment Bean, and create a new List<String> Bean
#Bean
#Qualifier("topics")
List<String> topics(final Environment environment) {
final var topics = environment.getProperty("topics", "");
return Arrays.asList(topics.split(","));
}
From now on, that List can be #Autowired.
You can also consider creating your custom qualifier annotation, maybe #Topics.
Then
#Service
class TopicService {
#Topics
#Autowired
private List<String> topics;
...
}
Or even
#Service
class TopicService {
private final List<String> topics;
TopicService(#Topics final List<String> topics) {
this.topics = topics;
}
...
}
What you could do is use an externalized file.
Pass to the environment parameters the path to that file.
-DtopicsPath=C:/whatever/path/file.json
Than use the Environment Bean to recover that path. Read the file content and ask Jackson to deserialize it
You'd also need to create a simple Topic class
public class Topic {
public String name;
public String id;
}
Which represents an element of this JSON array
[
{
"name": "topic-1",
"id": "id-1"
},
{
"name": "topic-2",
"id": "id-2"
}
]
#Bean
List<Topic> topics(
final Environment environment,
final ObjectMapper objectMapper) throws IOException {
// Get the file path
final var topicsPath = environment.getProperty("topicsPath");
if (topicsPath == null) {
return Collections.emptyList();
}
// Read the file content
final var json = Files.readString(Paths.get(topicsPath));
// Convert the JSON to Java objects
final var topics = objectMapper.readValue(json, Topic[].class);
return Arrays.asList(topics);
}
Also facing the same issue , fixed with having a array in deployment.yaml from values.yml replacing the default values of application.yml
example as :
deployment.yml -
----------------
env:
- name : SUBSCRIBTION_SITES_0_DATAPROVIDER
value: {{ (index .Values.subscription.sites 0).dataprovider | quote }}
- name: SUBSCRIBTION_SITES_0_NAME
value: {{ (index .Values.subscription.sites 0).name | quote }}
values.yml -
---------------
subscription:
sites:
- dataprovider: abc
name: static
application.yml -
------------------
subscription:
sites:
- dataprovider: ${SUBSCRIBTION_SITES_0_DATAPROVIDER:abcd}
name: ${SUBSCRIBTION_SITES_0_NAME:static}
Java Code :
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "subscription")
public class DetailsProperties {
private List<DetailsDto> sites;
DetailsProperties() {
this.sites = new ArrayList<>();
}
}
Pojo mapped :
#Getter
#Setter
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class DetailsDto {
private String dataprovider;
private String name;
}
I built a quick little utility to do this.
import java.util.LinkedList;
import java.util.List;
import org.springframework.core.env.Environment;
/**
* Convenience methods for dealing with properties.
*/
public final class PropertyUtils {
private PropertyUtils() {
}
public static List<String> getPropertyArray(Environment environment, String propertyName) {
final List<String> arrayPropertyAsList = new LinkedList<>();
int i = 0;
String value;
do {
value = environment.getProperty(propertyName + "[" + i++ + "]");
if (value != null) {
arrayPropertyAsList.add(value);
}
} while (value != null);
return arrayPropertyAsList;
}
}
You could modify this without too many changes to support multiple fields as well. I've seen similar things done to load an array of database configurations from properties.
This question already has answers here:
How to inject a Map using the #Value Spring Annotation?
(8 answers)
Closed 2 years ago.
I need to fetch static key value pair data from application.properties file.Is it possible to do so with SpringBoot annotation #Value.
Suggestions would be appreciated.
Example:
languageMap={'1'='English','2'='French'}
#Value($("languageMap"))
Map<String,String> languageMap;
You can inject Map using #ConfigurationProperties Annotation as mentioned in the docs.
https://docs.spring.io/spring-boot/docs/1.2.3.RELEASE/reference/htmlsingle/#boot-features-external-config-loading-yaml]
According that docs you can load properties:
language.map[0]='English'
language.map[1]='French'
#ConfigurationProperties(prefix="language")
public class LanguageMap{
private List<String> languages= new ArrayList<String>();
public List<String> getLanguages() {
return this.languages;
}
}
application.properties:
languageMap[1]= English
languageMap[2]= French
Code, just use #ConfigurationProperties and setter method(setLanguageMap) is mandatory for the Map field, otherwise you don't get values.
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController("/myclass")
#ConfigurationProperties
public class TestControllerEx {
Map<String, String> languageMap;
#GetMapping
public ResponseEntity control() {
System.out.println(getLanguageMap());
return new ResponseEntity("success", HttpStatus.OK);
}
public Map<String, String> getLanguageMap() {
return languageMap;
}
public void setLanguageMap(Map<String, String> languageMap) {
this.languageMap = languageMap;
}
}
output:
{1=English, 2=French}
Yes, it is possible using #ConfigurableProperties. You have to create a class to access those properties. Take a look at this. In that example, see how additionalHeaders are being accessed. That will help you.
Use #ConfigurableProperties and restructure your properties file:
#Configuration
#PropertySource("<prop-file-path>")
#ConfigurationProperties()
public class ConfigProperties {
#Value($("languageMap"))
Map<String,String> languageMap;
}
Properties File:
languageMap.1=English
languageMap.2=French
Spring-MVC's #RequestMapping annotation has parameter "name" which can be used for identifying each resource.
For some circumstances I need access this information on fly: retrieve mapping details (e.g. path) by given name.
Sure I can scan for classes for this annotation and retrieve needed instances via:
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(RequestMapping.class));
// ... find classes ... go through its methods ...
But it is quite ugly. Is any more simple solution?
You can use RequestMappingHandlerMapping to get all your mappings and filter them based on name. Following is a code snippet which creates a rest api and returns the path details of a api/mapping.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
#RestController
public class EndpointController {
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#GetMapping("endpoints/{name}")
public String show(#PathVariable("name") String name) {
String output = name + "Not Found";
Map<RequestMappingInfo, HandlerMethod> methods = this.handlerMapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : methods.entrySet()) {
if (entry.getKey().getName() != null && entry.getKey().getName().equals(name)) {
output = entry.getKey().getName() + " : " + entry.getKey();
break;
}
}
return output;
}
}
The above is just an example, You can use the RequestMappingHandlerMapping anyway you want until you can autowire it.