First, through the configuration class, create a bean of type java.util.Date.

1
2
3
4
5
6
7
8
@Configuration
public class DateConfig {

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

Time is not constant, I want to get the current time object in the form of a bean, 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 does not use the Spring Cloud package, this does not work, so I tried registerBean to dynamically register the same name bean. After all, the so-called container is just a Map, as long as the key by the same name overrides the value in the Map can be dynamically refreshed.

1
2
3
4
5
6
7
    private ApplicationContext applicationContext;

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

When I request this endpoint, a BeanDefinitionOverrideException exception is thrown and the bean cannot be overridden.

You can see the reason for this in DefaultListableBeanFactory.registerBeanDefinition.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
                // ...
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) { 
            // If the value of allowBeanDefinitionOverriding is false for the case where the bean already exists, an exception will be thrown here.
            if (!isAllowBeanDefinitionOverriding()) { 
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            }
            else if (existingDefinition.getRole() < beanDefinition.getRole()) { 
                // 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);
        }
     //...

This value is initialized in the SpringApplication.prepareContext method during SpringBoot initialization.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    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)
                        // Initialize the property here.
                        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); 
            }
        }
        if (this.lazyInitialization) {  // Enable lazy initialization.
            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);
    }

Next, look at how the values in the configuration file are set to the SpringApplication.allowBeanDefinitionOverriding property.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    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.");
        
        // Binding Properties
        bindToSpringApplication(environment); 
        if (!this.isCustomEnvironment) {
            environment = convertEnvironment(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

    protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
                        //bind the configuration file to the current property
                        //looks like ConfigurationProperties
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this)); 
        }
        catch (Exception ex) {//...}
    }

Add the following configuration to application.properties.

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

After restarting the application and re-requesting the above endpoint, everything is OK and no exceptions are thrown.

Retrieve the bean Date from the IOC, the time also becomes the latest value.

The allow-bean-definition-overriding configuration item is estimated to exist for compatibility with some bean conflicts that may exist between different components. The later initialization of a bean component can override a component already created inside Spring. If now Spring has initialized bean A internally and successfully added it to the container, then the Spring component that is loaded again also has a Class that inherits from bean A, which needs to be added to the container. If there is no mechanism to override based on the same bean name, the component will fail in initialization.

It is also worth noting that the method registerBean only removes the cache of the bean from the container. For beans that have been injected into object properties, their values will not change and you need to manually call beanFactory.getBean(“beanName”) to get the latest bean, because initialization will only be performed when the bean does not exist. If you encounter this scenario where you need a bean refresh, then you can generate a proxy method by annotating the @Lookup annotation.

1
2
3
4
    @Lookup
    public Date initDate() {
        return null;
    }

Reference https://segmentfault.com/a/1190000042232473