mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 18:28:43 +08:00 
			
		
		
		
	实现配置中心的基础
This commit is contained in:
		| @@ -0,0 +1,28 @@ | ||||
| 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(); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,19 @@ | ||||
| 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); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox; | ||||
|  | ||||
| import java.util.Set; | ||||
|  | ||||
| public class DBConfig implements Config { | ||||
|  | ||||
|     @Override | ||||
|     public String getProperty(String key, String defaultValue) { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Set<String> getPropertyNames() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,14 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.enums; | ||||
|  | ||||
| /** | ||||
|  * 属性变化类型枚举 | ||||
|  * | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| public enum PropertyChangeType { | ||||
|  | ||||
|     ADDED, // 添加 | ||||
|     MODIFIED, // 修改 | ||||
|     DELETED // 删除 | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,35 @@ | ||||
| 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; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,53 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.model; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
|  | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * A change event when a namespace's config is changed. | ||||
|  * | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| @AllArgsConstructor | ||||
| public class ConfigChangeEvent { | ||||
|  | ||||
|     /** | ||||
|      * 变化属性的集合 | ||||
|      * | ||||
|      * KEY:属性名 | ||||
|      * VALUE:配置变化 | ||||
|      */ | ||||
|     private final Map<String, ConfigChange> m_changes; | ||||
|  | ||||
|     /** | ||||
|      * Get the keys changed. | ||||
|      * | ||||
|      * @return the list of the keys | ||||
|      */ | ||||
|     public Set<String> changedKeys() { | ||||
|         return m_changes.keySet(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get a specific change instance for the key specified. | ||||
|      * | ||||
|      * @param key the changed key | ||||
|      * @return the change instance | ||||
|      */ | ||||
|     public ConfigChange getChange(String key) { | ||||
|         return m_changes.get(key); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check whether the specified key is changed | ||||
|      * | ||||
|      * @param key the key | ||||
|      * @return true if the key is changed, false otherwise. | ||||
|      */ | ||||
|     public boolean isChanged(String key) { | ||||
|         return m_changes.containsKey(key); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,16 @@ | ||||
| /** | ||||
|  * 配置中心客户端,基于 Apollo Client 实现,所以叫 ApolloX | ||||
|  * | ||||
|  * 差别在于,我们使用 {@link cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.config.SysConfigDO} 表作为配置源。 | ||||
|  * 当然,功能肯定也会相对少些,满足最小化诉求。 | ||||
|  * | ||||
|  * 1. 项目初始化时,可以使用 SysConfigDO 表的配置 | ||||
|  * 2. 使用 Spring @Value 可以注入属性 | ||||
|  * 3. SysConfigDO 表的配置修改时,注入到 @Value 的属性可以刷新 | ||||
|  * | ||||
|  * 另外,整个包结构会参考 Apollo 为主,方便维护与理解 | ||||
|  * | ||||
|  * 注意,目前有两个特性是不支持的 | ||||
|  * 1. 自定义配置变化的监听器 | ||||
|  */ | ||||
| package cn.iocoder.dashboard.framework.apollox; | ||||
| @@ -0,0 +1,68 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.annotation; | ||||
|  | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.core.Ordered; | ||||
| import org.springframework.core.PriorityOrdered; | ||||
| import org.springframework.util.ReflectionUtils; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * Apollo 处理器抽象类,封装了在 Spring Bean 初始化之前,处理属性和方法。 | ||||
|  * | ||||
|  * Create by zhangzheng on 2018/2/6 | ||||
|  */ | ||||
| public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered { | ||||
|  | ||||
|     @Override | ||||
|     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||||
|         Class<?> clazz = bean.getClass(); | ||||
|         // 处理所有 Field | ||||
|         for (Field field : findAllField(clazz)) { | ||||
|             processField(bean, beanName, field); | ||||
|         } | ||||
|         // 处理所有的 Method | ||||
|         for (Method method : findAllMethod(clazz)) { | ||||
|             processMethod(bean, beanName, method); | ||||
|         } | ||||
|         return bean; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { | ||||
|         return bean; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * subclass should implement this method to process field | ||||
|      */ | ||||
|     protected abstract void processField(Object bean, String beanName, Field field); | ||||
|  | ||||
|     /** | ||||
|      * subclass should implement this method to process method | ||||
|      */ | ||||
|     protected abstract void processMethod(Object bean, String beanName, Method method); | ||||
|  | ||||
|     @Override | ||||
|     public int getOrder() { | ||||
|         // make it as late as possible | ||||
|         return Ordered.LOWEST_PRECEDENCE; // 最高优先级 | ||||
|     } | ||||
|  | ||||
|     private List<Field> findAllField(Class<?> clazz) { | ||||
|         final List<Field> res = new LinkedList<>(); | ||||
|         ReflectionUtils.doWithFields(clazz, res::add); | ||||
|         return res; | ||||
|     } | ||||
|  | ||||
|     private List<Method> findAllMethod(Class<?> clazz) { | ||||
|         final List<Method> res = new LinkedList<>(); | ||||
|         ReflectionUtils.doWithMethods(clazz, res::add); | ||||
|         return res; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,141 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.annotation; | ||||
|  | ||||
| import cn.hutool.core.lang.Singleton; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.property.*; | ||||
| import com.google.common.collect.LinkedListMultimap; | ||||
| import com.google.common.collect.Multimap; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.BeanUtils; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.beans.factory.config.BeanFactoryPostProcessor; | ||||
| import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | ||||
| import org.springframework.context.annotation.Bean; | ||||
|  | ||||
| import java.beans.PropertyDescriptor; | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Collection; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * Spring value processor of field or method which has @Value and xml config placeholders. | ||||
|  * | ||||
|  * Spring Value 处理器,处理: | ||||
|  * | ||||
|  * 1. 带有 `@Value` 注解的 Field 和 Method | ||||
|  * 2. XML 配置的 Bean 的 PlaceHolder 们 | ||||
|  * | ||||
|  * 每个 Field、Method、XML PlaceHolder 被处理成一个 SpringValue 对象,添加到 SpringValueRegistry 中。 | ||||
|  * | ||||
|  * 目的还是,为了 PlaceHolder 的自动更新机制。 | ||||
|  * | ||||
|  * @author github.com/zhegexiaohuozi  seimimaster@gmail.com | ||||
|  * @since 2017/12/20. | ||||
|  */ | ||||
| @Slf4j | ||||
| public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor { | ||||
|  | ||||
|     /** | ||||
|      * SpringValueDefinition 集合 | ||||
|      * | ||||
|      * KEY:beanName | ||||
|      * VALUE:SpringValueDefinition 集合 | ||||
|      */ | ||||
|     private static Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create(); | ||||
|  | ||||
|     private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class); | ||||
|     private final SpringValueRegistry springValueRegistry = Singleton.get(SpringValueRegistry.class); | ||||
|  | ||||
|     @Override | ||||
|     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { | ||||
|         beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||||
|         // 处理 Field 和 Method | ||||
|         super.postProcessBeforeInitialization(bean, beanName); | ||||
|         // 处理 XML 配置的 Bean 的 PlaceHolder 们 | ||||
|         processBeanPropertyValues(bean, beanName); | ||||
|         return bean; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void processField(Object bean, String beanName, Field field) { | ||||
|         // register @Value on field | ||||
|         Value value = field.getAnnotation(Value.class); | ||||
|         if (value == null) { | ||||
|             return; | ||||
|         } | ||||
|         // 提取 `keys` 属性们。 | ||||
|         Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); | ||||
|         if (keys.isEmpty()) { | ||||
|             return; | ||||
|         } | ||||
|         // 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。 | ||||
|         for (String key : keys) { | ||||
|             SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false); | ||||
|             springValueRegistry.register(key, springValue); | ||||
|             log.debug("Monitoring {}", springValue); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void processMethod(Object bean, String beanName, Method method) { | ||||
|         // register @Value on method | ||||
|         Value value = method.getAnnotation(Value.class); | ||||
|         if (value == null) { | ||||
|             return; | ||||
|         } | ||||
|         // 忽略 @Bean 注解的方法 | ||||
|         // skip Configuration bean methods | ||||
|         if (method.getAnnotation(Bean.class) != null) { | ||||
|             return; | ||||
|         } | ||||
|         // 忽略非 setting 方法 | ||||
|         if (method.getParameterTypes().length != 1) { | ||||
|             log.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", bean.getClass().getName(), method.getName(), method.getParameterTypes().length); | ||||
|             return; | ||||
|         } | ||||
|         // 提取 `keys` 属性们。 | ||||
|         Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); | ||||
|         if (keys.isEmpty()) { | ||||
|             return; | ||||
|         } | ||||
|         // 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。 | ||||
|         for (String key : keys) { | ||||
|             SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false); | ||||
|             springValueRegistry.register(key, springValue); | ||||
|             log.info("Monitoring {}", springValue); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void processBeanPropertyValues(Object bean, String beanName) { | ||||
|         // 获得 SpringValueDefinition 数组 | ||||
|         Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions.get(beanName); | ||||
|         if (propertySpringValues == null || propertySpringValues.isEmpty()) { | ||||
|             return; | ||||
|         } | ||||
|         // 循环 SpringValueDefinition 数组,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。 | ||||
|         for (SpringValueDefinition definition : propertySpringValues) { | ||||
|             try { | ||||
|                 PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(bean.getClass(), definition.getPropertyName()); | ||||
|                 Method method = pd.getWriteMethod(); | ||||
|                 if (method == null) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), bean, beanName, method, false); | ||||
|                 springValueRegistry.register(definition.getKey(), springValue); | ||||
|                 log.debug("Monitoring {}", springValue); | ||||
|             } catch (Throwable ex) { | ||||
|                 log.error("Failed to enable auto update feature for {}.{}", bean.getClass(), definition.getPropertyName()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // clear | ||||
|         // 移除 Bean 对应的 SpringValueDefinition 数组 | ||||
|         beanName2SpringValueDefinitions.removeAll(beanName); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,34 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.boot; | ||||
|  | ||||
|  | ||||
| import cn.hutool.core.lang.Singleton; | ||||
| import cn.iocoder.dashboard.framework.apollox.Config; | ||||
| import cn.iocoder.dashboard.framework.apollox.DBConfig; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySource; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.context.ApplicationContextInitializer; | ||||
| import org.springframework.context.ConfigurableApplicationContext; | ||||
| import org.springframework.core.env.ConfigurableEnvironment; | ||||
|  | ||||
| import static cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME; | ||||
|  | ||||
| @Slf4j | ||||
| public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { | ||||
|  | ||||
|     @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 | ||||
|         Config config = Singleton.get(DBConfig.class); | ||||
|         ConfigPropertySource configPropertySource = new ConfigPropertySource(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, config); | ||||
|         // 添加到 `environment` 中,且优先级最高 | ||||
|         environment.getPropertySources().addFirst(configPropertySource); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| 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 对象 | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,39 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.config; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.apollox.Config; | ||||
| 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]; | ||||
|  | ||||
|     public 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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,42 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.config; | ||||
|  | ||||
| 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); | ||||
|         // 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制 | ||||
|         BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); | ||||
|  | ||||
|         // 处理 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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.config; | ||||
|  | ||||
| public interface PropertySourcesConstants { | ||||
|  | ||||
|   String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources"; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,159 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.property; | ||||
|  | ||||
| import cn.hutool.core.lang.Singleton; | ||||
| import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener; | ||||
| import cn.iocoder.dashboard.framework.apollox.enums.PropertyChangeType; | ||||
| import cn.iocoder.dashboard.framework.apollox.model.ConfigChange; | ||||
| import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent; | ||||
| import com.alibaba.fastjson.JSON; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.TypeConverter; | ||||
| import org.springframework.beans.factory.config.ConfigurableBeanFactory; | ||||
| import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | ||||
| import org.springframework.core.env.Environment; | ||||
| import org.springframework.util.CollectionUtils; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.Type; | ||||
| import java.util.Collection; | ||||
| import java.util.Objects; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * 自动更新配置监听器 | ||||
|  * | ||||
|  * Create by zhangzheng on 2018/3/6 | ||||
|  */ | ||||
| public class AutoUpdateConfigChangeListener implements ConfigChangeListener { | ||||
|  | ||||
|     private static final Logger logger = LoggerFactory.getLogger(AutoUpdateConfigChangeListener.class); | ||||
|  | ||||
|     /** | ||||
|      * {@link TypeConverter#convertIfNecessary(Object, Class, Field)} 是否带上 Field 参数,因为 Spring 3.2.0+ 才有该方法 | ||||
|      */ | ||||
|     private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter; | ||||
|     private final Environment environment; | ||||
|     private final ConfigurableBeanFactory beanFactory; | ||||
|     /** | ||||
|      * TypeConverter 对象,参见 https://blog.csdn.net/rulerp2014/article/details/51100857 | ||||
|      */ | ||||
|     private final TypeConverter typeConverter; | ||||
|     private final PlaceholderHelper placeholderHelper; | ||||
|     private final SpringValueRegistry springValueRegistry; | ||||
|  | ||||
|     public AutoUpdateConfigChangeListener(Environment environment, ConfigurableListableBeanFactory beanFactory) { | ||||
|         this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter(); | ||||
|         this.beanFactory = beanFactory; | ||||
|         this.typeConverter = this.beanFactory.getTypeConverter(); | ||||
|         this.environment = environment; | ||||
|         this.placeholderHelper = Singleton.get(PlaceholderHelper.class); | ||||
|         this.springValueRegistry = Singleton.get(SpringValueRegistry.class); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onChange(ConfigChangeEvent changeEvent) { | ||||
|         // 获得更新的 KEY 集合 | ||||
|         Set<String> keys = changeEvent.changedKeys(); | ||||
|         if (CollectionUtils.isEmpty(keys)) { | ||||
|             return; | ||||
|         } | ||||
|         // 循环 KEY 集合,更新 StringValue | ||||
|         for (String key : keys) { | ||||
|             // 忽略,若不在 SpringValueRegistry 中 | ||||
|             // 1. check whether the changed key is relevant | ||||
|             Collection<SpringValue> targetValues = springValueRegistry.get(key); | ||||
|             if (targetValues == null || targetValues.isEmpty()) { | ||||
|                 continue; | ||||
|             } | ||||
|             // 校验是否需要更新 | ||||
|             // 2. check whether the value is really changed or not (since spring property sources have hierarchies) | ||||
|             if (!shouldTriggerAutoUpdate(changeEvent, key)) { | ||||
|                 continue; | ||||
|             } | ||||
|             // 循环,更新 SpringValue | ||||
|             // 3. update the value | ||||
|             for (SpringValue val : targetValues) { | ||||
|                 updateSpringValue(val); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check whether we should trigger the auto update or not. | ||||
|      * <br /> | ||||
|      * For added or modified keys, we should trigger auto update if the current value in Spring equals to the new value. | ||||
|      * <br /> | ||||
|      * For deleted keys, we will trigger auto update anyway. | ||||
|      */ | ||||
|     private boolean shouldTriggerAutoUpdate(ConfigChangeEvent changeEvent, String changedKey) { | ||||
|         ConfigChange configChange = changeEvent.getChange(changedKey); | ||||
|         // 若变更类型为删除,需要触发更新 | ||||
|         if (configChange.getChangeType() == PropertyChangeType.DELETED) { | ||||
|             return true; | ||||
|         } | ||||
|         // 若变更类型为新增或修改,判断 environment 的值是否和最新值相等。 | ||||
|         // 【高能】!!! | ||||
|         return Objects.equals(environment.getProperty(changedKey), configChange.getNewValue()); | ||||
|     } | ||||
|  | ||||
|     private void updateSpringValue(SpringValue springValue) { | ||||
|         try { | ||||
|             // 解析值 | ||||
|             Object value = resolvePropertyValue(springValue); | ||||
|             // 更新 StringValue | ||||
|             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, String, Set, TypeConverter) | ||||
|      */ | ||||
|     private Object resolvePropertyValue(SpringValue springValue) { | ||||
|         // value will never be null, as @Value and @ApolloJsonValue will not allow that | ||||
|         Object value = placeholderHelper.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder()); | ||||
|         // 如果值数据结构是 JSON 类型,则使用 Gson 解析成对应值的类型 | ||||
|         if (springValue.isJson()) { | ||||
|             value = parseJsonValue((String) value, springValue.getGenericType()); | ||||
|         } else { | ||||
|             // 如果类型为 Field | ||||
|             if (springValue.isField()) { | ||||
|                 // org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+ | ||||
|                 if (typeConverterHasConvertIfNecessaryWithFieldParameter) { | ||||
|                     value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField()); | ||||
|                 } else { | ||||
|                     value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType()); | ||||
|                 } | ||||
|             // 如果类型为 Method | ||||
|             } else { | ||||
|                 value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getMethodParameter()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     private Object parseJsonValue(String json, Type targetType) { | ||||
|         try { | ||||
|             return JSON.parseObject(json, targetType); | ||||
|         } catch (Throwable ex) { | ||||
|             logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex); | ||||
|             throw ex; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() { | ||||
|         try { | ||||
|             TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class); | ||||
|         } catch (Throwable ex) { | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,160 @@ | ||||
| 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; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,53 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.property; | ||||
|  | ||||
| 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.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); | ||||
|  | ||||
|     /** | ||||
|      * Spring ConfigurableEnvironment 对象 | ||||
|      */ | ||||
|     private ConfigurableEnvironment environment; | ||||
|  | ||||
|     @Override | ||||
|     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @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; // 最高优先级 | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,152 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.property; | ||||
|  | ||||
| import org.springframework.core.MethodParameter; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.lang.reflect.Method; | ||||
| import java.lang.reflect.Type; | ||||
|  | ||||
| /** | ||||
|  * Spring @Value method info | ||||
|  * | ||||
|  * @author github.com/zhegexiaohuozi  seimimaster@gmail.com | ||||
|  * @since 2018/2/6. | ||||
|  */ | ||||
| public class SpringValue { | ||||
|  | ||||
|     /** | ||||
|      * Bean 对象 | ||||
|      */ | ||||
|     private Object bean; | ||||
|     /** | ||||
|      * Bean 名字 | ||||
|      */ | ||||
|     private String beanName; | ||||
|     /** | ||||
|      * Spring 方法参数封装 | ||||
|      */ | ||||
|     private MethodParameter methodParameter; | ||||
|     /** | ||||
|      * Field | ||||
|      */ | ||||
|     private Field field; | ||||
|     /** | ||||
|      * KEY | ||||
|      * | ||||
|      * 即在 Config 中的属性 KEY 。 | ||||
|      */ | ||||
|     private String key; | ||||
|     /** | ||||
|      * 占位符 | ||||
|      */ | ||||
|     private String placeholder; | ||||
|     /** | ||||
|      * 值类型 | ||||
|      */ | ||||
|     private Class<?> targetType; | ||||
|     /** | ||||
|      * 是否 JSON | ||||
|      */ | ||||
|     private boolean isJson; | ||||
|     /** | ||||
|      * 泛型。当是 JSON 类型时,使用 | ||||
|      */ | ||||
|     private Type genericType; | ||||
|  | ||||
|     // Field | ||||
|     public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) { | ||||
|         this.bean = bean; | ||||
|         this.beanName = beanName; | ||||
|         // Field | ||||
|         this.field = field; | ||||
|         this.key = key; | ||||
|         this.placeholder = placeholder; | ||||
|         // Field 差异 | ||||
|         this.targetType = field.getType(); | ||||
|         this.isJson = isJson; | ||||
|         if (isJson) { | ||||
|             this.genericType = field.getGenericType(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Method | ||||
|     public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) { | ||||
|         this.bean = bean; | ||||
|         this.beanName = beanName; | ||||
|         // Method | ||||
|         this.methodParameter = new MethodParameter(method, 0); | ||||
|         this.key = key; | ||||
|         this.placeholder = placeholder; | ||||
|         // Method 差异 | ||||
|         Class<?>[] paramTps = method.getParameterTypes(); | ||||
|         this.targetType = paramTps[0]; | ||||
|         this.isJson = isJson; | ||||
|         if (isJson) { | ||||
|             this.genericType = method.getGenericParameterTypes()[0]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void update(Object newVal) throws IllegalAccessException, InvocationTargetException { | ||||
|         // Field | ||||
|         if (isField()) { | ||||
|             injectField(newVal); | ||||
|         // Method | ||||
|         } else { | ||||
|             injectMethod(newVal); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void injectField(Object newVal) throws IllegalAccessException { | ||||
|         boolean accessible = field.isAccessible(); | ||||
|         field.setAccessible(true); | ||||
|         field.set(bean, newVal); | ||||
|         field.setAccessible(accessible); | ||||
|     } | ||||
|  | ||||
|     private void injectMethod(Object newVal) throws InvocationTargetException, IllegalAccessException { | ||||
|         methodParameter.getMethod().invoke(bean, newVal); | ||||
|     } | ||||
|  | ||||
|     public String getBeanName() { | ||||
|         return beanName; | ||||
|     } | ||||
|  | ||||
|     public Class<?> getTargetType() { | ||||
|         return targetType; | ||||
|     } | ||||
|  | ||||
|     public String getPlaceholder() { | ||||
|         return this.placeholder; | ||||
|     } | ||||
|  | ||||
|     public MethodParameter getMethodParameter() { | ||||
|         return methodParameter; | ||||
|     } | ||||
|  | ||||
|     public boolean isField() { | ||||
|         return this.field != null; | ||||
|     } | ||||
|  | ||||
|     public Field getField() { | ||||
|         return field; | ||||
|     } | ||||
|  | ||||
|     public Type getGenericType() { | ||||
|         return genericType; | ||||
|     } | ||||
|  | ||||
|     public boolean isJson() { | ||||
|         return isJson; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         if (isField()) { | ||||
|             return String.format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass().getName(), field.getName()); | ||||
|         } | ||||
|         return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(), | ||||
|                 methodParameter.getMethod().getName()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,28 @@ | ||||
| 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; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,94 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.property; | ||||
|  | ||||
| import cn.hutool.core.lang.Singleton; | ||||
| import com.google.common.collect.LinkedListMultimap; | ||||
| import com.google.common.collect.Multimap; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.MutablePropertyValues; | ||||
| import org.springframework.beans.PropertyValue; | ||||
| import org.springframework.beans.factory.config.BeanDefinition; | ||||
| import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | ||||
| import org.springframework.beans.factory.config.TypedStringValue; | ||||
| import org.springframework.beans.factory.support.BeanDefinitionRegistry; | ||||
| import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
|  | ||||
| /** | ||||
|  * To process xml config placeholders, e.g. | ||||
|  * | ||||
|  * <pre> | ||||
|  *  <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean"> | ||||
|  *    <property name="timeout" value="${timeout:200}"/> | ||||
|  *    <property name="batch" value="${batch:100}"/> | ||||
|  *  </bean> | ||||
|  * </pre> | ||||
|  */ | ||||
| public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor { | ||||
|  | ||||
|     /** | ||||
|      * SpringValueDefinition 集合 | ||||
|      * <p> | ||||
|      * KEY:beanName | ||||
|      * VALUE:SpringValueDefinition 集合 | ||||
|      */ | ||||
|     private static final Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create(); | ||||
|     /** | ||||
|      * 是否初始化的标识 | ||||
|      */ | ||||
|     private static final AtomicBoolean initialized = new AtomicBoolean(false); | ||||
|  | ||||
|     private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class); | ||||
|  | ||||
|     @Override | ||||
|     public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { | ||||
|         processPropertyValues(registry); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { | ||||
|     } | ||||
|  | ||||
|     public static Multimap<String, SpringValueDefinition> getBeanName2SpringValueDefinitions() { | ||||
|         return beanName2SpringValueDefinitions; | ||||
|     } | ||||
|  | ||||
|     private void processPropertyValues(BeanDefinitionRegistry beanRegistry) { | ||||
|         // 若已经初始化,直接返回 | ||||
|         if (!initialized.compareAndSet(false, true)) { | ||||
|             // already initialized | ||||
|             return; | ||||
|         } | ||||
|         // 循环 BeanDefinition 集合 | ||||
|         String[] beanNames = beanRegistry.getBeanDefinitionNames(); | ||||
|         for (String beanName : beanNames) { | ||||
|             BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName); | ||||
|             // 循环 BeanDefinition 的 PropertyValue 数组 | ||||
|             MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues(); | ||||
|             List<PropertyValue> propertyValues = mutablePropertyValues.getPropertyValueList(); | ||||
|             for (PropertyValue propertyValue : propertyValues) { | ||||
|                 // 获得 `value` 属性。 | ||||
|                 Object value = propertyValue.getValue(); | ||||
|                 // 忽略非 Spring PlaceHolder 的 `value` 属性。 | ||||
|                 if (!(value instanceof TypedStringValue)) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 获得 `placeholder` 属性。 | ||||
|                 String placeholder = ((TypedStringValue) value).getValue(); | ||||
|                 // 提取 `keys` 属性们。 | ||||
|                 Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder); | ||||
|                 if (keys.isEmpty()) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 循环 `keys` ,创建对应的 SpringValueDefinition 对象,并添加到 `beanName2SpringValueDefinitions` 中。 | ||||
|                 for (String key : keys) { | ||||
|                     beanName2SpringValueDefinitions.put(beanName, | ||||
|                             new SpringValueDefinition(key, placeholder, propertyValue.getName())); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,31 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.property; | ||||
|  | ||||
| import com.google.common.collect.LinkedListMultimap; | ||||
| import com.google.common.collect.Multimap; | ||||
|  | ||||
| import java.util.Collection; | ||||
|  | ||||
| /** | ||||
|  * {@link SpringValue} 注册表 | ||||
|  */ | ||||
| public class SpringValueRegistry { | ||||
|  | ||||
|     /** | ||||
|      * SpringValue 集合 | ||||
|      * | ||||
|      * KEY:属性 KEY ,即 Config 配置 KEY | ||||
|      * VALUE:SpringValue 数组 | ||||
|      */ | ||||
|     private final Multimap<String, SpringValue> registry = LinkedListMultimap.create(); | ||||
|  | ||||
|     // 注册 | ||||
|     public void register(String key, SpringValue springValue) { | ||||
|         registry.put(key, springValue); | ||||
|     } | ||||
|  | ||||
|     // 获得 | ||||
|     public Collection<SpringValue> get(String key) { | ||||
|         return registry.get(key); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,38 @@ | ||||
| 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; | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										4
									
								
								src/main/resources/META-INF/spring.factories
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/main/resources/META-INF/spring.factories
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| 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 | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV