Reputation: 61
I'd like to add a new property source that could be used to read property values in an application. I'd like to do this using Spring. I have a piece of code like this in a @Configuration class:
@Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
MutablePropertySources sources = new MutablePropertySources();
MyCustomPropertySource propertySource = new MyCustomPropertySource("my custom property source");
sources.addFirst(propertySource);
properties.setPropertySources(sources);
return properties;
}
This seems to work pretty well. However, what it is also doing is overriding other property values (e.g. server.port property in application.properties file used by spring boot) which I don't want to overwrite. So the basic question is what's the best way to add this propertysource but not have it override other properties. Any way to grab the existing propertysources and simply add on to it?
Upvotes: 6
Views: 15280
Reputation: 2245
You are searching for EnvironmentPostProcessor
You can for example read out database properties with it and make them available on the spring environment:
@Order
public class CustomPropertiesProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(
ConfigurableEnvironment environment, SpringApplication application) {
...
final Map<String, Object> properties = new HashMap<>(100);
// Environment
environment
.getPropertySources()
.addFirst(new MapPropertySource("yourPrefix", properties));
}
Do not forget to have it registered in /META-INF/spring.factories
(should be in the resources folder in case of a maven project):
org.springframework.boot.env.EnvironmentPostProcessor=\
com.your.package.CustomPropertiesProcessor
You can inject PropertyResolver
to check how to access the properties (because you do not need to put the prefix on the key).
Upvotes: 0
Reputation: 3626
I got this working by adding a custom initiailizer to my spring boot app:
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
new SpringApplicationBuilder(MyApp.class)
.initializers(new MyContextInitializer()) // <---- here
.run(args);
}
}
Where MyContextInitializer
contains: -
public class MyContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
// Create map for properites and add first (important)
Map<String, Object> myProperties = new HashMap<>();
myProperties.put("some-prop", "custom-value");
environment.getPropertySources().addFirst(
new MapPropertySource("my-props", myProperties));
}
}
Note, if your application.yaml
contains: -
some-prop: some-value
another-prop: this is ${some-prop} property
Then the initialize
method will update the some-prop
to custom-value
and when the app loads it will have the following values at run-time:
some-prop: custom-value
another-prop: this is custom-value property
Note, if the initialize
method did a simple System.setProperty
call i.e.
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
System.setProperty("some-prop", "custom-value");
}
... then the another-prop
would be equal to this is some-value property
which is not what we generally want (and we lose the power of Spring config property resolution).
Upvotes: 10
Reputation: 4414
Try setting IgnoreUnresolvablePlaceholders to TRUE. I had a similar problem which I was able to resolve in this way. In my case, I had another placeholderconfigurer, which was working - but properties in the second one were not being resolved unless I set this property to TRUE.
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(Boolean.TRUE);
return propertySourcesPlaceholderConfigurer;
}
Upvotes: 3
Reputation: 7230
Yet another possibility (after lots of experimentation, it's what worked for me) would be to declare your PropertySource
inside a ApplicationContextInitializer
and then inject that one in your SpringBootServletInitializer
:
public class MyPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertyInitializer.class);
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
MyPropertySource ps = new MyPropertySource();
applicationContext.getEnvironment().getPropertySources().addFirst(ps);
}
}
public class MyInitializer extends SpringBootServletInitializer{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return super.configure(builder.initializers(new MyPropertyInitializer()));
}
}
Upvotes: 1
Reputation: 1
If you implement PropertySourceFactory as such:
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
public class CustomPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) {
...
}
}
You can use the following property source:
@PropertySource(name="custom-prop-source", value="", factory=CustomPropertySourceFactory.class)
Kind of hack-ish, but it works.
Upvotes: 0
Reputation: 2126
You can perhaps add your propertySource straight into environment once it is initialized.
EDIT: As this is done AFTER the class is processed, you cannot expect the @Value
annotations to pick up anything from this particular PropertySource in the same @Configuration
class - or any other that is loaded before.
@Configuration
public class YourPropertyConfigClass{
@Value("${fromCustomSource}")
String prop; // failing - property source not yet existing
@Autowired
ConfigurableEnvironment env;
@PostConstruct
public void init() throws Exception {
env.getPropertySources().addFirst(
new MyCustomPropertySource("my custom property source"));
}
}
@Configuration
@DependsOn("YourPropertyConfigClass")
public class PropertyUser {
@Value("${fromCustomSource}")
String prop; // not failing
}
You could move the @PostConstruct
to a separate @Configuration
class and mark other classes using those properties @DependOn("YourPropertyConfigClass")
(this works - but perhaps there is a better way to force configuration order?).
Anyway - it is only worth it, if MyCustomPropertySource
cannot be simply added using @PropertySource("file.properties")
annotation - which would solve the problem for simple property files.
Upvotes: 0