Now create a bean date inside the configuration class

 @Configuration
public class DateConfig {

    @Bean("date")
    public Date  date(){
        return new Date();
    }
}

The time is not static, I want to get the current time, how should I override the bean already in the container. At first I thought of using org..cloud.context.scope.refresh.RefreshScope , but the Spring boot project did not use the Spring Cloud package. This didn't work, so I tried registerBean to dynamically register a bean with the same name, wondering if it could be overwritten Beans in the container, after all, the so-called container is just a Map, as long as the value on the Map is overwritten by the mechanism, dynamic refresh can be achieved.

 private ApplicationContext applicationContext;

    @GetMapping("setting/now")
    public void dkd(){
        GenericApplicationContext gac = (GenericApplicationContext)applicationContext;
        gac.registerBean("date",Date.class);
    }

When this request is executed, an error is reported directly, and a BeanDefinitionOverrideException is thrown, and the bean cannot be overridden. You can see the reason in DefaultListableBeanFactory.registerBeanDefinition

 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
                // 省略多余代码
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) { //对于已经存在bean
            if (!isAllowBeanDefinitionOverriding()) { //如果allowBeanDefinitionOverriding 这个值为false 这里就会抛出异常
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            }
            else if (existingDefinition.getRole() < beanDefinition.getRole()) { //这里是BeanDefinition  
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding user-defined bean definition for bean '" + beanName +
                            "' with a framework-generated bean definition: replacing [" +
                            existingDefinition + "] with [" + beanDefinition + "]");
                }
            }
            else if (!beanDefinition.equals(existingDefinition)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
     //省略。。

Then I found that this allowBeanDefinitionOverriding will initialize this value in SpringApplication when SpringBoot is just initialized, in SpringApplication.prepareContext

 private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        bootstrapContext.close(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
            ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
            if (beanFactory instanceof DefaultListableBeanFactory) {
                ((DefaultListableBeanFactory) beanFactory)
                        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); //设置到DefaultListableBeanFactory中
            }
        }
        if (this.lazyInitialization) { //开启懒加载配置
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

Then see how the configuration file value is set to SpringApplication.allowBeanDefinitionOverriding

 private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(bootstrapContext, environment);
        DefaultPropertiesPropertySource.moveToEnd(environment);
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                "Environment prefix cannot be set via properties.");
        bindToSpringApplication(environment); //将配置环境bind到属性中
        if (!this.isCustomEnvironment) {
            environment = convertEnvironment(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

    protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
                        //将配置文件绑定到当前属性上
                        //看起来就有ConfigurationProperties 那味了
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this)); 
        }
        catch (Exception ex) {//略}
    }

Add the following configuration in application.properties

spring.main.allow-bean-definition-overriding=true

Re-execute the HTTP request after restarting, no error is reported, and the date bean is re-fetched, and the time becomes the latest value.

experience

It is estimated that there may be some bean conflicts between different components in order to add this configuration, and the later initialization of bean components can override the components already created in Spring. If bean A has been initialized in Spring now, and it is successfully added to the container, then loading and then loading the Spring component also has a Class that inherits bean A, which needs to be added to the container. If there is no mechanism for the same override of beanName, the component will fail at initialization.
It is also worth noting that the registerBean method only deletes the bean cache in the container. How to inject the bean into the object property, this value will not change at this time, you need to manually call beanFactory.getBean("beanName"), Because the initialization will only be performed if the bean does not exist. If there is such a bean refresh scenario you can use @Lookup to generate a proxy method.

 @Lookup
    public Date initDate() { //这里会将容器内Date类型注入,每次调用方法,重新从容器获取一次
        return null;
    }

神易风
106 声望52 粉丝

alert("hello world")