当前位置: 首页 > 后端技术 > Java

腾讯二面:@Bean和@Component用在同一个类上会怎样?

时间:2023-04-02 00:15:18 Java

疑惑背景疑惑描述最近在开发过程中,发现之前的一种写法,类似如下。在我的理解中,@Configuration加上@Bean会创建一个userName不为null的UserManager对象,@Component也会创建一个userName为null的UserManager对象。那么我们在其他对象中注入UserManager对象时,注入的是哪个对象呢?因为项目已经上线很久了,所以这种写法没有编译报错,运行也没有问题。稍后我会和我的同事一起了解这件事。我其实是想让它生效,事实上它确实生效了。那么问题来了:Spring容器中到底有多少个UserManager类型的对象?SpringBoot版本项目中使用的SpringBoot版本为:2.0.3。RELEASE对象的scope为默认值,即单例结果校验有多种校验方式。可以通过调试和源码查看Spring容器中有多少个UserManager对象,也可以直接从UserManager的构造方法入手,看看调用了哪些构造方法等等。我们先从构造方法开始,看看UserManager被实例化了多少次。只调用带参数的构造方法,不带参数的构造方法原地不动(根本不调用)由于UserManager的构造函数只调用了一次,所以前面的问题:注入哪个对象就很清楚了,没得选,只有@Configuration加上@Bean创建的userName不为null的UserManager对象又来了:为什么不通过@Component创建一个userName为null的UserManager对象呢?源码分析@Configuration与@Component密切相关,所以@Configuration可以按组件来扫描。ConfigurationClassPostProcessor与@Configuration密切相关。其类继承结构图如下:实现了BeanFactoryPostProcessor接口和PriorityOrdered接口。关于BeanFactoryPostProcessor,我们从Abstrac说起tApplicationContext的refresh方法调用的invokeBeanFactoryPostProcessors(beanFactory)开始跟源码。此时com.lee.qsl包下的组件扫描完成,com.lee.qsl包下的UserConfig、UserController、UserManager和子包都扫描完成注意@Bean的处理还没有启动,通过@Component扫描UserManager;这时Spring容器中的beanDefinitionMap中的UserManager是这样的。下一步非常重要,它与我们想要的答案密切相关。循环递归处理UserConfig、UserController和UserManager,封装成ConfigurationClass,递归扫描BeanDefinition循环,我们看一下configClassesUserConfigbean定义信息中的beanMethods元素[BeanMethod:name=userManager,declaringClass=com.lee.qsl.config.UserConfig]那我们继续往下看,在答案出现的那个链接里有没有发现什么?@Component修饰的UserManager定义直接被@Configuration+@Bean修饰的UserManager覆盖。Bean定义类型也被ScannedGenericBeanDefinition替换为ConfigurationClassBeanDefinition。通过BeanDefinition创建实例时,创建的实例自然是@Configuration+@Bean修饰的UserManager,即会反射调用UserManager的参数化构造方法。从那时起,答案就很清楚了。spring居然给了个提示2021-10-0320:37:33.697INFO13600---[只是日志级别是info,太不起眼spring升级优化可能spring团队意识到info层级太不显眼,或者意识到直接覆盖的方式不合理,于是在Spring5.1.2.RELEASE(SpringBoot为2.1.0.RELEASE)中进行了优化。具体看启动时的直接报错,Spring也提示Thebean'userManager',definedinclasspathresource[com/lee/qsl/config/UserConfig.class],couldnotberegistered。具有该名称的bean已经在文件[D:\qsl-project\spring-boot-bean-component\target\classes\com\lee\qsl\manager\UserManager.class]中定义并且禁用了覆盖。下面跟着源码,主要看一下和Spring5.0.7.RELEASE的区别新增了一个配置项allowBeanDefinitionOverriding,控制是否允许BeanDefinition覆盖。默认情况下,这是不允许的。我们可以在配置文件中配置:spring.main.allow-bean-definition-overriding=true最好让BeanDefinition覆盖这个处理方法。最好把选择权交给开发者,而不是自己偷偷处理。达到了开发者想要的效果。总结Spring5.0.7.RELEASE(SpringBoot2.0.3.RELEASE)支持@Configuration+@Bean和@Component同时作用于同一个类。启动时会给出info级别的日志提示,同时@Configuration+@Bean修饰的BeanDefinition会被@Component修饰的BeanDefinition覆盖。可能Spring团队意识到上面的处理不合适,所以在Spring5.1.2.RELEASE中,增加了优化处理,添加配置项:allowBeanDefinitionOverriding,主动权交给开发者,由开发者决定是否允许覆盖。关于allowBeanDefinitionOverriding,前面说的不对,于是去源码里加了下面的。Spring1.2引入DefaultListableBeanFactory时,有一个privatebooleanallowBeanDefinitionOverriding=true;,默认是允许BeanDefinition覆盖Spring4.1.2。引入了isAllowBeanDefinitionOverriding()方法。Spring一直允许默认覆盖BeanDefinition。它已更改为SpringBoot。SpringBoot2.1.0之前没有覆盖Spring的allowBeanDefinitionOverriding的默认值,SpringBoot2.1.0中允许BeanDefinition覆盖的还是SpringApplication定义了一个私有属性:allowBeanDefinitionOverriding没有指定值显示,那么默认值为false,然后在SpringBoot启动过程中,这个值会用来覆盖Spring中默认的allowBeanDefinitionOverridingallowBeanDefinitionOverriding的值,我想大家应该已经很清楚了。