增加 apollo 读取本地 db,需要进一步优化~
parent
c4c614592a
commit
17cb37f577
@ -1,35 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 配置接口
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public interface Config {
|
||||
|
||||
/**
|
||||
* Return the property value with the given key, or {@code defaultValue} if the key doesn't exist.
|
||||
*
|
||||
* @param key the property name
|
||||
* @param defaultValue the default value when key is not found or any error occurred
|
||||
* @return the property value
|
||||
*/
|
||||
String getProperty(String key, String defaultValue);
|
||||
|
||||
/**
|
||||
* Return a set of the property names
|
||||
*
|
||||
* @return the property names
|
||||
*/
|
||||
Set<String> getPropertyNames();
|
||||
|
||||
/**
|
||||
* Add change listener to this config instance.
|
||||
*
|
||||
* @param listener the config change listener
|
||||
*/
|
||||
void addChangeListener(ConfigChangeListener listener);
|
||||
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox;
|
||||
|
||||
import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
|
||||
|
||||
/**
|
||||
* {@link Config} 变化监听器
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public interface ConfigChangeListener {
|
||||
|
||||
/**
|
||||
* Invoked when there is any config change for the namespace.
|
||||
*
|
||||
* @param changeEvent the event for this change
|
||||
*/
|
||||
void onChange(ConfigChangeEvent changeEvent);
|
||||
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox;
|
||||
|
||||
import cn.hutool.core.lang.Singleton;
|
||||
|
||||
public class ConfigService {
|
||||
|
||||
public static Config getConfig(String namespace) {
|
||||
return Singleton.get(DefaultConfig.class);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
public class DefaultConfig implements Config {
|
||||
|
||||
@Override
|
||||
public String getProperty(String key, String defaultValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPropertyNames() {
|
||||
return Collections.emptySet(); // TODO 等下实现
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChangeListener(ConfigChangeListener listener) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.enums;
|
||||
|
||||
/**
|
||||
* 属性变化类型枚举
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public enum PropertyChangeType {
|
||||
|
||||
ADDED, // 添加
|
||||
MODIFIED, // 修改
|
||||
DELETED // 删除
|
||||
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.internals;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* 配置 Repository 抽象类
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public abstract class AbstractConfigRepository implements ConfigRepository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigRepository.class);
|
||||
|
||||
/**
|
||||
* RepositoryChangeListener 数组
|
||||
*/
|
||||
private List<RepositoryChangeListener> m_listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* 尝试同步
|
||||
*
|
||||
* @return 是否同步成功
|
||||
*/
|
||||
protected boolean trySync() {
|
||||
try {
|
||||
// 同步
|
||||
sync();
|
||||
// 返回同步成功
|
||||
return true;
|
||||
} catch (Throwable ex) {
|
||||
// Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
|
||||
logger.warn("Sync config failed, will retry. Repository {}", getClass(), 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发监听器们
|
||||
*
|
||||
* @param namespace Namespace 名字
|
||||
* @param newProperties 配置
|
||||
*/
|
||||
protected void fireRepositoryChange(String namespace, Properties newProperties) {
|
||||
// 循环 RepositoryChangeListener 数组
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.internals;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public interface RepositoryChangeListener {
|
||||
|
||||
/**
|
||||
* Invoked when config repository changes.
|
||||
*
|
||||
* @param namespace the namespace of this repository change
|
||||
* @param newProperties the properties after change
|
||||
*/
|
||||
void onRepositoryChange(String namespace, Properties newProperties);
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.model;
|
||||
|
||||
|
||||
import cn.iocoder.dashboard.framework.apollox.enums.PropertyChangeType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Holds the information for a config change.
|
||||
* 配置每个属性变化的信息
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ConfigChange {
|
||||
|
||||
/**
|
||||
* 属性名
|
||||
*/
|
||||
private final String propertyName;
|
||||
/**
|
||||
* 老值
|
||||
*/
|
||||
private String oldValue;
|
||||
/**
|
||||
* 新值
|
||||
*/
|
||||
private String newValue;
|
||||
/**
|
||||
* 变化类型
|
||||
*/
|
||||
private PropertyChangeType changeType;
|
||||
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.annotation;
|
||||
|
||||
import cn.hutool.core.lang.Singleton;
|
||||
import cn.iocoder.dashboard.framework.apollox.Config;
|
||||
import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener;
|
||||
import cn.iocoder.dashboard.framework.apollox.DefaultConfig;
|
||||
import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Apollo Annotation Processor for Spring Application
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public class ApolloAnnotationProcessor extends ApolloProcessor {
|
||||
|
||||
@Override
|
||||
protected void processField(Object bean, String beanName, Field field) {
|
||||
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()), "Invalid type: %s for field: %s, should be Config", field.getType(), field);
|
||||
|
||||
// 创建 Config 对象
|
||||
Config config = Singleton.get(DefaultConfig.class);
|
||||
|
||||
// 设置 Config 对象,到对应的 Field
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
ReflectionUtils.setField(field, bean, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processMethod(final Object bean, String beanName, final Method method) {
|
||||
ApolloConfigChangeListener annotation = AnnotationUtils.findAnnotation(method, ApolloConfigChangeListener.class);
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
Preconditions.checkArgument(parameterTypes.length == 1, "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, method);
|
||||
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]), "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], method);
|
||||
|
||||
// 创建 ConfigChangeListener 监听器。该监听器会调用被注解的方法。
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
ConfigChangeListener configChangeListener = changeEvent -> {
|
||||
// 反射调用
|
||||
ReflectionUtils.invokeMethod(method, bean, changeEvent);
|
||||
};
|
||||
|
||||
// 向指定 Namespace 的 Config 对象们,注册该监听器
|
||||
Config config = Singleton.get(DefaultConfig.class);
|
||||
config.addChangeListener(configChangeListener);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Use this annotation to inject Apollo Config Instance.
|
||||
*
|
||||
* <p>Usage example:</p>
|
||||
* <pre class="code">
|
||||
* //Inject the config for "someNamespace"
|
||||
* @ApolloConfig("someNamespace")
|
||||
* private Config config;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Documented
|
||||
public @interface ApolloConfig {
|
||||
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Use this annotation to register Apollo ConfigChangeListener.
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface ApolloConfigChangeListener {
|
||||
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.boot;
|
||||
|
||||
|
||||
import cn.iocoder.dashboard.framework.apollox.Config;
|
||||
import cn.iocoder.dashboard.framework.apollox.ConfigService;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourceFactory;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.util.SpringInjector;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME;
|
||||
|
||||
@Slf4j
|
||||
public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext context) {
|
||||
ConfigurableEnvironment environment = context.getEnvironment();
|
||||
// 忽略,若已经有 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
|
||||
if (environment.getPropertySources().contains(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
|
||||
// already initialized
|
||||
return;
|
||||
}
|
||||
|
||||
// 忽略,若已经有 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
|
||||
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
|
||||
// already initialized
|
||||
return;
|
||||
}
|
||||
|
||||
// 获得 "apollo.bootstrap.namespaces" 配置项
|
||||
List<String> namespaceList = Collections.singletonList("default");
|
||||
|
||||
// 按照优先级,顺序遍历 Namespace
|
||||
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
|
||||
for (String namespace : namespaceList) {
|
||||
// 创建 Apollo Config 对象
|
||||
Config config = ConfigService.getConfig(namespace);
|
||||
// 创建 Namespace 对应的 ConfigPropertySource 对象
|
||||
// 添加到 `composite` 中。
|
||||
composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
|
||||
}
|
||||
|
||||
// 添加到 `environment` 中,且优先级最高
|
||||
environment.getPropertySources().addFirst(composite);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.boot;
|
||||
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourcesProcessor;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(PropertySourcesProcessor.class) // 缺失 PropertySourcesProcessor 时
|
||||
public class ApolloAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public ConfigPropertySourcesProcessor configPropertySourcesProcessor() {
|
||||
return new ConfigPropertySourcesProcessor(); // 注入 ConfigPropertySourcesProcessor bean 对象
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.config;
|
||||
|
||||
import cn.iocoder.dashboard.framework.apollox.Config;
|
||||
import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Property source wrapper for Config
|
||||
*
|
||||
* 基于 {@link Config} 的 PropertySource 实现类
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public class ConfigPropertySource extends EnumerablePropertySource<Config> {
|
||||
|
||||
private static final String[] EMPTY_ARRAY = new String[0];
|
||||
|
||||
ConfigPropertySource(String name, Config source) { // 此处的 Apollo Config 作为 `source`
|
||||
super(name, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getPropertyNames() {
|
||||
// 从 Config 中,获得属性名集合
|
||||
Set<String> propertyNames = this.source.getPropertyNames();
|
||||
// 转换成 String 数组,返回
|
||||
if (propertyNames.isEmpty()) {
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
return propertyNames.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
return this.source.getProperty(name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 ConfigChangeListener 到 Config 中
|
||||
*
|
||||
* @param listener 监听器
|
||||
*/
|
||||
public void addChangeListener(ConfigChangeListener listener) {
|
||||
this.source.addChangeListener(listener);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.config;
|
||||
|
||||
import cn.iocoder.dashboard.framework.apollox.Config;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link ConfigPropertySource} 工厂
|
||||
*/
|
||||
public class ConfigPropertySourceFactory {
|
||||
|
||||
/**
|
||||
* ConfigPropertySource 数组
|
||||
*/
|
||||
private final List<ConfigPropertySource> configPropertySources = Lists.newLinkedList();
|
||||
|
||||
// 创建 ConfigPropertySource 对象
|
||||
public ConfigPropertySource getConfigPropertySource(String name, Config source) {
|
||||
// 创建 ConfigPropertySource 对象
|
||||
ConfigPropertySource configPropertySource = new ConfigPropertySource(name, source);
|
||||
// 添加到数组中
|
||||
configPropertySources.add(configPropertySource);
|
||||
return configPropertySource;
|
||||
}
|
||||
|
||||
public List<ConfigPropertySource> getAllConfigPropertySources() {
|
||||
return Lists.newLinkedList(configPropertySources);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.config;
|
||||
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.annotation.ApolloAnnotationProcessor;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.annotation.SpringValueProcessor;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.property.SpringValueDefinitionProcessor;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.util.BeanRegistrationUtil;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
|
||||
/**
|
||||
* Apollo Property Sources processor for Spring XML Based Application
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor implements BeanDefinitionRegistryPostProcessor {
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
// 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class);
|
||||
// 注册 ApolloAnnotationProcessor 到 BeanDefinitionRegistry 中,因为 XML 配置的 Bean 对象,也可能存在 @ApolloConfig 和 @ApolloConfigChangeListener 注解。
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), ApolloAnnotationProcessor.class);
|
||||
// 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
|
||||
// 注册 ApolloJsonValueProcessor 到 BeanDefinitionRegistry 中,因为 XML 配置的 Bean 对象,也可能存在 @ApolloJsonValue 注解。
|
||||
// BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(), ApolloJsonValueProcessor.class); TODO 芋艿:暂时不需要迁移
|
||||
|
||||
// 处理 XML 配置的 Spring PlaceHolder
|
||||
processSpringValueDefinition(registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be
|
||||
* instantiated if it is added in postProcessBeanDefinitionRegistry phase, so we have to manually
|
||||
* call the postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here...
|
||||
*/
|
||||
private void processSpringValueDefinition(BeanDefinitionRegistry registry) {
|
||||
// 创建 SpringValueDefinitionProcessor 对象
|
||||
SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
|
||||
// 处理 XML 配置的 Spring PlaceHolder
|
||||
springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.config;
|
||||
|
||||
public interface PropertySourcesConstants {
|
||||
|
||||
String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
|
||||
|
||||
String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
|
||||
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.property;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanExpressionContext;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.Scope;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Placeholder 工具类
|
||||
*
|
||||
* Placeholder helper functions.
|
||||
*/
|
||||
public class PlaceholderHelper {
|
||||
|
||||
private static final String PLACEHOLDER_PREFIX = "${";
|
||||
private static final String PLACEHOLDER_SUFFIX = "}";
|
||||
private static final String VALUE_SEPARATOR = ":";
|
||||
private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
|
||||
private static final String EXPRESSION_PREFIX = "#{";
|
||||
private static final String EXPRESSION_SUFFIX = "}";
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// 获得 BeanDefinition 对象
|
||||
BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory.getMergedBeanDefinition(beanName) : null);
|
||||
// resolve expressions like "#{systemProperties.myProp}"
|
||||
return evaluateBeanDefinitionString(beanFactory, strVal, bd);
|
||||
}
|
||||
|
||||
private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value, BeanDefinition beanDefinition) {
|
||||
if (beanFactory.getBeanExpressionResolver() == null) {
|
||||
return value;
|
||||
}
|
||||
Scope scope = (beanDefinition != null ? beanFactory.getRegisteredScope(beanDefinition.getScope()) : null);
|
||||
return beanFactory.getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(beanFactory, scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract keys from placeholder, e.g.
|
||||
* <ul>
|
||||
* <li>${some.key} => "some.key"</li>
|
||||
* <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>
|
||||
* <li>${${some.key}} => "some.key"</li>
|
||||
* <li>${${some.key:other.key}} => "some.key"</li>
|
||||
* <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>
|
||||
* <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Set<String> extractPlaceholderKeys(String propertyString) {
|
||||
Set<String> placeholderKeys = Sets.newHashSet();
|
||||
|
||||
if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) {
|
||||
return placeholderKeys;
|
||||
}
|
||||
|
||||
Stack<String> stack = new Stack<>();
|
||||
stack.push(propertyString);
|
||||
|
||||
while (!stack.isEmpty()) {
|
||||
String strVal = stack.pop();
|
||||
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
|
||||
if (startIndex == -1) {
|
||||
placeholderKeys.add(strVal);
|
||||
continue;
|
||||
}
|
||||
int endIndex = findPlaceholderEndIndex(strVal, startIndex);
|
||||
if (endIndex == -1) {
|
||||
// invalid placeholder?
|
||||
continue;
|
||||
}
|
||||
|
||||
String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
|
||||
|
||||
// ${some.key:other.key}
|
||||
if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
|
||||
stack.push(placeholderCandidate);
|
||||
} else {
|
||||
// some.key:${some.other.key:100}
|
||||
int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);
|
||||
|
||||
if (separatorIndex == -1) {
|
||||
stack.push(placeholderCandidate);
|
||||
} else {
|
||||
stack.push(placeholderCandidate.substring(0, separatorIndex));
|
||||
String defaultValuePart =
|
||||
normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
|
||||
if (!Strings.isNullOrEmpty(defaultValuePart)) {
|
||||
stack.push(defaultValuePart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// has remaining part, e.g. ${a}.${b}
|
||||
if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
|
||||
String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
|
||||
if (!Strings.isNullOrEmpty(remainingPart)) {
|
||||
stack.push(remainingPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return placeholderKeys;
|
||||
}
|
||||
|
||||
private boolean isNormalizedPlaceholder(String propertyString) {
|
||||
return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.endsWith(PLACEHOLDER_SUFFIX);
|
||||
}
|
||||
|
||||
private boolean isExpressionWithPlaceholder(String propertyString) {
|
||||
return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.endsWith(EXPRESSION_SUFFIX)
|
||||
&& propertyString.contains(PLACEHOLDER_PREFIX);
|
||||
}
|
||||
|
||||
private String normalizeToPlaceholder(String strVal) {
|
||||
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
|
||||
if (startIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);
|
||||
if (endIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());
|
||||
}
|
||||
|
||||
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
|
||||
int index = startIndex + PLACEHOLDER_PREFIX.length();
|
||||
int withinNestedPlaceholder = 0;
|
||||
while (index < buf.length()) {
|
||||
if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
|
||||
if (withinNestedPlaceholder > 0) {
|
||||
withinNestedPlaceholder--;
|
||||
index = index + PLACEHOLDER_SUFFIX.length();
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
} else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {
|
||||
withinNestedPlaceholder++;
|
||||
index = index + SIMPLE_PLACEHOLDER_PREFIX.length();
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.property;
|
||||
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySource;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourceFactory;
|
||||
import cn.iocoder.dashboard.framework.apollox.spring.util.SpringInjector;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Apollo Property Sources processor for Spring Annotation Based Application. <br /> <br />
|
||||
* <p>
|
||||
* The reason why PropertySourcesProcessor implements {@link BeanFactoryPostProcessor} instead of
|
||||
* {@link org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor} is that lower versions of
|
||||
* Spring (e.g. 3.1.1) doesn't support registering BeanDefinitionRegistryPostProcessor in ImportBeanDefinitionRegistrar
|
||||
* - {@link com.ctrip.framework.apollo.spring.annotation.ApolloConfigRegistrar}
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
|
||||
|
||||
/**
|
||||
* 是否初始化的标识
|
||||
*/
|
||||
private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
|
||||
|
||||
private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
|
||||
/**
|
||||
* Spring ConfigurableEnvironment 对象
|
||||
*/
|
||||
private ConfigurableEnvironment environment;
|
||||
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
if (INITIALIZED.compareAndSet(false, true)) {
|
||||
// 初始化 AutoUpdateConfigChangeListener 对象,实现属性的自动更新
|
||||
initializeAutoUpdatePropertiesFeature(beanFactory);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {
|
||||
// 创建 AutoUpdateConfigChangeListener 对象
|
||||
AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(environment, beanFactory);
|
||||
// 循环,向 ConfigPropertySource 注册配置变更器
|
||||
List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
|
||||
for (ConfigPropertySource configPropertySource : configPropertySources) {
|
||||
configPropertySource.addChangeListener(autoUpdateConfigChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
//it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
|
||||
this.environment = (ConfigurableEnvironment) environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
// make it as early as possible
|
||||
return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.property;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Spring Value 定义
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class SpringValueDefinition {
|
||||
|
||||
/**
|
||||
* KEY
|
||||
*
|
||||
* 即在 Config 中的属性 KEY 。
|
||||
*/
|
||||
private final String key;
|
||||
/**
|
||||
* 占位符
|
||||
*/
|
||||
private final String placeholder;
|
||||
/**
|
||||
* 属性名
|
||||
*/
|
||||
private final String propertyName;
|
||||
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.util;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Bean Registration 工具类
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public class BeanRegistrationUtil {
|
||||
|
||||
// 注册 `beanClass` 到 BeanDefinitionRegistry 中,当且仅当 `beanName` 和 `beanClass` 都不存在对应的 BeanDefinition 时
|
||||
public static boolean registerBeanDefinitionIfNotExists(BeanDefinitionRegistry registry, String beanName, Class<?> beanClass) {
|
||||
// 不存在 `beanName` 对应的 BeanDefinition
|
||||
if (registry.containsBeanDefinition(beanName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不存在 `beanClass` 对应的 BeanDefinition
|
||||
String[] candidates = registry.getBeanDefinitionNames();
|
||||
for (String candidate : candidates) {
|
||||
BeanDefinition beanDefinition = registry.getBeanDefinition(candidate);
|
||||
if (Objects.equals(beanDefinition.getBeanClassName(), beanClass.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册 `beanClass` 到 BeanDefinitionRegistry 中
|
||||
BeanDefinition annotationProcessor = BeanDefinitionBuilder.genericBeanDefinition(beanClass).getBeanDefinition();
|
||||
registry.registerBeanDefinition(beanName, annotationProcessor);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package cn.iocoder.dashboard.framework.apollox.spring.util;
|
||||
|
||||
import cn.hutool.core.lang.Singleton;
|
||||
|
||||
/**
|
||||
* Spring 注入器
|
||||
*/
|
||||
public class SpringInjector {
|
||||
|
||||
public static <T> T getInstance(Class<T> clazz) {
|
||||
return Singleton.get(clazz);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.dashboard.framework.apollox.spring.boot.ApolloAutoConfiguration
|
||||
org.springframework.context.ApplicationContextInitializer=\
|
||||
cn.iocoder.dashboard.framework.apollox.spring.boot.ApolloApplicationContextInitializer
|
||||
Loading…
Reference in New Issue