手动实现 Spring @Value 属性动态刷新

332 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

实现思路

  1. 项目启动时通过 BeanPostProcessor 记录所有的 @Value 属性使用者。
  2. 通过指定方法或暴露 API 对 Spring 应用的某个属性项进行修改。 修改属性的方法通过反射一并将所有的 @Value 属性使用者中的值修改,达到刷新配置的目的。 核心代码

参考了 Apollo 配置中心的原理,起初也是通过SpringCloud 配置动态刷新这个功能引起了我的兴趣。

核心代码

1.属性配置文件。

server.port=8081
VariableName=ikun
VariableUrl=https://ikun.com

2.属性使用者。

@RestController
public class ConfigController {
    @Value("${VariableName:匿名}")
    private String name;

    @Value("${VariableUrl}")
    private String VariableUrl;

    @GetMapping("/get")
    public String get() {
        return name + ":1" + VariableUrl;
    }
}

3.通过 BeanPostProcessor 找到并记录所有的 @Value 属性使用者。

@Component
public class SpringValueProcessor implements BeanPostProcessor, BeanFactoryAware {

    private final PlaceholderHelper placeholderHelper = new PlaceholderHelper();
    public SpringValueRegistry springValueRegistry = new SpringValueRegistry();

    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = bean.getClass();
        for (Field field : findAllField(clazz)) {
            processField(bean, beanName, field);
        }
        return bean;
    }

    /**
     * 将使用了 {@link Value} 注解的字段记录起来,以便后续动态更新。
     */
    private void processField(Object bean, String beanName, Field field) {
        // register @Value on field
        Value value = field.getAnnotation(Value.class);
        if (value == null) {
            return;
        }
        Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());     // 解析 SpEL 表达式 key

        if (keys.isEmpty()) {
            return;
        }

        for (String key : keys) {
            SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
            springValueRegistry.register(beanFactory, key, springValue);
        }
    }

    /**
     * @return 返回指定类型上所有的字段
     */
    private List<Field> findAllField(Class<?> clazz) {
        final List<Field> res = new LinkedList<>();
        ReflectionUtils.doWithFields(clazz, res::add);
        return res;
    }
}

4.通过反射对属性使用者进行修改。

private void injectField(Object newVal) throws IllegalAccessException {
    boolean accessible = field.isAccessible();
    field.setAccessible(true);
    field.set(bean, newVal);
    field.setAccessible(accessible);
}

5.对外暴露属性动态更新的 API,实现外部动态修改属性的目的。

@GetMapping("/updateName")
public String updateName(String newName) {
    // 拿到所有通过 @Value 注入了 VariableName 属性的 bean 及字段信息
    Collection<SpringValue> targetValues = springValueProcessor.springValueRegistry.get(beanFactory, "VariableName");
    for (SpringValue val : targetValues) {
        try {
            val.update(newName);           // 通过反射进行修改
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    return this.name;           // 返回修改后的效果
}

完整代码 github.com/lmmarisej/S…