I would like to have a properties setup which can, on certain environments, override specific properties. For example, our default JDBC properties for dev are:
db.driverClassName=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/ourdb
db.username=root
db.password=
The problem is that some of our devs would like to have a different username/password on the db, or possibly even a non locally hosted db. The same is true for our rabbitMQ configuration, which currently uses a similar localhost, guest/guest setup. Being able to override the properties of certain elements of this configuration on a per-developer basis would allow us to move much of the infrastructure/installation requirements for building the software off the local machine and onto dedicated servers.
I have set-up a simple project to wrap my head around the configuration required to achieve what I want, and this is my first foray into the world of spring property configuration, since up till now, property loading and management is done with some custom code. Here is my setup:
class Main_PropertyTest {
public static void main(String[] args) {
String environment = System.getenv("APPLICATION_ENVIRONMENT"); // Environment, for example: "dev"
String subEnvironment = System.getenv("APPLICATION_SUB_ENVIRONMENT"); // Developer name, for example: "joe.bloggs"
System.setProperty("spring.profiles.active", environment);
System.setProperty("spring.profiles.sub", subEnvironment);
try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertyTestConfiguration.class)) {
Main_PropertyTest main = context.getBean(Main_PropertyTest.class);
main.printProperty();
}
}
private final String property;
public Main_PropertyTest(String property) {
this.property = property;
}
public void printProperty() {
System.out.println("And the property is: '" + property + "'.");
}
}
And my configuration:
#Configuration
public class PropertyTestConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer primaryPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.active") + ".main.properties"));
return propertySourcesPlaceholderConfigurer;
}
#Bean
public static PropertySourcesPlaceholderConfigurer secondaryPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.sub") + ".main.properties"));
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertySourcesPlaceholderConfigurer.setOrder(-1);
return propertySourcesPlaceholderConfigurer;
}
#Bean
public Main_PropertyTest main_PropertyTest(#Value("${main.property}") String property) {
Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
return main_PropertyTest;
}
}
And for completeness, my dev.main.properties and test.main.properties:
main.property=dev
main.property=test
The main problem is that I get an illegal argument exception. As far as I can tell, what I have written should be the javaconfig equivalent of this method: http://taidevcouk.wordpress.com/2013/07/04/overriding-a-packaged-spring-application-properties-file-via-an-external-file/
Unfortunately I get the following error: java.lang.IllegalArgumentException: Could not resolve placeholder 'main.property' in string value "${main.property}". Note that I also need to take care of the case where there is no sub-environment, and this is the case I have started with (although I get the same error even if both files exist). If I remove the bean which sets up the second propertysourcesplaceholderconfigurer, then it all works fine (by which I mean dev.main.properties is loaded and "And the property is: 'dev'." is printed out).
A secondary problem is that the code doesn't look great, and each layer of the system will need two PSPC's set-up so that they can access these properties. Furthermore, it requires a lot of manual calls to System.getProperty(), since I couldn't pass ${spring.profiles.active} to PSPC.setLocation();
Note: I have tried #PropertySources({primaryproperties, secondaryProperties}), but this fails because secondaryProperties does not exist. I have also tried #Autowired Environment environment; and getting the properties from that, but the secondary PSPC causes the environment to not be autowired...
So following this lengthy explanation, my questions are:
Is this the right way of solving this problem?
If so, what is wrong with my configuration?
How can I simplify the configuration (if at all)?
Is there an alternative mechanism available which would solve my problem?
Thank you for your time! :)
Your configuration is flawed when configuring BeanFactoryPostProcessor with java config the methods should be static. However it can be even easier, instead of registering your own PropertySourcesPlaceholderConfigurer utilize the default #PropertySource support.
Rewerite your jav config to the following
#Configuration
#PropertySource(name="main", value= "${spring.profiles.active}.main.properties")
public class PropertyTestConfiguration {
#Autowired
private Environment env;
#PostConstruct
public void initialize() {
String resource = env.getProperty("spring.profiles.sub") +".main.properties";
Resource props = new ClassPathResource(resource);
if (env instanceof ConfigurableEnvironment && props.exists()) {
MutablePropertySources sources = ((ConfigurableEnvironment) env).getPropertySources();
sources.addBefore("main", new ResourcePropertySource(props));
}
}
#Bean
public Main_PropertyTest main_PropertyTest(#Value("${main.property}") String property) {
Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
return main_PropertyTest;
}
}
This should first load the dev.main.properties and additionally the test.main.properties which will override the earlier loaded properties (when filled ofcourse).
I had a similar issue with overwriting already existing properties in integration tests
I came up with this solution:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {
SomeProdConfig.class,
MyWebTest.TestConfig.class
})
#WebIntegrationTest
public class MyWebTest {
#Configuration
public static class TestConfig {
#Inject
private Environment env;
#PostConstruct
public void overwriteProperties() throws Exception {
final Map<String,Object> systemProperties = ((ConfigurableEnvironment) env)
.getSystemProperties();
systemProperties.put("some.prop", "test.value");
}
}
Related
I would like to check that my application runs only under given set of allowed profiles.
I would write
#Autowire
Environment environment;
#PostConstruct
public void checkAndReportProfiles() {
// checks
... environment.getActiveProfiles()
but the problem is that this runs after all beans were attempted to initialize and failed if profiles were incorrect.
I want to check profiles explicitly and fail with explicit message.
Is it possible to do this?
Option 1: you can create a bean which performes the checks for you.
#Component
public class TestBean {
public TestBean() {
throw new BeanInitializationException("error message");
}
}
to execute this before certain problematic beans you can annotate theese beans with #DependsOn
But now you have a bean with no purpose hanging around.
Option 2:
Perform the checks in a configration class.
To get access to the spring evironment or configuration properties, you can inject them via constructor.
#Configuration
public class TestConfiguration {
public TestConfiguration(Environment environment, #Value("${some.key}") String property) {
String[] profiles = environment.getActiveProfiles();
// perform tests here
throw new BeanInitializationException("error message");
}
}
Option 3: Create beans programatically and check before the bean is created.
#Configuration
public class TestConfiguration {
#Value("${some.key}")
String property;
private final Environment environment;
public TestConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
public SomeBean someBean() {
if(whateverToTest) {
throw new BeanInitializationException("error message");
}
return new SomeBean();
}
}
Option 4: Perform test in the constructor of that particular bean. The constructor is called first.
#Component
public class SomeBean {
public SomeBean() {
if(whateverToTest) {
throw new BeanInitializationException("error message");
}
}
}
Option 3 is the cleanest solution in my opinion, but also the most difficult to implement. Especially if you are using constructor based injections.
You can try this.
Create a class for keeping the association profile/error
public enum ErrorProfile {
default("This is the error num 1"),
dev("This is the error num 2"),
test("This is the error num 3"),
prod("This is the error num 4");
private String profile;
private String message;
ErrorProfile(String profile, String message){
this.profile = profile;
this.profile = message;
}
}
Then create a profile checker that execute at the lunch of your spring project during the context loading.
Java provides a valueOf(String) method for all enum types.
Thus, we can always get an enum value based on the declared name. This means that, after we get the value of the profile we can retrieve the message code with:
ErrorProfile.valueOf("prod")
Then it will be enough to print it in the Logs, Exception or whatever you need.
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
#Slf4j
#Configuration
public class PropertiesLogger {
#Value("${spring.profiles.active}")
private String activeProfile;
#PostConstruct
public void printProperties() {
log.debug("Loading application-context profiles. Active profile found: {} [message: {}]",
activeProfile,
ErrorProfile.valueOf(activeProfile));
// and in this case (cause you want to rise an exception with that message)
throw new RuntimeException(ErrorProfile.valueOf(activeProfile))
}
}
To explicitly check the Spring profile, you can use the following approach:
1)Set the active profile via the --spring.profiles.active application launch option:java -jar myapp.jar --spring.profiles.active=production
2)Set the active profile via the environment:
export SPRING_PROFILES_ACTIVE=productionjava -jar myapp.jar
3)Set the active profile via an application.properties or application.yml file:
# application.properties spring.profiles.active=production
# application.yml spring: profiles: active: production
i have properties like this
connection.local=0.0.0.0
i write it on application.properties and at application-local.properties it is correct or not ?
but when i want to get this value with annotation
i use plain java for build apps. and use spring context for get value and annotations
#Component
#Scope("singleton")
#Slf4j
#Configuration
public class SocketEngine extends Thread {
/**
* This is to make sure that the server is running and trying even when
* idxdatafeed disconnects
*/
#Value("${connection.local}")
private String connectionLocalhost;
public void run() {
while (true) {
Socket server = null;
String firstData="xvabv";
try {
log.info("Connecting to server " + connectionLocalhost+"!");
server = new Socket(connectionLocalhost, 9010);
server.setSoTimeout(10000);
PrintWriter writer= new PrintWriter(server.getOutputStream());
i got value of connectionLocal is null why like that ?
You may solve your problem by injecting the value in the constructor:
public SocketEngine(#Value("${connection.local}") String connectionLocalhost) {
this.connectionLocalhost = connectionLocalhost;
run();
}
This should resolve the problem. But really I don't why spring here acts like that (tested). Also you don't need the annotation #Compnent because #Configuration includes it.
The official approach is to define the following method in your configuration class:
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
But it didn't work, and that's why I'm confused!
I solved the problem , add like below code.
#PropertySource(value = "classpath:application.properties", ignoreResourceNotFound = true)
I have defined some external properties in my main class as follows:
#PropertySources({
#PropertySource(value = "classpath:application.properties", ignoreResourceNotFound = true),
#PropertySource(value = "file:/opt/app/conf/database.properties", ignoreResourceNotFound = true),
#PropertySource(value = "file:/opt/app/conf/overrides.properties", ignoreResourceNotFound = true)
})
For some of the properties I would like to do some post processing, for example encrypting or enrichment before they are actually used by any beans. One such property is spring.datasource.password
I have done the following:
Write an ApplicationContextInitializer<ConfigurableApplicationContext> and tried to process those properties in the initialize() method
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String value = environment.getProperty("spring.datasource.password");
System.out.println("The value in initializer is " + value);
System.out.println("The database url in initializer is " + environment.getProperty("spring.datasource.url"));
System.out.println("The database username in initializer is " + environment.getProperty("spring.datasource.username"));
// .. other code
}
}
and included the above class in the META-INF/spring.factories as follows:
org.springframework.context.ApplicationContextInitializer=com.myapp.MyApplicationContextInitializer
I am seeing null values in all of the above properties that are printed though both database.properties and overrides.properties are present. They are the very first statements to be printed (even before the banner)
Another approach I tried is org.springframework.boot.env.EnvironmentPostProcessor and adding in META-INF/spring.factories as
org.springframework.boot.env.EnvironmentPostProcessor=com.myapp.PropertiesProcessor
But still, I get the same null value.
Interestingly, when I pass in the
-Dspring.config.location="file:/opt/app/conf/database.properties, file:/opt/app/conf/overrides.properties"
system property before executing the war, it works i.e. the values are printed.
But I do not want to manually pass in the system property at runtime. Is there a way to do it?
What is wrong with my code or approach. Are there any other ways of doing the post-processing before actually creating the beans?
I solved similar problem by adding property source with highest precedence.
Add META-INF/spring.factories with content:
org.springframework.boot.env.EnvironmentPostProcessor=
com.example.SettingsPropertiesAddingPostProcessor
SettingsPropertiesAddingPostProcessor implementation:
public class SettingsPropertiesAddingPostProcessor implements EnvironmentPostProcessor {
private static final String SETTINGS_CONFIG_PATH = "/tmp/settings.properties";
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
File settingsFile = new File(SETTINGS_CONFIG_PATH);
if (!settingsFile.exists()) {
log.debug("Config file not found, skipping adding custom property source");
return;
}
log.debug("Config file found, adding custom property source");
Properties props = loadProperties(settingsFile);
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addFirst(new PropertiesPropertySource("settings-source", props));
}
private Properties loadProperties(File f) {
FileSystemResource resource = new FileSystemResource(f);
try {
return PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException ex) {
throw new IllegalStateException("Failed to load local settings from " + f.getAbsolutePath(), ex);
}
}
}
That should be all.
Following the advice of #M. Deinum, regarding using "spring.config.additional-location", I have made a workaround as follows:
#SpringBootApplication
#EnableSwagger2
public class MyApp extends SpringBootServletInitializer {
public static void main(String[] args) {
System.setProperty("spring.config.additional-location", "file:/opt/app/conf/database.properties, file:/opt/app/conf/overrides.properties");
SpringApplication springApplication = new SpringApplication(MyApp.class);
springApplication.run(args);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("Setting properties onStartup");
System.setProperty("spring.config.additional-location", "file:/opt/app/conf/database.properties, file:/opt/app/conf/overrides.properties");
super.onStartup(servletContext);
}
}
I have called the System.setProperty() in the onStartup() method of the SpringBootServletInitializer by overriding it as above and then invoked the super class' onStartup()
The latter part i.e. setting system property in onStartup method helps when the application is deployed in a web container like Tomcat.
Note: We can also append the properties to spring.config.additional-location instead of set so that other additional locations can also be added during runtime.
Is there any way to load a class marked with #ConfigurationProperties without using a Spring Context directly? Basically I want to reuse all the smart logic that Spring does but for a bean I manually instantiate outside of the Spring lifecycle.
I have a bean that loads happily in Spring (Boot) and I can inject this into my other Service beans:
#ConfigurationProperties(prefix="my")
public class MySettings {
String property1;
File property2;
}
See the spring docco for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args
But now I need to access this bean from a class that is created outside of Spring (by Hibernate). The class is created so early in the app startup process that Spring Boot has not yet made the application context available through the classic lookup helper methods or roll-my-own static references.
So I instead want to do something like:
MySettings mySettings = new MySettings();
SpringPropertyLoadingMagicClass loader = new SpringPropertyLoadingMagicClass();
loader.populatePropertyValues(mySettings);
And have MySettings end up with all its values loaded, from the command line, system properties, app.properties, etc. Is there some class in Spring that does something like this or is it all too interwoven with the application context?
Obviously I could just load the Properties file myself, but I really want to keep Spring Boot's logic around using command line variables (e.g. --my.property1=xxx), or system variables, or application.properties or even a yaml file, as well as its logic around relaxed binding and type conversion (e.g. property2 is a File) so that it all works exactly the same as when used in the Spring context.
Possible or pipe dream?
Thanks for your help!
I had the same "issue".
Here is how I solved it in SpringBoot version 1.3.xxx and 1.4.1.
Let's say we have the following yaml configuration file:
foo:
apis:
-
name: Happy Api
path: /happyApi.json?v=bar
-
name: Grumpy Api
path: /grumpyApi.json?v=grrr
and we have the following ConfigurationProperties:
#ConfigurationProperties(prefix = "foo")
public class ApisProperties {
private List<ApiPath> apis = Lists.newArrayList();
public ApisProperties() {
}
public List<ApiPath> getApis() {
return apis;
}
public static class ApiPath {
private String name;
private String path;
public String getName() {
return name;
}
public void setName(final String aName) {
name = aName;
}
public String getPath() {
return path;
}
public void setPath(final String aPath) {
path = aPath;
}
}
}
Then, to do the "magic" things of Spring Boot programmatically (e.g. loading some properties in a static method), you can do:
private static ApisProperties apiProperties() {
try {
ClassPathResource resource;
resource = new ClassPathResource("/config/application.yml");
YamlPropertiesFactoryBean factoryBean;
factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setSingleton(true); // optional depends on your use-case
factoryBean.setResources(resource);
Properties properties;
properties = factoryBean.getObject();
MutablePropertySources propertySources;
propertySources = new MutablePropertySources();
propertySources.addLast(new PropertiesPropertySource("apis", properties));
ApisProperties apisProperties;
apisProperties = new ApisProperties();
PropertiesConfigurationFactory<ApisProperties> configurationFactory;
configurationFactory = new PropertiesConfigurationFactory<>(apisProperties);
configurationFactory.setPropertySources(propertySources);
configurationFactory.setTargetName("foo"); // it's the same prefix as the one defined in the #ConfigurationProperties
configurationFactory.bindPropertiesToTarget();
return apisProperties; // apiProperties are fed with the values defined in the application.yaml
} catch (BindException e) {
throw new IllegalArgumentException(e);
}
}
Here's an update to ctranxuan's answer for Spring Boot 2.x. In our situation, we avoid spinning up a Spring context for unit tests, but do like to test our configuration classes (which is called AppConfig in this example, and its settings are prefixed by app):
public class AppConfigTest {
private static AppConfig config;
#BeforeClass
public static void init() {
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(new ClassPathResource("application.yaml"));
Properties properties = factoryBean.getObject();
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(propertySource);
config = binder.bind("app", AppConfig.class).get(); // same prefix as #ConfigurationProperties
}
}
The "magic" class you are looking for is PropertiesConfigurationFactory. But I would question your need for it - if you only need to bind once, then Spring should be able to do it for you, and if you have lifecycle issues it would be better to address those (in case they break something else).
This post is going into similar direction but extends the last answer with also validation and property placeholder resolutions.
Spring Boot Binder API support for #Value Annotations
#Value annotations in ConfigurationPropertys don't seem to bind properly though (at least if the referenced values are not part of the ConfigurationProperty's prefix namespace).
import org.springframework.boot.context.properties.bind.Binder
val binder = Binder.get(environment)
binder.bind(prefix, MySettings.class).get
I'm trying to dynamically access properties from Spring's Environment property abstraction.
I declare my property files like this:
<context:property-placeholder
location="classpath:server.common.properties,
classpath:server.${my-environment}.properties" />
In my property file server.test.properties, I define the following:
myKey=foo
Then, given the following code:
#Component
public class PropertyTest {
#Value("${myKey}")
private String propertyValue;
#Autowired
private PropertyResolver propertyResolver;
public function test() {
String fromResolver = propertyResolver.getProperty("myKey");
}
}
When I run this code, I end up with propertyValue='foo', but fromResolver=null;
Receiving propertyValue indicates that the properties are being read, (and I know this from other parts of my code). However, attempting to look them up dynamically is failing.
Why? How can I dynamically look up property values, without having to use #Value?
Simply adding a <context:property-placeholder/> doesn't add a new PropertySource to the Environment. If you read the article you linked completely, you'll see it suggests registering an ApplicationContextInitializer in order to add new PropertySources so they'll be available in the way you're trying to use them.
To get this to work I had to split out the reading of the properties into a #Configuration bean, as shown here.
Here's the complete example:
#Configuration
#PropertySource("classpath:/server.${env}.properties")
public class AngularEnvironmentModuleConfiguration {
private static final String PROPERTY_LIST_NAME = "angular.environment.properties";
#Autowired
private Environment environment;
#Bean(name="angularEnvironmentProperties")
public Map<String,String> getAngularEnvironmentProperties()
{
String propertiesToInclude = environment.getProperty(PROPERTY_LIST_NAME, "");
String[] propertyNames = StringUtils.split(propertiesToInclude, ",");
Map<String,String> properties = Maps.newHashMap();
for (String propertyName : propertyNames)
{
String propertyValue = environment.getProperty(propertyName);
properties.put(propertyName, propertyValue);
}
return properties;
}
}
The set of properties are then injected elsewhere, to be consumed.