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

时间:2017-01-10 09:30:52

标签: spring-boot spring-cloud-config spring-cloud-consul

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

TestDynaProps:

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(",");
    }

Config类:

@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

2 个答案:

答案 0 :(得分:0)

以下是一些猜测:

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实现的,It'll work in most browsers本身实现了BeanDefinitionRegistryPostProcessor。也许这两个后处理器的排序是个问题。

猜猜。

答案 1 :(得分:0)

我们最终通过使用ByteBuddy在建议的动态bean类上附加@RefreshScope注释然后使用Bean Definition Post Processor将它们添加到Spring Context来解决这个问题。 后处理器被添加到spring.factories,以便在任何其他动态bean依赖bean之前加载。