关于spring:Spring覆盖已初始化bean

37次阅读

共计 5154 个字符,预计需要花费 13 分钟才能阅读完成。

当初在配置类外面创立一个 bean date

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

工夫并不是变化无穷的,我想要获取以后工夫呢,应该怎么笼罩曾经在容器内 bean。我一开始想到应用 org..cloud.context.scope.refresh.RefreshScope, 然而 Spring boot 我的项目并没有应用到 Spring Cloud 包,这个走不通,就试着registerBean 动静注册雷同名字 bean,想着能不能笼罩容器内 bean,毕竟所谓容器只不过是 Map 而已,只有通过机制笼罩掉 Map 上 value 就能够实现动静刷新了。

    private ApplicationContext applicationContext;

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

执行这个申请,间接报错了,抛出了一个 BeanDefinitionOverrideException 异样,bean 不能被笼罩。在 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);
        }
     // 省略。。

而后发现这个 allowBeanDefinitionOverriding 在 SpringBoot 刚初始化时,在 SpringApplication 会初始化这个值,在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);
    }

接着看下配置文件值如何设置到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) {// 略}
    }

在 application.properties 增加上面配置

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

重启后从新执行 HTTP 申请,没有报错了,从新获取 date 这个 bean,工夫也变成最新值了。

心得

增加多这一个配置预计为了兼容不同组件间可能存在一些 bean 抵触状况,前面初始化 bean 组件能够笼罩 Spring 外部曾经创立组件。如果当初 Spring 外部曾经初始化 bean A,并且胜利退出到容器中了,这时加载再加载 Spring 组件也有一个 Class 继承 bean A,这是须要增加到容器中。如果没有 beanName 雷同笼罩的机制,组件在初始化就会失败。
还有一点值得注意的,registerBean 这个办法只有在容器中删除这个 bean 缓存,如何曾经将 bean 注入到对象属性中,这时这个值不会变动的,须要手动调用 beanFactory.getBean(“beanName”),因为只有这个 bean 不存在时候才会执行初始化。如果有这种 bean 刷新场景能够应用 @Lookup 来生成一个代理办法。

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

正文完
 0