揭秘Spring配置黑魔法:无需重启,让静态配置也能跳舞!

484 阅读7分钟

在微服务架构的世界里,配置的动态更新犹如魔法,而静态配置则常常是这魔法的绊脚石。今天,让我们一起探索如何突破Spring框架的限制,让静态配置也能随心所欲地"跳舞"!

静态配置:Spring的心头痛

在Spring中,@Value注解装饰的静态字段就像是被施了定身法:

public class GlobalConfig {
    @Value("${app.timeout}")
    private static int TIMEOUT;
}

这个TIMEOUT值在应用启动后就固定不变,哪怕你在配置中心更新了值,它依旧我行我素。这不仅让运维人员头疼,也让开发者在面对需要频繁调整的配置时束手无策。

Spring的内置解决方案:功能有余,优雅不足

Spring确实提供了一些应对之策,但每一种都有其局限性:

@RefreshScope:

@RefreshScope
@Component
public class DynamicConfig {
    @Value("${app.dynamic.value}")
    private String dynamicValue;
}

看似美好,但它不支持静态字段,而且会重新创建整个bean,代价不小。

@ConfigurationProperties:

@ConfigurationProperties(prefix = "app")
@Component
public class AppProperties {
    private String dynamicValue;
}

灵活,但主要针对非静态字段,对我们的静态配置无能为力。

ApplicationContext.getEnvironment():

@Autowired
private Environment env;

public String getDynamicValue() {
    return env.getProperty("app.dynamic.value");
}

每次都要手动获取,用起来不够优雅。

Spring的局限:为什么不能动态更新static变量?

在深入StaticConfigUpdater之前,我们需要理解为什么Spring框架本身不支持动态更新static变量。这涉及到Spring的核心实现原理。

Spring的属性注入机制 Spring通过其IoC容器管理bean的生命周期和依赖注入。对于@Value注解的处理,主要在AutowiredAnnotationBeanPostProcessor类中进行。让我们看看关键代码:

// 在org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor类中
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    // ...
    ReflectionUtils.doWithLocalFields(clazz, field -> {
        AnnotationAttributes ann = findAutowiredAnnotation(field);
        if (ann != null) {
            if (Modifier.isStatic(field.getModifiers())) {
                if (logger.isInfoEnabled()) {
                    logger.info("Autowired annotation is not supported on static fields: " + field);
                }
                return;
            }
            boolean required = determineRequiredStatus(ann);
            currElements.add(new AutowiredFieldElement(field, required));
        }
    });
    // ...
}

这段代码揭示了Spring处理@Value注解(实际上是Autowired注解的一种特殊形式)的核心逻辑:

Spring遍历类的所有字段。对于每个字段,它检查是否存在@Value(或@Autowired)注解。如果字段是静态的(static),Spring会忽略它,并记录一条信息日志。只有非静态字段才会被添加到注入元数据中。为什么Spring做出这个限制?生命周期管理:静态字段属于类,而不是实例。Spring的IoC容器主要管理bean实例的生命周期,而不是类级别的元素。

线程安全考虑:动态更新静态字段可能导致并发问题,特别是在多线程环境中。

设计哲学:Spring推崇依赖注入和控制反转,而静态字段通常被视为全局状态,这与这些原则有些冲突。

技术限制:Spring的刷新机制(如@RefreshScope)是基于重新创建bean实例来实现的,这对静态字段不起作用。

让静态配置活起来

面对Spring的这些局限,我们需要一个更强大的解决方案。我们巧妙地利用了Spring的事件机制和反射,实现了静态配置的动态更新。让我们来解析其中的关键点:

事件监听:通过实现ApplicationListener,我们能够捕获配置变更事件。

缓存机制:使用ConcurrentHashMap缓存setter方法,提高性能。

动态查找:如果缓存中没有找到setter,会动态查找并缓存。

类型转换:利用ConversionService进行安全的类型转换,支持各种数据类型。

反射调用:通过反射调用setter方法,实现配置更新。

原理

事件驱动:不依赖Spring的常规注入机制,而是监听配置变更事件。

反射机制:使用Java反射API直接操作静态字段,绕过了Spring的限制。

缓存优化:通过缓存setter方法,减少反射带来的性能开销。

类型安全:利用ConversionService确保类型安全的值转换。

代码

// 注入 Spring 的环境对象,用于访问和获取配置属性
private final Environment environment;
// 注入 Spring 的应用上下文,用于获取 Bean 的类型和管理应用中的所有 Bean
private final ApplicationContext applicationContext;
// 缓存配置键和对应的 setter 方法,以便快速查找和更新
private final Map<String, Method> setterCache = new ConcurrentHashMap<>();
// 使用 Spring 的转换服务,将字符串转换为目标类型
private final ConversionService conversionService = new DefaultConversionService();

public StaticConfigUpdater(Environment environment, ApplicationContext applicationContext) {
    this.environment = environment;
    this.applicationContext = applicationContext;
    // 扫描带有 @Value 注解的 setter 方法,并将它们缓存起来
    scanForSetters();
}

@Override
// 监听 Spring 的 EnvironmentChangeEvent 事件
public void onApplicationEvent(EnvironmentChangeEvent event) {
    // 获取发生变化的配置键集合
    Set<String> updatedKeys = event.getKeys();

    for (String key : updatedKeys) {
        // 从环境中获取新的配置值
        String newValue = environment.getProperty(key);
        if (newValue != null) {
            // 使用缓存的 setter 方法更新静态字段
            updateStaticFieldsUsingSetter(key, newValue);
        }
    }
}

// 扫描所有 Bean,查找带有 @Value 注解的 setter 方法,并将它们缓存起来
private void scanForSetters() {
    // 获取应用上下文中所有 Bean 的定义名称
    String[] allBeanNames = applicationContext.getBeanDefinitionNames();

    // 遍历每个 Bean
    for (String beanName : allBeanNames) {
        // 获取 Bean 的类类型
        Class<?> beanClass = applicationContext.getType(beanName);
        //精确查找
        if (beanClass != null && AnnotatedElementUtils.hasAnnotation(beanClass, RefreshScope.class)) {
            // 遍历类中的所有方法
            for (Method method : beanClass.getDeclaredMethods()) {
                // 只扫set方法中带有@Value注解的
                if (method.getName().startsWith("set") && method.isAnnotationPresent(Value.class)) {
                    // 获取 @Value 注解中的值表达式
                    Value valueAnnotation = method.getAnnotation(Value.class);
                    String valueExpression = valueAnnotation.value();
                    // 提取配置键
                    String configKey = extractConfigKey(valueExpression);
                    if (configKey != null) {
                        // 将配置键和对应的方法缓存起来
                        setterCache.put(configKey, method);
                    }
                }
            }
        }
    }
}

// 使用缓存的 setter 方法更新静态字段
private void updateStaticFieldsUsingSetter(String configKey, String newValue) {
    // 从缓存中获取对应的 setter 方法
    Method setter = setterCache.get(configKey);

    if (setter == null) {
        // 如果缓存中没有,尝试动态查找 setter 方法
        setter = findSetterMethodDynamically(configKey);

        if (setter != null) {
            // 找到后更新缓存
            setterCache.put(configKey, setter);
        }
    }

    if (setter != null) {
        try {
            // 获取 setter 方法的参数类型
            Class<?> parameterType = setter.getParameterTypes()[0];

            // 如果参数类型是 String,但实际要注入的是 Map 或 Collection 类型
            if (parameterType == String.class) {
                // 查找目标字段的类型
                Field targetField = findFieldByPossibleNames(setter.getDeclaringClass(), getFieldNameFromSetter(setter));
                if (targetField != null) {
                    // 目标字段类型
                    Class<?> targetFieldType = targetField.getType();

                    if (Map.class.isAssignableFrom(targetFieldType)) {
                        // 解析字符串并更新 Map
                        //Map<String, String> newMap = convertStringToMap(newValue);
                        updateMapFieldDirectly(targetField);
                    } else if (Collection.class.isAssignableFrom(targetFieldType)) {
                        // 解析字符串并更新 Collection
                        //Collection<String> newCollection = convertStringToCollection(newValue, targetFieldType);
                        updateCollectionFieldDirectly(targetField);
                    }
                    // 进行设置
                    setter.invoke(applicationContext.getBean(setter.getDeclaringClass()), newValue);
                }
            } else {
                // 使用 Spring 的 ConversionService 进行类型转换
                Object convertedValue = conversionService.convert(newValue, parameterType);
                if (convertedValue != null) {
                    // 动态调用 setter 方法
                    Object beanInstance = applicationContext.getBean(setter.getDeclaringClass());
                    setter.invoke(beanInstance, convertedValue);
                }
            }
        } catch (Exception e) {
            log.error("Error updating field for config key: {}",  configKey);
            e.printStackTrace();
        }
    } else {
        // 处理仍未找到 setter 的情况
        log.error("No setter found for config key: {}. The configuration update for this key will be skipped.",configKey );
    }
}

// 动态查找与配置键匹配的 setter 方法
private Method findSetterMethodDynamically(String configKey) {
    try {
        // 遍历所有缓存的类,查找符合条件的 setter 方法
        for (String beanName : applicationContext.getBeanDefinitionNames()) {
            Class<?> beanClass = applicationContext.getType(beanName);
            if (beanClass != null) {
                for (Method method : beanClass.getDeclaredMethods()) {
                    if (method.isAnnotationPresent(Value.class)) {
                        Value valueAnnotation = method.getAnnotation(Value.class);
                        String valueExpression = valueAnnotation.value();
                        String extractedKey = extractConfigKey(valueExpression);
                        if (configKey.equals(extractedKey)) {
                            return method;
                        }
                    }
                }
            }
        }
    } catch (Exception e) {
        log.error("Error finding setter method dynamically for config key: {}" , configKey);
        e.printStackTrace();
    }
    return null; // 如果没有找到合适的方法,返回 null
}

// 提取 ${xxxx} 中的配置键
private String extractConfigKey(String valueExpression) {
    if (valueExpression.startsWith("${") && valueExpression.endsWith("}")) {
        return valueExpression.substring(2, valueExpression.length() - 1);
    }
    return null;
}

// 将字符串解析为 Map
/*private Map<String, String> convertStringToMap(String newValue) {
    // 假设字符串的格式是 key_value,以逗号分隔每个条目
    Map<String, String> map = new HashMap<>();
    String[] pairs = newValue.split(",");
    for (String pair : pairs) {
        String[] keyValue = pair.split("_");
        if (keyValue.length == 2) {
            map.put(keyValue[0].trim(), keyValue[1].trim());
        }
    }
    return map;
}*/

// 将字符串解析为 Collection
/*private Collection<String> convertStringToCollection(String newValue, Class<?> collectionType) {
    // 将字符串转换为集合,假设元素是以逗号分隔
    List<String> items = Stream.of(newValue.split(","))
            .map(String::trim)
            .collect(Collectors.toList());

    if (Set.class.isAssignableFrom(collectionType)) {
        return new HashSet<>(items); // 创建 Set
    } else if (List.class.isAssignableFrom(collectionType)) {
        return new ArrayList<>(items); // 创建 List
    } else {
        // 对于其他集合类型,可以在这里添加更多的处理逻辑
        throw new IllegalArgumentException("Unsupported collection type: " + collectionType);
    }
}*/


// 直接更新 Collection 类型的字段
private void updateCollectionFieldDirectly(Field field) throws Exception {
        field.setAccessible(true);

        // 创建一个新的集合来替换旧的集合
        Collection<Object> newGenericCollection = createNewCollectionInstance(field.getType());

        // 设置新的集合到静态字段
        field.set(null, newGenericCollection);
}

// 根据目标集合类型创建新的集合实例
private Collection<ObjectcreateNewCollectionInstance(Class<?> collectionType) {
    if (Set.class.isAssignableFrom(collectionType)) {
        return new HashSet<>();
    } else if (List.class.isAssignableFrom(collectionType)) {
        return new ArrayList<>();
    } else {
        // 默认返回一个 ArrayList
        return new ArrayList<>();
    }
}



// 直接更新 Map 类型的字段
private void updateMapFieldDirectly(Field field) throws Exception {
        field.setAccessible(true);

        // 创建一个新的 Map 来替换旧的 Map
        Map<ObjectObject> newGenericMap = new HashMap<>();

        // 设置新的 Map 到静态字段
        field.set(null, newGenericMap);
}

// 通过生成可能的字段名称,尝试查找目标字段
private Field findFieldByPossibleNames(Class<?> clazz, String fieldName) {
    // 生成可能的字段名称列表
    List<String> possibleNames = generatePossibleFieldNames(fieldName);

    for (String possibleName : possibleNames) {
        try {
            Field field = clazz.getDeclaredField(possibleName);
            return field;
        } catch (NoSuchFieldException ignored) {
        }
    }
    return null; // 如果没有找到合适的字段,返回 null
}

// 生成可能的字段名称列表
private List<String> generatePossibleFieldNames(String fieldName) {
    List<String> possibleNames = new ArrayList<>();
    possibleNames.add(fieldName); // 原始字段名
    possibleNames.add(Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1)); // 首字母小写
    possibleNames.add(fieldName.replaceAll("([A-Z])", "_$1").toLowerCase()); // 小写下划线分隔
    possibleNames.add(fieldName.replaceAll("([A-Z])", "_$1").toUpperCase()); // 大写下划线分隔
    return possibleNames;
}

// 从 setter 方法名推断字段名
private String getFieldNameFromSetter(Method setter) {
    String methodName = setter.getName();
    if (methodName.startsWith("set")) {
        methodName = methodName.substring(3);
        return Character.toLowerCase(methodName.charAt(0)) + methodName.substring(1);
    }
    return null;
}

现在,每当你在配置中心修改app.timeout的值,自动更新TIMEOUT,无需重启应用!