通过 BeanDefinitionRegistryPostProcessor 注册的 @RefreshScope 注释 Bean 不会在 Cloud Config 更改时刷新

@RefreshScope annotated Bean registered through BeanDefinitionRegistryPostProcessor not getting refreshed on Cloud Config changes

我有一个动态注册 bean 的 BeanDefinitionRegistryPostProcessor class。有时,正在注册的 bean 具有 Spring 云注释 @RefreshScope。 但是,当更改云配置环境时,不会刷新此类 beans。调试时,会触发适当的应用程序事件,但是不会重新实例化动态 bean。需要一些帮助。下面是我的代码:

TestDyna道具:

public class TestDynaProps {

    private String prop;

    private String value;

    public String getProp() {
        return prop;
    }

    public void setProp(String prop) {
        this.prop = prop;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("TestDynaProps [prop=").append(prop).append(", value=").append(value).append("]");
        return builder.toString();
    }

}

TestDynaPropConsumer:

@RefreshScope
public class TestDynaPropConsumer {

    private TestDynaProps props;

    public void setProps(TestDynaProps props) {
        this.props = props;
    }

    @PostConstruct
    public void init() {
        System.out.println("Init props : " + props);
    }

    public String getVal() {
        return props.getValue();
    }

}

BeanDefinitionRegistryPostProcessor:

public class PropertyBasedDynamicBeanDefinitionRegistrar implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {

    private ConfigurableEnvironment environment;

    private final Class<?> propertyConfigurationClass;

    private final String propertyBeanNamePrefix;

    private final String propertyKeysPropertyName;

    private Class<?> propertyConsumerBean;

    private String consumerBeanNamePrefix;

    private List<String> dynaBeans;

    public PropertyBasedDynamicBeanDefinitionRegistrar(Class<?> propertyConfigurationClass,
        String propertyBeanNamePrefix, String propertyKeysPropertyName) {
        this.propertyConfigurationClass = propertyConfigurationClass;
        this.propertyBeanNamePrefix = propertyBeanNamePrefix;
        this.propertyKeysPropertyName = propertyKeysPropertyName;
        dynaBeans = new ArrayList<>();
    }

    public void setPropertyConsumerBean(Class<?> propertyConsumerBean, String consumerBeanNamePrefix) {
        this.propertyConsumerBean = propertyConsumerBean;
        this.consumerBeanNamePrefix = consumerBeanNamePrefix;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = (ConfigurableEnvironment) environment;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefRegistry) throws BeansException {
        if (environment == null) {
            throw new BeanCreationException("Environment must be set to initialize dyna bean");
        }
        String[] keys = getPropertyKeys();
        Map<String, String> propertyKeyBeanNameMapping = new HashMap<>();
        for (String k : keys) {
            String trimmedKey = k.trim();
            String propBeanName = getPropertyBeanName(trimmedKey);
            registerPropertyBean(beanDefRegistry, trimmedKey, propBeanName);
            propertyKeyBeanNameMapping.put(trimmedKey, propBeanName);
        }
        if (propertyConsumerBean != null) {
            String beanPropertyFieldName = getConsumerBeanPropertyVariable();
            for (Map.Entry<String, String> prop : propertyKeyBeanNameMapping.entrySet()) {
                registerConsumerBean(beanDefRegistry, prop.getKey(), prop.getValue(), beanPropertyFieldName);
            }
        }
    }

    private void registerConsumerBean(BeanDefinitionRegistry beanDefRegistry, String trimmedKey, String propBeanName, String beanPropertyFieldName) {
        String consumerBeanName = getConsumerBeanName(trimmedKey);
        AbstractBeanDefinition consumerDefinition = preparePropertyConsumerBeanDefinition(propBeanName, beanPropertyFieldName);
        beanDefRegistry.registerBeanDefinition(consumerBeanName, consumerDefinition);
        dynaBeans.add(consumerBeanName);
    }

    private void registerPropertyBean(BeanDefinitionRegistry beanDefRegistry, String trimmedKey, String propBeanName) {
        AbstractBeanDefinition propertyBeanDefinition = preparePropertyBeanDefinition(trimmedKey);
        beanDefRegistry.registerBeanDefinition(propBeanName, propertyBeanDefinition);
        dynaBeans.add(propBeanName);
    }

    private String getConsumerBeanPropertyVariable() throws IllegalArgumentException {
        Field[] beanFields = propertyConsumerBean.getDeclaredFields();
        for (Field bField : beanFields) {
            if (bField.getType().equals(propertyConfigurationClass)) {
                return bField.getName();
            }
        }
        throw new BeanCreationException(String.format("Could not find property of type %s in bean class %s",
            propertyConfigurationClass.getName(), propertyConsumerBean.getName()));
    }

    private AbstractBeanDefinition preparePropertyBeanDefinition(String trimmedKey) {
        BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(PropertiesConfigurationFactory.class);
        bdb.addConstructorArgValue(propertyConfigurationClass);
        bdb.addPropertyValue("propertySources", environment.getPropertySources());
        bdb.addPropertyValue("conversionService", environment.getConversionService());
        bdb.addPropertyValue("targetName", trimmedKey);
        return bdb.getBeanDefinition();
    }

    private AbstractBeanDefinition preparePropertyConsumerBeanDefinition(String propBeanName, String beanPropertyFieldName) {
        BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(propertyConsumerBean);
        bdb.addPropertyReference(beanPropertyFieldName, propBeanName);
        return bdb.getBeanDefinition();
    }

    private String getPropertyBeanName(String trimmedKey) {
        return propertyBeanNamePrefix + trimmedKey.substring(0, 1).toUpperCase() + trimmedKey.substring(1);
    }

    private String getConsumerBeanName(String trimmedKey) {
        return consumerBeanNamePrefix + trimmedKey.substring(0, 1).toUpperCase() + trimmedKey.substring(1);
    }

    private String[] getPropertyKeys() {
        String keysProp = environment.getProperty(propertyKeysPropertyName);
        return keysProp.split(",");
    }

配置class:

@Configuration
public class DynaPropsConfig {

    @Bean
    public PropertyBasedDynamicBeanDefinitionRegistrar dynaRegistrar() {
        PropertyBasedDynamicBeanDefinitionRegistrar registrar = new PropertyBasedDynamicBeanDefinitionRegistrar(TestDynaProps.class, "testDynaProp", "dyna.props");
        registrar.setPropertyConsumerBean(TestDynaPropConsumer.class, "testDynaPropsConsumer");
        return registrar;
    }
}

Application.java

@SpringBootApplication
@EnableDiscoveryClient
@EnableScheduling
public class Application extends SpringBootServletInitializer {

    private static Class<Application> applicationClass = Application.class;

    public static void main(String[] args) {
        SpringApplication sa = new SpringApplication(applicationClass);             
        sa.run(args);
    }
}

还有,我的 bootstrap.properties:

spring.cloud.consul.enabled=true
spring.cloud.consul.config.enabled=true
spring.cloud.consul.config.format=PROPERTIES
spring.cloud.consul.config.watch.delay=15000
spring.cloud.discovery.client.health-indicator.enabled=false
spring.cloud.discovery.client.composite-indicator.enabled=false

application.properties

dyna.props=d1,d2

d1.prop=d1prop
d1.value=d1value
d2.prop=d2prop
d2.value=d2value

以下是一些猜测:

1) 可能 @RefreshScope 元数据没有传递到 bean 定义的元数据。调用 setScope()?

2) RefreshScope实际上是由https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java实现的,它本身实现了BeanDefinitionRegistryPostProcessor。也许这两个 post 处理器的顺序有问题。

只是猜测。

我们最终解决了这个问题,方法是使用 ByteBuddy 在建议的动态 bean 类 上附加 @RefreshScope 注释,然后使用 Bean 定义 Post 处理器将它们添加到 Spring 上下文。 Post 处理器被添加到 spring.factories 以便它在任何其他依赖于动态 bean 的 bean 之前加载。