I am moving a working project from using SpringBoot command line arguments to reading properties from a file. Here are the involved portions of the @Configuration
class:
@Configuration
class RemoteCommunication {
@Inject
StandardServletEnvironment env
@Bean
static PropertySourcesPlaceholderConfigurer placeholderConfigurer () {
// VERIFIED this is executing...
PropertySourcesPlaceholderConfigurer target = new PropertySourcesPlaceholderConfigurer()
// VERIFIED this files exists, is readable, is a valid properties file
target.setLocation (new FileSystemResource ('/Users/me/Desktop/mess.properties'))
// A Debugger does NOT show this property source in the inject Environment
target
}
@Bean // There are many of these for different services, only one shown here.
MedicalSorIdService medicalSorIdService () {
serviceInstantiator (MedicalSorIdService_EpicSoap, 'uri.sor.id.lookup.internal')
}
// HELPER METHODS...
private <T> T serviceInstantiator (final Class<T> classToInstantiate, final String propertyKeyPrimary) {
def value = retrieveSpringPropertyFromConfigurationParameter (propertyKeyPrimary)
classToInstantiate.newInstance (value)
}
private def retrieveSpringPropertyFromConfigurationParameter (String propertyKeyPrimary) {
// PROBLEM: the property is not found in the Environment
def value = env.getProperty (propertyKeyPrimary, '')
if (value.isEmpty ()) throw new IllegalStateException ('Missing configuration parameter: ' + "\"$propertyKeyPrimary\"")
value
}
Using @Value
to inject the properties does work, however I'd rather work with the Environment
directly if at all possible. If the settings are not in the Environment
then I am not exactly sure where @Value
is pulling them from...
env.getProperty()
continues to work well when I pass in command line arguments specifying the properties though.
Any suggestions are welcome!
The issue here is the distinction between PropertySourcesPlaceholderConfigurer
and StandardServletEnvironment
, or Environment
for simplicity.
The Environment
is an object that backs the whole ApplicationContext
and can resolve a bunch of properties (the Environment
interface extends PropertyResolver
). A ConfigurableEnvironment
has a MutablePropertySources
object which you can retrieve through getPropertySources()
. This MutablePropertySources
holds a LinkedList
of PropertySource
objects which are checked in order to resolve a requested property.
PropertySourcesPlaceholderConfigurer
is a separate object with its own state. It holds its own MutablePropertySources
object for resolving property placeholders. PropertySourcesPlaceholderConfigurer
implements EnvironmentAware
so when the ApplicationContext
gets hold of it, it gives it its Environment
object. The PropertySourcesPlaceholderConfigurer
adds this Environment
's MutablePropertySources
to its own. It then also adds the various Resource
objects you specified with setLocation()
as additional properties. These Resource
objects are not added to the Environment
's MutablePropertySources
and therefore aren't available with env.getProperty(String)
.
So you cannot get the properties loaded by the PropertySourcesPlaceholderConfigurer
into the Environment
directly. What you can do instead is add directly to the Environment
's MutablePropertySouces
. One way is with
@PostConstruct
public void setup() throws IOException {
Resource resource = new FileSystemResource("spring.properties"); // your file
Properties result = new Properties();
PropertiesLoaderUtils.fillProperties(result, resource);
env.getPropertySources().addLast(new PropertiesPropertySource("custom", result));
}
or simply (thanks @M.Deinum)
@PostConstruct
public void setup() throws IOException {
env.getPropertySources().addLast(new ResourcePropertySource("custom", "file:spring.properties")); // the name 'custom' can come from anywhere
}
Note that adding a @PropertySource
has the same effect, ie. adding directly to the Environment
, but you're doing it statically rather than dynamically.
In SpringBoot it's enough to use @EnableConfigurationProperties
annotation - you don't need to setup PropertySourcesPlaceholderConfigurer
.
Then on POJO you add annotation @ConfigurationProperties and Spring automatically injects your properties defined in application.properties.
You can also use YAML files - you just need to add proper dependency (like SnakeYaml) to classpath
You can find detailed example here: http://spring.io/blog/2013/10/30/empowering-your-apps-with-spring-boot-s-property-support
I achieved this during PropertySourcesPlaceholderConfigurer
instantiation.
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurerBean(Environment env) {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yamlFactorybean = new YamlPropertiesFactoryBean();
yamlFactorybean.setResources(determineResources(env));
PropertiesPropertySource yampProperties = new PropertiesPropertySource("yml", yamlFactorybean.getObject());
((AbstractEnvironment)env).getPropertySources().addLast(yampProperties);
propertySourcesPlaceholderConfigurer.setProperties(yamlFactorybean.getObject());
return propertySourcesPlaceholderConfigurer;
}
private static Resource[] determineResources(Environment env){
int numberOfActiveProfiles = env.getActiveProfiles().length;
ArrayList<Resource> properties = new ArrayList(numberOfActiveProfiles);
properties.add( new ClassPathResource("application.yml") );
for (String profile : env.getActiveProfiles()){
String yamlFile = "application-"+profile+".yml";
ClassPathResource props = new ClassPathResource(yamlFile);
if (!props.exists()){
log.info("Configuration file {} for profile {} does not exist");
continue;
}
properties.add(props);
}
if (log.isDebugEnabled())
log.debug("Populating application context with properties files: {}", properties);
return properties.toArray(new Resource[properties.size()]);
}
Maybe all you need is to set -Dspring.config.location=...
(alternatively SPRING_CONFIG_LOCATION
as an env var)? That has the effect of adding an additional config file to the default path for the app at runtime which takes precedence over the normal application.properties
? See howto docs for details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With