美文网首页
Spring覆盖已初始化bean

Spring覆盖已初始化bean

作者: 神易风 | 来源:发表于2022-07-26 22:06 被阅读0次

    现在在配置类里面创建一个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;
        }
    

    相关文章

      网友评论

          本文标题:Spring覆盖已初始化bean

          本文链接:https://www.haomeiwen.com/subject/wuhdbrtx.html