Environment环境加载
Environment就是指Spring应用运行的环境信息,例如从系统变量、命令行、配置文件application.yml等文件中的信息加载之后都会保存到Environment中。
Environment的初始化
org.springframework.boot.SpringApplication#run(java.lang.String...)
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
准备环境的过程中,主要就是创建和配置Spring容器的环境信息:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//按照应用的类型创建标准的环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置环境的PropertySource,设置profiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//事件驱动加载环境配置
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
1. 创建一个标准的环境
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
//若是Servlet应用则创建Servlet环境
return new StandardServletEnvironment();
case REACTIVE:
//若是reactive应用则创建Servlet环境
return new StandardReactiveWebEnvironment();
default:
//否则创建标准环境
return new StandardEnvironment();
}
}
获取webApplicationType: org.springframework.boot.WebApplicationType#deduceFromClasspath
static WebApplicationType deduceFromClasspath() {
//org.springframework.web.reactive.DispatcherHandler类存在,
//且org.springframework.web.servlet.DispatcherServlet类不存在
//且org.glassfish.jersey.servlet.ServletContainer类不存在,则为REACTIVE应用
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
//javax.servlet.Servlet类
//或org.springframework.web.context.ConfigurableWebApplicationContext类不存在则为普通的应用
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
//否则为Servlet应用
return WebApplicationType.SERVLET;
}
这里以Servlet应用为例,看下环境的类结构图:
StandardServletEnvironment初始化
由于AbstractEnvironment类初始化调用了customizePropertySource方法,而customizePropertySource在StandardServletEnvironment和StandardEnvironment都有重写,所以会优先执行他们的方法:
org.springframework.web.context.support.StandardServletEnvironment#customizePropertySources
protected void customizePropertySources(MutablePropertySources propertySources) {
//将Servlet的配置封装成一个标准的属性源添加到动态可变的属性源列表中
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
//将Servlet的上下文封装成一个标准的属性源添加到动态可变的属性源列表中
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
//添加JNDI的属性源
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
org.springframework.core.env.StandardEnvironment#customizePropertySources
protected void customizePropertySources(MutablePropertySources propertySources) {
将系统属性源System.getProperties()添加到动态可变的属性源列表中
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
//将系统环境属性System.getenv()添加到动态可变的属性源列表中
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
2.配置Environment和配置profiles
org.springframework.boot.SpringApplication#configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
//添加类型转化的服务
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
//配置Environment中的propertysources
configurePropertySources(environment, args);
//配置profiles
configureProfiles(environment, args);
}
org.springframework.boot.SpringApplication#configurePropertySources
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
//若默认配置不为空,则将默认配置添加到MutablePropertySources,这个defaultProperties可以在SpringApplication中设置
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
//若设置了命令行属性,则设置成SimpleCommandLinePropertySource添加到MutablePropertySources的最前面
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
org.springframework.boot.SpringApplication#configureProfiles
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
//若设置了activeProfiles则加入到环境中
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
3.发送环境准备的事件
listeners.environmentPrepared(environment)
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
当所有实现了SpringApplicationRunListener的监听器,都会执行environmentPrepared的方法,在spring.factories配置了EventPublishingRunListener类:
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared
发送了一个ApplicationEnvironmentPreparedEvent事件
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
而ConfigFileApplicationListener这个监听器正是来监听并处理ApplicationEnvironmentPreparedEvent事件的。
org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEvent
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
接着处理环境准备就绪的事件,配置加载过程
org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
ConfigFileApplicationListener同样实现了EnvironmentPostProcessor接口,所以最后达到postProcessEnvironment的方法
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
//添加一个RandomValuePropertySource到Environment的MutablePropertySources中
RandomValuePropertySource.addToEnvironment(environment);
//加载spring boot中的配置信息,比如application.yml或者application.properties
new Loader(environment, resourceLoader).load();
}
new Loader(environment, resourceLoader).load()
这里是调用了FilteredPropertySource的apply方法,判断是否有设置defaultProperties,如果有则将默认配置设置一个FilteredPropertySource,然后再调用最后一个参数定义的匿名类(lambda表达式)
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
匿名类主要逻辑:
调用initializeProfiles初始化默认的Profile,没有设置的话就用默认,初始化之后保存到 private Deque<Profile> profiles; 中,它是一个LIFO队列。 因为 profiles 采用了 LIFO 队列,后进先出。所以会先加载profile为null的配置文件 ,也就是匹配 application.properties、application.yml 。
如果profiles不为空,则循环遍历每一个profiles,调用 load方法进行加载。
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#initializeProfiles
该方法的作用是加载存在已经激活的 profiles,有自己设置的则加载自己设置的profiles,没有的话则使用默认的profiles
private void initializeProfiles() {
// The default profile for these purposes is represented as null. We add it
// first so that it is processed first and has lowest priority.
this.profiles.add(null);
Binder binder = Binder.get(this.environment);
//获取spring.profiles.active的Profiles
Set<Profile> activatedViaProperty = getProfiles(binder, ACTIVE_PROFILES_PROPERTY);
//获取spring.profiles.incloud的Profiles
Set<Profile> includedViaProperty = getProfiles(binder, INCLUDE_PROFILES_PROPERTY);
//获取其他的Profiles
List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
this.profiles.addAll(otherActiveProfiles);
// Any pre-existing active profiles set via property sources (e.g.
// System properties) take precedence over those added in config files.
this.profiles.addAll(includedViaProperty);
addActiveProfiles(activatedViaProperty);
if (this.profiles.size() == 1) { // only has null profile
//如果没有设置司机的profiles,则使用默认的profiles
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
接下来就是具体加载每个Profile
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load
搜索约定配置文件的位置,循环每个位置,获取每个位置下的文件名称再进行加载
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
//按照每个名称进行加载
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load(java.lang.String, java.lang.String, org.springframework.boot.context.config.ConfigFileApplicationListener.Profile, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentFilterFactory, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentConsumer)
按照文件名称的后缀,获取具体的资源加载器,进行具体文件的加载过程
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
throw new IllegalStateException("File extension of config file location '" + location
+ "' is not known to any PropertySourceLoader. If the location is meant to reference "
+ "a directory, it must end in '/'");
}
Set<String> processed = new HashSet<>();
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
资源加载器目前自带的有两种,一种是properties文件加载器,一种是yaml/yml文件加载器:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
至此,springBoot中的资源文件加载完毕,解析顺序从上到下。
Config Server获取配置
Spring Cloud Config Server提供了EnvironmentController,这样通过在浏览器访问即可从git中获取配置信息
EnvironmentController
@RequestMapping(path = "/{name}/{profiles:.*[^-].*}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Environment defaultLabel(@PathVariable String name,
@PathVariable String profiles) {
return getEnvironment(name, profiles, null, false);
}
@RequestMapping(path = "/{name}/{profiles:.*[^-].*}",
produces = EnvironmentMediaType.V2_JSON)
public Environment defaultLabelIncludeOrigin(@PathVariable String name,
@PathVariable String profiles) {
return getEnvironment(name, profiles, null, true);
}
@RequestMapping(path = "/{name}/{profiles}/{label:.*}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
return getEnvironment(name, profiles, label, false);
}
@RequestMapping(path = "/{name}/{profiles}/{label:.*}",
produces = EnvironmentMediaType.V2_JSON)
public Environment labelledIncludeOrigin(@PathVariable String name,
@PathVariable String profiles, @PathVariable String label) {
return getEnvironment(name, profiles, label, true);
}
我们可以按照application、profiles、label三要素的配置方式来获取远程地址的配置信息,调用的都是getEnvironment方法:
public Environment getEnvironment(String name, String profiles, String label,
boolean includeOrigin) {
name = normalize(name);
label = normalize(label);
Environment environment = this.repository.findOne(name, profiles, label,
includeOrigin);
if (!this.acceptEmpty
&& (environment == null || environment.getPropertySources().isEmpty())) {
throw new EnvironmentNotFoundException("Profile Not found");
}
return environment;
}
org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository#findOne
以git远程地址为例,调用findOne获取Environment,其实git地址可以配置多个地址,从每个地址中拿到符合application、profiles、label三要素的配置信息
public Environment findOne(String application, String profile, String label,
boolean includeOrigin) {
for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
if (repository.matches(application, profile, label)) {
for (JGitEnvironmentRepository candidate : getRepositories(repository,
application, profile, label)) {
try {
if (label == null) {
label = candidate.getDefaultLabel();
}
Environment source = candidate.findOne(application, profile,
label, includeOrigin);
if (source != null) {
return source;
}
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"Cannot load configuration from " + candidate.getUri()
+ ", cause: (" + e.getClass().getSimpleName()
+ ") " + e.getMessage(),
e);
}
continue;
}
}
}
}
JGitEnvironmentRepository candidate = getRepository(this, application, profile,
label);
if (label == null) {
label = candidate.getDefaultLabel();
}
if (candidate == this) {
return super.findOne(application, profile, label, includeOrigin);
}
return candidate.findOne(application, profile, label, includeOrigin);
}
org.springframework.cloud.config.server.environment.AbstractScmEnvironmentRepository#findOne(java.lang.String, java.lang.String, java.lang.String, boolean)
调用抽象类的findOne方法,主要有两个核心逻辑
调用getLocations从GIT远程仓库同步到本地
使用 NativeEnvironmentRepository 委托来读取本地文件内容
public synchronized Environment findOne(String application, String profile,
String label, boolean includeOrigin) {
NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(
getEnvironment(), new NativeEnvironmentProperties());
Locations locations = getLocations(application, profile, label);
delegate.setSearchLocations(locations.getLocations());
Environment result = delegate.findOne(application, profile, "", includeOrigin);
result.setVersion(locations.getVersion());
result.setLabel(label);
return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
getUri());
}
至此,服务端获取远程git地址的配置信息完成。
ConfigClient获取配置
在spring boot项目启动时,有一个prepareContext的方法,它会回调所有实现了ApplicationContextInitializer 的实例,来做一些初始化工作。
org.springframework.boot.SpringApplication#prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration#initialize
获取所有PropertySourceLocator, 遍历每个PropertySourceLocator,调用locateCollection获取属性源列表加入到Spring的MutablePropertySources 中
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//遍历每个PropertySourceLocator
for (PropertySourceLocator locator : this.propertySourceLocators) {
Collection<PropertySource<?>> source = locator.locateCollection(environment);
if (source == null || source.size() == 0) {
continue;
}
List<PropertySource<?>> sourceList = new ArrayList<>();
for (PropertySource<?> p : source) {
//加入到sourceList中
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
sourceList.add(new BootstrapPropertySource<>(enumerable));
}
else {
sourceList.add(new SimpleBootstrapPropertySource(p));
}
}
logger.info("Located property source: " + sourceList);
composite.addAll(sourceList);
empty = false;
}
//若获取的属性源不为空的情况下,加入到MutablePropertySources中
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
for (PropertySource<?> p : environment.getPropertySources()) {
if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(p.getName());
}
}
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
org.springframework.cloud.bootstrap.config.PropertySourceLocator#locateCollection(org.springframework.core.env.Environment)
PropertySource<?> locate(Environment environment);
default Collection<PropertySource<?>> locateCollection(Environment environment) {
return locateCollection(this, environment);
}
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
Environment environment) {
PropertySource<?> propertySource = locator.locate(environment);
if (propertySource == null) {
return Collections.emptyList();
}
if (CompositePropertySource.class.isInstance(propertySource)) {
Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource)
.getPropertySources();
List<PropertySource<?>> filteredSources = new ArrayList<>();
for (PropertySource<?> p : sources) {
if (p != null) {
filteredSources.add(p);
}
}
return filteredSources;
}
else {
return Arrays.asList(propertySource);
}
}
locateCollection获取属性源时,调用了locate方法,而locate方法是抽象方法,由其子类实现,Config Client提供了一个ConfigServicePropertySourceLocator:
org.springframework.cloud.config.client.ConfigServicePropertySourceLocator#locate
通过RestTemplate调用一个Config Server服务获得远程配置信息,然后将信息包装成OriginTrackedMapPropertySource,保存到 CompositePropertySource 中
@Retryable(interceptor = "configServerRetryInterceptor")
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
ConfigClientProperties properties = this.defaultProperties.override(environment);
CompositePropertySource composite = new OriginTrackedCompositePropertySource(
"configService");
RestTemplate restTemplate = this.restTemplate == null
? getSecureRestTemplate(properties) : this.restTemplate;
Exception error = null;
String errorBody = null;
try {
String[] labels = new String[] { "" };
if (StringUtils.hasText(properties.getLabel())) {
labels = StringUtils
.commaDelimitedListToStringArray(properties.getLabel());
}
String state = ConfigClientStateHolder.getState();
// Try all the labels until one works
for (String label : labels) {
Environment result = getRemoteEnvironment(restTemplate, properties,
label.trim(), state);
if (result != null) {
log(result);
// result.getPropertySources() can be null if using xml
if (result.getPropertySources() != null) {
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = translateOrigins(source.getName(),
(Map<String, Object>) source.getSource());
composite.addPropertySource(
new OriginTrackedMapPropertySource(source.getName(),
map));
}
}
if (StringUtils.hasText(result.getState())
|| StringUtils.hasText(result.getVersion())) {
HashMap<String, Object> map = new HashMap<>();
putValue(map, "config.client.state", result.getState());
putValue(map, "config.client.version", result.getVersion());
composite.addFirstPropertySource(
new MapPropertySource("configClient", map));
}
return composite;
}
}
errorBody = String.format("None of labels %s found", Arrays.toString(labels));
}
catch (HttpServerErrorException e) {
error = e;
if (MediaType.APPLICATION_JSON
.includes(e.getResponseHeaders().getContentType())) {
errorBody = e.getResponseBodyAsString();
}
}
catch (Exception e) {
error = e;
}
if (properties.isFailFast()) {
throw new IllegalStateException(
"Could not locate PropertySource and the fail fast property is set, failing"
+ (errorBody == null ? "" : ": " + errorBody),
error);
}
logger.warn("Could not locate PropertySource: "
+ (error != null ? error.getMessage() : errorBody));
return null;
}