Reading applicatiion.properties once in a singleton class - spring-boot

I have a singleton configuration class where I want to store all the properties for our web application.
How do we read in the application.properies file like any other properties file without using annotations?
What is the the fully qualified filename for application.properies i.e. /application.properies?
We only want to read application.properties once.

Spring boot already reads all the properties stored in application.properties and much more, read Externalized Configuration documentation.
If you want to map one property named server.port you can just use #Value("${server.port}") Integer port.
If you want to access to all the properties loaded by Spring Boot, you can use the Environment object and access to all loaded PropertySources and retrieve all values from each property source.
In this this answer shows how. However, to avoid losing the precedence order of loaded properties, you have to reverse the property source list. Here you can find the code to load all the properties without losing the spring precedence order:
#Configuration
public class AppConfiguration {
#Autowired
Environment env;
public void loadProperties() {
Map<String, Object> map = new HashMap();
for (Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator().reverse(); it.hasNext(); ) {
PropertySource propertySource = (PropertySource) it.next();
if (propertySource instanceof MapPropertySource) {
map.putAll(((MapPropertySource) propertySource).getSource());
}
}
}
}

Related

Is it possible to have application.properties depend on multiple profiles?

Is it possible to have a Spring Boot properties file depend on two or more profiles? Something like application-profile1-profile2.properties?
Spring Boot does not support this out of the box. It only supports a single profile as described here.
However, it does provide enough flexibility to add your own property sources using EnvironmentPostProcessor.
Here is an example of how to implement this:
public class MultiProfileEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private final ResourceLoader resourceLoader = new DefaultResourceLoader();
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
String[] activeProfiles = environment.getActiveProfiles();
for (int i = 2; i <= activeProfiles.length; i++) {
Generator.combination(activeProfiles).simple(i)
.forEach(profileCombination -> {
String propertySourceName = String.join("-", profileCombination);
String location = "classpath:/application-" + propertySourceName + ".properties";
if (resourceLoader.getResource(location).exists()) {
try {
environment.getPropertySources().addFirst(new ResourcePropertySource(propertySourceName, location));
} catch (IOException e) {
throw new RuntimeException("could not add property source '" + propertySourceName + "'", e);
}
}
});
}
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
Couple of things to note:
This implementation only supports .properties files but can easily be extended to .yml files as well.
getActiveProfiles already returns the profiles in an order where the last one wins. This implementation relies on this order and builds the different file names leveraging this order. i.e. if active profiles are: profile1,profile2,profile3 then application-profile1-profile3.properties is supported but application-profile3-profile1.properties isn't, and application-profile1-profile3.properties will override properties defined in application-profile1.properties or application-profile3.properties.
This implementation uses a third party library com.github.dpaukov:combinatoricslib3 to create the different sets of profiles.
The property sources are added to the front of the property source list to override existing sources. But if you have custom property sources that should take precedence you need to modify this a bit to consider them in the order, i.e. by leveraging methods like Environment.addAfter.
Registering an EnvironmentPostProcessor is done using the spring.factories file.
There are 4 ways I know.
insert .yaml or .properties programmatically like Asi Bross Said. Use ResourceLoader or YamlPropertySourceLoader to insert.
Use .yaml. but it will be replace when you have a another spring project to dependent it.
Use properties instead of profiles. (For api project)
Use one #PropertySource to define properties file A.
Get the variables from properties file A and assign them to the parameters in another #PropertySource file path expression.
For example:
resources
/-application.properties <-- remove or empty,because it will be override by application project
/-moduleA
/-application.properties <-- Intellij can identify properties files started with application-
/-application-mysql-dev.properties
/-application-psql-dev.properties
/-application-psql-prod.properties
The content of resources/moduleA/application.properties :
moduleA.custom.profile1=mysql
moduleA.custom.profile2=dev
Content of Java Config file:
#SpringBootApplication
#PropertySources({
#PropertySource("/moduleA/application.properties"),
#PropertySource("/moduleA/application-${moduleA.custom.profile1}-${moduleA.custom.profile2}.properties"),
})
public class ModuleConfig {}
Use properties instead of profiles. (For application project)
resources
/-application.properties
/-application-mysql-dev.properties
/-application-psql-dev.properties
/-application-psql-prod.properties
The content of resources/application.properties :
moduleA.custom.profile1=mysql
moduleA.custom.profile2=dev
The content of SpringMvcApplication.java:
#SpringBootApplication
#PropertySource("/application-${moduleA.custom.profile1}-${moduleA.custom.profile2}.properties")
public class SpringMvcApplication {...}

List final list of properties - Spring Cloud Config Server

Spring Cloud Config Server accepts multiple profile and returns the properties for all the profiles when I access the /env endpoint of the application. The response lists the properties specific to each profile. If same property is present in 2 different property files , the one that is defined last takes precedence. Is there a way to get the final list of property key and values that will be used by the application?
For Cloud Config Client Application
I've tried different ways and found the following (accidentally):
GET /env/.* returns full list of configuration properties
For Cloud Config Server Application
It turns out this is already implemented, but not documented well. All you need is to request json, yml or properties according to the patterns:
/{application}-{profile}.{ext}
/{label}/{application}-{profile}.{ext}
import java.util.properties;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
public class MyClass {
#Autowired
private Environment env;
Properties getProperties() {
Properties props = new Properties();
CompositePropertySource bootstrapProperties = (CompositePropertySource) ((AbstractEnvironment) env).getPropertySources().get("bootstrapProperties");
for (String propertyName : bootstrapProperties.getPropertyNames()) {
props.put(propertyName, bootstrapProperties.getProperty(propertyName));
}
return props;
}
}
Sorry... this is my first time answering a question here. I created an account specifically to
answer this question because I came upon it while researching the same issue. I found a
solution that worked for me and decided to share it.
Here goes my explanation of what was done:
I initialize a new "Properties" object (could be a HashMap or whatever else you want)
I lookup the property source for the "bootstrapProperties" which is a CompositePropertySource object.
This property source contains all of the application properties that were loaded.
I loop through all the property names returned from the "getPropertyNames" method on the CompositePropertySource object
and create a new property entry.
I return the properties object.
This seems to be an intentional limitation of the Spring Framework.
See here
You could hack it and inject the PropertySources interface, then loop over all the individual PropertySource objects, but you'd have to know what properties you're looking for.
Externalized Configuration
Spring Boot allows you to externalize your configuration so you can work with the same application code in different environments. You can use properties files, YAML files, environment variables and command-line arguments to externalize configuration. Property values can be injected directly into your beans using the #Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via #ConfigurationProperties.
Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order:
Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
#TestPropertySource annotations on your tests.
#SpringBootTest#properties annotation attribute on your tests.
Command line arguments.
Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property)
ServletConfig init parameters.
ServletContext init parameters.
JNDI attributes from java:comp/env.
Java System properties (System.getProperties()).
OS environment variables.
A RandomValuePropertySource that only has properties in random.*.
Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)
Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
Application properties outside of your packaged jar (application.properties and YAML variants).
Application properties packaged inside your jar (application.properties and YAML variants).
#PropertySource annotations on your #Configuration classes.
Default properties (specified using SpringApplication.setDefaultProperties).
The below program prints properties from spring boot environment.
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.StandardServletEnvironment;
#Component
public class EnvironmentLogger extends ApplicationObjectSupport {
#Override
protected void initApplicationContext(ApplicationContext context) throws BeansException {
Environment environment = context.getEnvironment();
String[] profiles = environment.getActiveProfiles();
if(profiles != null && profiles.length > 0) {
for (String profile : profiles) {
System.out.print(profile);
}
} else {
System.out.println("Setting default profile");
}
//Print the profile properties
if(environment != null && environment instanceof StandardServletEnvironment) {
StandardServletEnvironment env = (StandardServletEnvironment)environment;
MutablePropertySources mutablePropertySources = env.getPropertySources();
if(mutablePropertySources != null) {
for (PropertySource<?> propertySource : mutablePropertySources) {
if(propertySource instanceof MapPropertySource) {
MapPropertySource mapPropertySource = (MapPropertySource)propertySource;
if(mapPropertySource.getPropertyNames() != null) {
System.out.println(propertySource.getName());
String[] propertyNames = mapPropertySource.getPropertyNames();
for (String propertyName : propertyNames) {
Object val = mapPropertySource.getProperty(propertyName);
System.out.print(propertyName);
System.out.print(" = " + val);
}
}
}
}
}
}
}
}

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);
}
}

creating custom PropertySourcesPlaceholderConfigurer using property loaded dynamically

I am facing some challenge creating PropertySourcesPlaceholderConfigurer based on some value that is available in another property file.
I have a property file, custom-{environment}.property, which contains a value, that is needed to set location of PropertySourcesPlaceholderConfigurer.
My CustomConfiguration looks something like:
#Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(customLocation);
//Custom url location based on a String available in the properties file. This is the problem area
return propertySourcesPlaceholderConfigurer;
}
I want to populate this customLocation from the properties file. Tried autowiring Environment, but it's failing as environment is null when placeholderConfigurer() is getting called. Tried using #PropertySource("custom-${environment}.property") and then #Value("**customLocation**"), but that's also not working.
Please let me know how this can be done. Thanks in advance.
I would suggest adding an ApplicationContextInitializer to load your property files instead of a plain #PropertySource. First load your custom-{environment}.properties next your configurable properties file.
public class PropertySourceInitializer implements ApplicationContextInitializer {
private static final String DEFAULT_CONFIG_FILE = "classpath:custom-${environment}.properties";
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
final ConfigurableEnvironment env = applicationContext.getEnvironment();
final MutablePropertySources mps = env.getPropertySources();
//
Resource resource = applicationContext.getResource(env.resolvePlaceholders(DEFAULT_CONFIG_FILE));
mps.addLast(new ResourcePropertySource(resource.getDescription(), resource));
String additional = env.getProperty("name.of.property");
if (StringUtils.hasText(additional) {
Resource additionalResource = applicationContext.getResource(env.resolvePlaceholders(additional));
if (additionalResource.isReadable() ) {
mps.addLast(new ResourcePropertySource(resource.getDescription(), resource));
}
}
}
}
Trying to get it to work with a #PropertySource will be much harder as the phases in which the PropertySourcesPlaceHolderConfigurer is created is different then the one in which the #PropertySource annotations are scanned. Staged loading of #PropertySource (which is basically what you want) is quite difficult. Spring Boot also has its own loading mechanism (which actually is also a ApplicationContextInitializer.
Can you try setting your location as a system property?
#Value("#{ systemProperties['myapp.location'] }")
private String location;
You need to set "myapp.location" as system property.

How do I add things to the /info endpoint in spring boot programmatically?

How do I add things to the /info endpoint in Spring Boot programmatically? The documentation states that this is possible for the /health endpoint through the use of HealthIndicator interface. Is there something for the /info endpoint as well?
I would like to add operating system name and version and other runtime info there.
In Spring Boot 1.4, you are able to declare InfoContributer beans to make this a whole lot easier:
#Component
public class ExampleInfoContributor implements InfoContributor {
#Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
See http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#production-ready-application-info-custom for more info.
The accepted answer actually clobbers the InfoEndpoint and does not add to it.
One way I found to add to the info is, in a #Configuration class, add an #Autowired method that adds extra properties following the info.* conventions to the environment. Then InfoEndpoint will pick them up when its invoked.
You can do something like the following:
#Autowired
public void setInfoProperties(ConfigurableEnvironment env) {
/* These properties will show up in the Spring Boot Actuator /info endpoint */
Properties props = new Properties();
props.put("info.timeZone", ZoneId.systemDefault().toString());
env.getPropertySources().addFirst(new PropertiesPropertySource("extra-info-props", props));
}
One way to do what you want (in the event that you have totally custom properties you need to display) is to declare a bean of type InfoEndpoint which will override the default.
#Bean
public InfoEndpoint infoEndpoint() {
final LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
map.put("test", "value"); //put whatever other values you need here
return new InfoEndpoint(map);
}
As you can see from the code above, the map can contain whatever info you need.
In the event that the data you want to show can be retrieved by the environment and is not custom, you do not need to override the InfoEndpoint bean, but you can simply add properties to the properties file with a prefix of info. One example where the OS name is evaluated is the following:
info.os = ${os.name}
In the code above, Spring Boot will evaluate the right-hand expression before returning the property in the /info endpoint.
A final note is that there is a ton of environment information that is already available in the /env endpoint
Update
As pointed out by #shabinjo, in newer Spring Boot versions there is no InfoEndpoint constructor that accepts a map.
You can however use the following snippet:
#Bean
public InfoEndpoint infoEndpoint() {
final Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("test", "value"); //put whatever other values you need here
return new InfoEndpoint(new MapInfoContributor(map));
}
The code above will completely override the default info that would end-up in /info.
To overcome this issue one could add the following bean
#Bean
public MapInfoContributor mapInfoContributor() {
return new MapInfoContributor(new HashMap<String, Object>() {{
put("test", "value");
}});
}
It should be possible to add a custom PropertySource inside an ApplicationListener to add custom info.* properties to the environment (see this answer for an example: How to Override Spring-boot application.properties programmatically)

Resources