Spring Environment backed by Typesafe Config
I tried all of the above and failed. One particular problem I had was initilization order of the beans. We for example needed flyway support to pick up some overriden properties which come from a typesafe config and also the same for other properties.
As suggested in one of the comments from m-deinum for us the following solutions works, also relying on the input from the other answers. By using an ApplicationContextInitializer
when loading the main App we make sure that the props are loaded at the start of the App and merged into the "env" correctly:
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Import;
@SpringBootConfiguration
@Import({MyAppConfiguration.class})
public class MyApp {
public static void main(String[] args) {
new SpringApplicationBuilder(MyApp.class)
.initializers(new MyAppContextInitializer())
.run(args);
}
}
The ContextInitializer
looks like this:
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
public class MyAppContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext ac) {
PropertiesLoader loader = new PropertiesLoader(ac.getEnvironment());
loader.addConfigToEnv();
}
}
The PropertiesLoader
works like this to load the properties from the config and stuff it into the environment:
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
class PropertiesLoader {
private ConfigurableEnvironment env;
public PropertiesLoader(ConfigurableEnvironment env) {
this.env = env;
}
public void addConfigToEnv() {
MutablePropertySources sources = env.getPropertySources();
Config finalConfig = ConfigFactory.load().resolve();
// you can also do other stuff like: ConfigFactory.parseFile(), use Config.withFallback to merge configs, etc.
TypesafeConfigPropertySource source = new TypesafeConfigPropertySource("typeSafe", finalConfig);
sources.addFirst(source);
}
}
And we also need the TypesafeConfigPropertySource
which works for the typesafe config:
import com.typesafe.config.Config;
import org.springframework.core.env.PropertySource;
public class TypesafeConfigPropertySource extends PropertySource<Config> {
public TypesafeConfigPropertySource(String name, Config source) {
super(name, source);
}
@Override
public Object getProperty(String path) {
if (path.contains("["))
return null;
if (path.contains(":"))
return null;
if (source.hasPath(path)) {
return source.getAnyRef(path);
}
return null;
}
}
I think I came up with a slightly more idiomatic way than manually adding the PropertySource
to the property sources. Creating a PropertySourceFactory
and referencing that with @PropertySource
First, we have a TypesafeConfigPropertySource
almost identical to what you have:
public class TypesafeConfigPropertySource extends PropertySource<Config> {
public TypesafeConfigPropertySource(String name, Config source) {
super(name, source);
}
@Override
public Object getProperty(String path) {
if (source.hasPath(path)) {
return source.getAnyRef(path);
}
return null;
}
}
Next, we create a PropertySource factory that returns that property source
public class TypesafePropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Config config = ConfigFactory.load(resource.getResource().getFilename()).resolve();
String safeName = name == null ? "typeSafe" : name;
return new TypesafeConfigPropertySource(safeName, config);
}
}
And finally, in our Configuration file, we can just reference the property source like any other PropertySource
instead of having to add the PropertySource ourselves:
@Configuration
@PropertySource(factory=TypesafePropertySourceFactory.class, value="someconfig.conf")
public class PropertyLoader {
// Nothing needed here
}
You create a PropertySource class as follows, it is similar to yours with the difference that you have to return the value or null and not let the lib throw a missing exception
public class TypesafeConfigPropertySource extends PropertySource<Config> {
private static final Logger LOG = getLogger(TypesafeConfigPropertySource.class);
public TypesafeConfigPropertySource(String name, Config source) {
super(name, source);
}
@Override
public Object getProperty(String name) {
try {
return source.getAnyRef(name);
} catch (ConfigException.Missing missing) {
LOG.trace("Property requested [{}] is not set", name);
return null;
}
}
}
Second step is to define a bean as follows
@Bean
public TypesafeConfigPropertySource provideTypesafeConfigPropertySource(
ConfigurableEnvironment env) {
Config conf = ConfigFactory.load().resolve();
TypesafeConfigPropertySource source =
new TypesafeConfigPropertySource("typeSafe", conf);
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(source); // Choose if you want it first or last
return source;
}
In cases where you want to autowire properties to other beans you need to use the
annotation @DependsOn
to the propertysource bean in order to ensure it is first loaded
Hope it helps
Laplie Anderson answer with some small improvements:
- throw exception if resource not found
- ignore path that contains
[
and:
characters
TypesafePropertySourceFactory.java
import java.io.IOException;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigResolveOptions;
public class TypesafePropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource)
throws IOException {
Config config = ConfigFactory
.load(resource.getResource().getFilename(),
ConfigParseOptions.defaults().setAllowMissing(false),
ConfigResolveOptions.noSystem()).resolve();
String safeName = name == null ? "typeSafe" : name;
return new TypesafeConfigPropertySource(safeName, config);
}
}
TypesafeConfigPropertySource.java
import org.springframework.core.env.PropertySource;
import com.typesafe.config.Config;
public class TypesafeConfigPropertySource extends PropertySource<Config> {
public TypesafeConfigPropertySource(String name, Config source) {
super(name, source);
}
@Override
public Object getProperty(String path) {
if (path.contains("["))
return null;
if (path.contains(":"))
return null;
if (source.hasPath(path)) {
return source.getAnyRef(path);
}
return null;
}
}