Spring Environment при поддержке Typesafe Config
Я хочу использовать в своем проекте безопасную конфигурацию (файлы конфигурации HOCON), что облегчает и упрощает настройку приложения. В настоящее время я использую обычный файл свойств Java (application.properties), который трудно обрабатывать в большом проекте.
Мой проект - Spring MVC (не проект с весенней загрузкой). Есть ли способ поддержать мою среду Spring (которую я внедряю в мои службы), чтобы она была поддержана типом безопасной конфигурации. Что не должно тормозить мою существующую среду @Value
аннотаций, @Autowired Environment
и т.п.
Как я могу сделать это с минимальными усилиями и изменениями в моем коде.
Это мое текущее решение: ищу какой-нибудь другой лучший способ
@Configuration
public class PropertyLoader{
private static Logger logger = LoggerFactory.getLogger(PropertyLoader.class);
@Bean
@Autowired
public static PropertySourcesPlaceholderConfigurer properties(Environment env) {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
Config conf = ConfigFactory.load();
conf.resolve();
TypesafePropertySource propertySource = new TypesafePropertySource("hoconSource", conf);
ConfigurableEnvironment environment = (StandardEnvironment)env;
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addLast(propertySource);
pspc.setPropertySources(propertySources);
return pspc;
}
}
class TypesafePropertySource extends PropertySource<Config>{
public TypesafePropertySource(String name, Config source) {
super(name, source);
}
@Override
public Object getProperty(String name) {
return this.getSource().getAnyRef(name);
}
}
3 ответа
Я думаю, что придумал немного более идиоматический способ, чем ручное добавление PropertySource
к источникам собственности. Создание PropertySourceFactory
и ссылаясь на это с @PropertySource
Во-первых, у нас есть TypesafeConfigPropertySource
почти идентично тому, что у вас есть:
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;
}
}
Далее мы создаем фабрику PropertySource, которая возвращает источник этого свойства
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);
}
}
И, наконец, в нашем файле конфигурации мы можем просто ссылаться на источник свойства, как и любой другой PropertySource
вместо того, чтобы добавлять PropertySource самостоятельно:
@Configuration
@PropertySource(factory=TypesafePropertySourceFactory.class, value="someconfig.conf")
public class PropertyLoader {
// Nothing needed here
}
Вы создаете класс PropertySource следующим образом: он похож на ваш, с той разницей, что вы должны возвращать значение или значение NULL и не позволять библиотеке выдавать пропущенное исключение
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;
}
}
}
Второй шаг - определить бин следующим образом
@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;
}
В случаях, когда вы хотите автоматически связать свойства с другими компонентами, вам нужно использовать аннотацию @DependsOn
к компоненту ProperTysource для обеспечения его первой загрузки
Надеюсь, поможет
Ответ Лапли Андерсона с небольшими улучшениями:
- выбросить исключение, если ресурс не найден
- игнорировать путь, который содержит
[
а также:
персонажи
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;
}
}
Я попробовал все вышеперечисленное и потерпел неудачу. Одна конкретная проблема, с которой я столкнулся, заключалась в порядке инициализации bean-компонентов. Нам, например, нужна была поддержка пролетного пути, чтобы подобрать некоторые переопределенные свойства, которые поступают из конфигурации с безопасным типом, а также то же самое для других свойств.
Как было предложено в одном из комментариев от m-deinum для нас, следующие решения работают, также полагаясь на ввод из других ответов. ИспользуяApplicationContextInitializer
при загрузке основного приложения мы убеждаемся, что реквизиты загружаются в начале приложения и правильно сливаются с "env":
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);
}
}
В ContextInitializer
выглядит так:
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();
}
}
В PropertiesLoader
работает так, чтобы загрузить свойства из конфигурации и поместить их в среду:
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);
}
}
И еще нам нужен TypesafeConfigPropertySource
который работает для конфигурации typeafe:
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;
}
}