# Apollo自动更新实现原理
版本:apollo-client 1.4.0
# 监听
# RemoteConfigRepository&LocalFileConfigRepository
这里只对监听器RemoteConfigRepository进行详解,LocalFileConfigRepository比较简单,可自行查看
# 初始化
一个命名空间对应了一个Config类,在初始化命名空间的Config类的同时,就会初始化对应的监听器RemoteConfigRepository
- DefaultConfigManager.getConfig
- ApolloInjector.getInstance(ConfigFactoryManager.class).getFactory(namespace).create(namespace)
- DefaultConfigFactory.create(String namespace)
DefaultConfigFactory.createLocalConfigRepository(namespace)
DefaultConfigFactory.createRemoteConfigRepository(namespace)
// 重点1: 一个命名空间被初始化的时候,同时远程监听器就被创建出来了,对应的config为DefaultConfig
- 调用RemoteConfigRepository构造方法
// 重点2: 一个命名空间被初始化的时候, 本地文件的监听器也被创建出来,用于更新本地缓存
- return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace))
new DefaultConfig(String namespace, ConfigRepository configRepository)
// 加载配置文件
DefaultConfig.loadFromResource(m_namespace);
- 从配置文件META-INF/config/{namespace}.properties加载对应命名空间的配置
DefaultConfig.initialize();
// 将读取到的配置存储到PropertySource中
- DefaultConfig.updateConfig(m_configRepository.getConfig(), m_configRepository.getSourceType());
// 注册监听器
- configRepository.addChangeListener(this);
- DefaultConfigFactory.create(String namespace)
- ApolloInjector.getInstance(ConfigFactoryManager.class).getFactory(namespace).create(namespace)
# RemoteConfigRepository做了哪些事
- 初始化配置
- 初始化读取远程配置
- 开启定时任务,定期读取远程配置
- 长轮询,判定远程配置是否有更新
- 刷新文件缓存
- 刷新内存中的缓存
# 代码分析
省略了部分代码,只保留关键部分
- 初始化
public RemoteConfigRepository(String namespace) {
// 初始化一系列配置开始
m_namespace = namespace;
m_configCache = new AtomicReference<>();
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);
m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
m_longPollServiceDto = new AtomicReference<>();
m_remoteMessages = new AtomicReference<>();
m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
m_configNeedForceRefresh = new AtomicBoolean(true);
m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
m_configUtil.getOnErrorRetryInterval() * 8);
gson = new Gson();
// 初始化一系列配置结束
// 程序启动时,首次读取apollo远程配置
this.trySync();
// 开始定时任务,定期获取远程的配置,判定是否有更新
this.schedulePeriodicRefresh();
// 长轮询,监听远程配置是否有更新
this.scheduleLongPollingRefresh();
}
- 定时任务
每5分钟获取一次远程配置,刷新缓存
private void schedulePeriodicRefresh() {
logger.debug("Schedule periodic refresh with interval: {} {}",
m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit());
m_executorService.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", m_namespace));
logger.debug("refresh config for namespace: {}", m_namespace);
trySync();
Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
}
},5, 5, TimeUnit.MINUTES);
}
- 长轮询
- 提交长轮询
private void scheduleLongPollingRefresh() {
remoteConfigLongPollService.submit(m_namespace, this);
}
public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) {
m_longPollServiceDto.set(longPollNotifiedServiceDto);
m_remoteMessages.set(remoteMessages);
m_executorService.submit(new Runnable() {
@Override
public void run() {
m_configNeedForceRefresh.set(true);
trySync();
}
});
}
- 开始长轮询
private void startLongPolling() {
if (!m_longPollStarted.compareAndSet(false, true)) {
//already started
return;
}
try {
// ...
m_longPollingService.submit(new Runnable() {
@Override
public void run() {
if (longPollingInitialDelayInMills > 0) {
try {
logger.debug("Long polling will start in {} ms.", longPollingInitialDelayInMills);
// 等待2s
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
//ignore
}
}
doLongPollingRefresh(appId, cluster, dataCenter);
}
});
} catch (Throwable ex) {
// ...
}
}
- 执行长轮询,读取远程的数据,读取成功之后更新本地的notification和remoteNotification
private void doLongPollingRefresh(String appId, String cluster, String dataCenter) {
final Random random = new Random();
ServiceDTO lastServiceDto = null;
while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
// 由限流器判断判断调用次数,每秒调用2次
// m_longPollRateLimiter = RateLimiter.create(2);
if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
//wait at most 5 seconds
// ....
}
// ...
try {
// ....
// 读取apollo配置
final HttpResponse<List<ApolloConfigNotification>> response =
m_httpUtil.doGet(request, m_responseType);
logger.debug("Long polling response: {}, url: {}", response.getStatusCode(), url);
if (response.getStatusCode() == 200 && response.getBody() != null) {
// 接口响应成功,通知更新
updateNotifications(response.getBody());
updateRemoteNotifications(response.getBody());
transaction.addData("Result", response.getBody().toString());
notify(lastServiceDto, response.getBody());
}
//try to load balance
// .. 事务提交
} catch (Throwable ex) {
/// ....
} finally {
/// ....
}
}
}
- 更新本地的notification,记录到ConcurrentMap中
private final ConcurrentMap<String, Long> m_notifications = Maps.newConcurrentMap();
private void updateNotifications(List<ApolloConfigNotification> deltaNotifications) {
for (ApolloConfigNotification notification : deltaNotifications) {
if (Strings.isNullOrEmpty(notification.getNamespaceName())) {
continue;
}
String namespaceName = notification.getNamespaceName();
if (m_notifications.containsKey(namespaceName)) {
m_notifications.put(namespaceName, notification.getNotificationId());
}
//since .properties are filtered out by default, so we need to check if there is notification with .properties suffix
String namespaceNameWithPropertiesSuffix =
String.format("%s.%s", namespaceName, ConfigFileFormat.Properties.getValue());
if (m_notifications.containsKey(namespaceNameWithPropertiesSuffix)) {
m_notifications.put(namespaceNameWithPropertiesSuffix, notification.getNotificationId());
}
}
}
- 更新远程的notification, 将结果记录到ConcurrentMap中
private final Map<String, ApolloNotificationMessages> m_remoteNotificationMessages = Maps.newConcurrentMap();
private void updateRemoteNotifications(List<ApolloConfigNotification> deltaNotifications) {
for (ApolloConfigNotification notification : deltaNotifications) {
if (Strings.isNullOrEmpty(notification.getNamespaceName())) {
continue;
}
if (notification.getMessages() == null || notification.getMessages().isEmpty()) {
continue;
}
ApolloNotificationMessages localRemoteMessages =
m_remoteNotificationMessages.get(notification.getNamespaceName());
if (localRemoteMessages == null) {
localRemoteMessages = new ApolloNotificationMessages();
m_remoteNotificationMessages.put(notification.getNamespaceName(), localRemoteMessages);
}
localRemoteMessages.mergeFrom(notification.getMessages());
}
}
- 通知更新
private void notify(ServiceDTO lastServiceDto, List<ApolloConfigNotification> notifications) {
if (notifications == null || notifications.isEmpty()) {
return;
}
for (ApolloConfigNotification notification : notifications) {
String namespaceName = notification.getNamespaceName();
//create a new list to avoid ConcurrentModificationException
List<RemoteConfigRepository> toBeNotified =
Lists.newArrayList(m_longPollNamespaces.get(namespaceName));
ApolloNotificationMessages originalMessages = m_remoteNotificationMessages.get(namespaceName);
ApolloNotificationMessages remoteMessages = originalMessages == null ? null : originalMessages.clone();
//since .properties are filtered out by default, so we need to check if there is any listener for it
toBeNotified.addAll(m_longPollNamespaces
.get(String.format("%s.%s", namespaceName, ConfigFileFormat.Properties.getValue())));
// 遍历需要通知的配置
for (RemoteConfigRepository remoteConfigRepository : toBeNotified) {
try {
remoteConfigRepository.onLongPollNotified(lastServiceDto, remoteMessages);
} catch (Throwable ex) {
Tracer.logError(ex);
}
}
}
}
- 执行通知
到这里我们就可以看到,这里和定时任务走到相同的逻辑了,trySync,下面我们看看trySync的逻辑
public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) {
m_longPollServiceDto.set(longPollNotifiedServiceDto);
m_remoteMessages.set(remoteMessages);
m_executorService.submit(new Runnable() {
@Override
public void run() {
m_configNeedForceRefresh.set(true);
trySync();
}
});
}
- trySync【AbstractConfigRepository】
public abstract class AbstractConfigRepository implements ConfigRepository {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigRepository.class);
private List<RepositoryChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
protected boolean trySync() {
try {
sync();
return true;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
logger
.warn("Sync config failed, will retry. Repository {}, reason: {}", this.getClass(), ExceptionUtil
.getDetailMessage(ex));
}
return false;
}
protected abstract void sync();
@Override
public void addChangeListener(RepositoryChangeListener listener) {
if (!m_listeners.contains(listener)) {
m_listeners.add(listener);
}
}
@Override
public void removeChangeListener(RepositoryChangeListener listener) {
m_listeners.remove(listener);
}
protected void fireRepositoryChange(String namespace, Properties newProperties) {
for (RepositoryChangeListener listener : m_listeners) {
try {
listener.onRepositoryChange(namespace, newProperties);
} catch (Throwable ex) {
Tracer.logError(ex);
logger.error("Failed to invoke repository change listener {}", listener.getClass(), ex);
}
}
}
}
- RemoteConfigRepository.sync
@Override
protected synchronized void sync() {
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");
try {
// 从缓存中获取配置
ApolloConfig previous = m_configCache.get();
// 调用一次接口,获取当前命名空间下,apollo最新的配置
ApolloConfig current = loadApolloConfig();
//reference equals means HTTP 304
// 如果配置有更新,调用fireRepositoryChange进行更新
if (previous != current) {
logger.debug("Remote Config refreshed!");
m_configCache.set(current);
this.fireRepositoryChange(m_namespace, this.getConfig());
}
if (current != null) {
Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),
current.getReleaseKey());
}
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}
- DefaultConfig.onRepositoryChange更新本地缓存,同时调用changeListener的onChange方法进行bean字段更新
@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
if (newProperties.equals(m_configProperties.get())) {
return;
}
ConfigSourceType sourceType = m_configRepository.getSourceType();
Properties newConfigProperties = new Properties();
newConfigProperties.putAll(newProperties);
// 将命名空间中每个修改,新增,删除的配置一对一的封装成ConfigChange对象,并构造map,map的key为propertyName
Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType);
//check double checked result
if (actualChanges.isEmpty()) {
return;
}
this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));
Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
}
- DefaultConfig.updateAndCalcConfigChanges
private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties newConfigProperties,
ConfigSourceType sourceType) {
// 将配置项转换成ConfigList,并设置变更类型,新增or删除or更新
List<ConfigChange> configChanges =
calcPropertyChanges(m_namespace, m_configProperties.get(), newConfigProperties);
ImmutableMap.Builder<String, ConfigChange> actualChanges =
new ImmutableMap.Builder<>();
// 双重校验,避免一个命名空间有多个本地备份
//1. 使用getProperty获取原始值
for (ConfigChange change : configChanges) {
change.setOldValue(this.getProperty(change.getPropertyName(), change.getOldValue()));
}
//2. 更新PropertySource !!!划重点,值在这里进行更新
updateConfig(newConfigProperties, sourceType);
clearConfigCache();
//3. use getProperty to update configChange's new value and calc the final changes
for (ConfigChange change : configChanges) {
change.setNewValue(this.getProperty(change.getPropertyName(), change.getNewValue()));
switch (change.getChangeType()) {
case ADDED:
// .....
actualChanges.put(change.getPropertyName(), change);
break;
case MODIFIED:
if (!Objects.equals(change.getOldValue(), change.getNewValue())) {
actualChanges.put(change.getPropertyName(), change);
}
break;
case DELETED:
// .....
actualChanges.put(change.getPropertyName(), change);
break;
default:
//do nothing
break;
}
}
return actualChanges.build();
}
- DefaultConfig.updateConfig, 替换本地缓存,用于后续读取
private final AtomicReference<Properties> m_configProperties;
private void updateConfig(Properties newConfigProperties, ConfigSourceType sourceType) {
m_configProperties.set(newConfigProperties);
m_sourceType = sourceType;
}
# 更新bean
# AutoUpdateConfigChangeListener
- 初始化
- 判断是否允许spring property自动更新,如果不允许,则不初始化AutoUpdateConfigChangeListener
- 初始化AutoUpdateConfigChangeListener
- 给所有PropertySource的listeners添加AutoUpdateConfigChangeListener,响应变更
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
// ...
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
/// ....
initializeAutoUpdatePropertiesFeature(beanFactory);
}
private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {
// 如果关闭Spring property的自动更新,不添加监听器,响应更新
// 默认开启,通过设置参数apollo.autoUpdateInjectedSpringProperties=false关闭自动更新
if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() ||
!AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {
return;
}
// 初始化AutoUpdateConfigChangeListener
AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(
environment, beanFactory);
// 读取所有的PropertySource, 添加AutoUpdateConfigChangeListener,响应变更
List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
for (ConfigPropertySource configPropertySource : configPropertySources) {
configPropertySource.addChangeListener(autoUpdateConfigChangeListener);
}
}
- 响应变更
public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
// .....
/**
* 响应变更
* @param changeEvent bean 对象,直接调用,并非事件响应模式
*/
@Override
public void onChange(ConfigChangeEvent changeEvent) {
Set<String> keys = changeEvent.changedKeys();
if (CollectionUtils.isEmpty(keys)) {
return;
}
for (String key : keys) {
// 1. check whether the changed key is relevant
Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
if (targetValues == null || targetValues.isEmpty()) {
continue;
}
// 2. 遍历更新bean对象的值
for (SpringValue val : targetValues) {
updateSpringValue(val);
}
}
}
private void updateSpringValue(SpringValue springValue) {
try {
// 从propertySource中读取新值
Object value = resolvePropertyValue(springValue);
// 利用反射机制,执行更新
springValue.update(value);
logger.info("Auto update apollo changed value successfully, new value: {}, {}", value,
springValue);
} catch (Throwable ex) {
logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
}
}
/**
* Logic transplanted from DefaultListableBeanFactory
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String, java.util.Set, org.springframework.beans.TypeConverter)
*/
private Object resolvePropertyValue(SpringValue springValue) {
// 通过placeholder读取
Object value = placeholderHelper
.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
// 将读取到到value转换为指定的类型
// 。。。。
return value;
}
// ....
}
- PlaceholderHelper.resolvePropertyValue
public class PlaceholderHelper {
/**
* Resolve placeholder property values, e.g.
* 将"${somePropertyValue}"替换成对应的值:"the actual property value"
*/
public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
// resolve string value
String strVal = beanFactory.resolveEmbeddedValue(placeholder);
/// ...
}
}
- AbstractBeanFactory
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
if (value == null) {
return null;
} else {
String result = value;
Iterator var3 = this.embeddedValueResolvers.iterator();
do {
if (!var3.hasNext()) {
return result;
}
StringValueResolver resolver = (StringValueResolver)var3.next();
result = resolver.resolveStringValue(result);
} while(result != null);
return null;
}
}
}
- debug到这里,会发现代码跟不下去了,先看看debug的内容
从图中我们可以看到,resolver指向是PropertySourcesPlaceholderConfigurer类,但可点进去的实现类,并没有这个类,接下来我们先来找找这个实现类
我们的思路是,首先去看apollo-client的starter是怎么加载的
- 找到apollo-client的spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration
- ApolloAutoConfiguration
@Configuration
@ConditionalOnProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED)
@ConditionalOnMissingBean(PropertySourcesProcessor.class)
public class ApolloAutoConfiguration {
@Bean
public ConfigPropertySourcesProcessor configPropertySourcesProcessor() {
return new ConfigPropertySourcesProcessor();
}
}
- ConfigPropertySourcesProcessor
在启动的时候,将PropertySourcesPlaceholderConfigurer注册为bean定义,并交由spring注册
public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
propertySourcesPlaceholderPropertyValues.put("order", 0);
// 我们需要找的PropertySourcesPlaceholderConfigurer
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
/// .....
processSpringValueDefinition(registry);
}
}
- PropertySourcesPlaceholderConfigurer.StringValueResolver
回到之前跟不下去的AbstractBeanFactory,我们在PropertySourcesPlaceholderConfigurer找到StringValueResolver方法
public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
protected boolean ignoreUnresolvablePlaceholders = false;
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
/// ...
this.processProperties(beanFactory, (ConfigurablePropertyResolver)(new PropertySourcesPropertyResolver(this.propertySources)));
this.appliedPropertySources = this.propertySources;
}
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
ConfigurablePropertyResolver propertyResolver) throws BeansException {
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);
StringValueResolver valueResolver = (strVal) -> {
String resolved = this.ignoreUnresolvablePlaceholders ? propertyResolver.resolvePlaceholders(strVal)
: propertyResolver.resolveRequiredPlaceholders(strVal);
if (this.trimValues) {
resolved = resolved.trim();
}
return resolved.equals(this.nullValue) ? null : resolved;
};
this.doProcessProperties(beanFactoryToProcess, valueResolver);
}
}
在这里打个断点,同时更新apollo的值,发现代码果然走到了这里
从代码可以看到使用propertyResolver.resolveRequiredPlaceholders(strVal)处理参数值读取,而propertyResolver来自于上一次所创建的PropertySourcesPropertyResolver
- 找到PropertySourcesPropertyResolver继承自父类AbstractPropertyResolver的resolveRequiredPlaceholders方法
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
public String resolveRequiredPlaceholders(String text) {
if (this.strictHelper == null) {
this.strictHelper = this.createPlaceholderHelper(false);
}
return this.doResolvePlaceholders(text, this.strictHelper);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
}
- PropertySourcesPropertyResolver.getPropertyAsRawString
helper.replacePlaceholders可以理解为传了一个方法进去,类似于使用el表达式的方式进行调用, 所以真正取值的地方在getPropertyAsRawString中。 PropertySourcesPropertyResolver中的propertySources,在创建的时候传入值: 系统初始化时读取到的所有的propertySource。
这里的核心就是,遍历所有的PropertySource, key匹配即为取到对应值。这里存在一个问题,如果一个key在多个命名空间中被设置,那么会被后加载的命名空间进行覆盖。
当然,这种使用方式也是不对的,应该确保在使用多个命名空间的时候,一个key只在一个命名空间中被定义。
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
// ...
@Nullable
private final PropertySources propertySources;
public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
this.propertySources = propertySources;
}
@Nullable
protected String getPropertyAsRawString(String key) {
return (String)this.getProperty(key, String.class, false);
}
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
// 遍历propertySources
Iterator var4 = this.propertySources.iterator();
while (var4.hasNext()) {
PropertySource<?> propertySource = (PropertySource)var4.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
}
// 从当前的propertySource中获取值,如果取到,就设置在value中
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = this.resolveNestedPlaceholders((String)value);
}
// 打印参数被找到的日志
this.logKeyFound(key, propertySource, value);
// 转换value到目标类型
return this.convertValueIfNecessary(value, targetValueType);
}
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
}