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

当在同一个类上使用@Bean和@Component时会发生什么?

时间:2023-04-01 16:50:11 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,可以看:https://www.cnblogs.com/youzh...然后我们从AbstractApplicationContext的refresh方法调用的invokeBeanFactoryPostProcessors(beanFactory)开始跟随源码,完成com.lee.qsl包下的组件扫描,com.lee.qslpackage和子包下的UserConfig、UserController、UserManager都扫描出来了。注意@Bean的处理还没有开始,通过@Component扫描出UserManager;此时Spring容器中的beanDefinitionMap中的UserManager如下一步非常重要,与我们想要的答案息息相关。递归处理UserConfig、UserController和UserManager,封装成ConfigurationClass,递归扫描BeanDefinition。循环结束后,我们再看一下configesUserConfigbean定义信息中的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---[main]o.s.b.f.s.DefaultListableBeanFactory:用不同的定义覆盖bean'userManager'的bean定义:替换[Genericbean:class[com.lee.qsl.manager.UserManager];0;依赖检查=0;autowireCandidate=真;初级=假;工厂BeanName=null;工厂方法名=空;初始化方法名=空;destroyMethodName=null;在文件[D:\qsl-project\spring-boot-bean-component\target\classes\com\lee\qsl\manager\UserManager.class]]中定义[Rootbean:class[null];范围=;摘要=假;懒惰初始化=假;自动接线模式=3;依赖检查=0;autowireCandidate=真;初级=假;factoryBeanName=用户配置;factoryMethodName=用户管理器;初始化方法名=空;destroyMethodName=(推断);定义在类路径资源[com/lee/qsl/config/UserConfig.class]]但是日志级别是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覆盖。默认情况下,这是不允许的。我们可以在配置文件中的Configure中设置: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,把主动权交给了Developers,由开发者决定是否允许覆盖或不添加关于allowBeanDefinitionOverriding,如前所述不是,后来查看了源码,添加了如下内容。Spring1.2引入DefaultListableBeanFactory时,有privatebooleanallowBeanDefinitionOverriding=true;,默认是允许BeanDefinition覆盖Spring4.1.2。isAllowBeanDefinitionOverriding()方法的引入Spring默认总是允许BeanDefinition覆盖,改变的是SpringBoot,SpringBoot2.1.0没有覆盖SpringBoot2.1.0之前Spring的allowBeanDefinitionOverriding的默认值,仍然允许BeanDefinition覆盖SpringApplication定义一个私有属性:allowBeanDefinitionOverriding没有指定值显示,那么默认值为false,然后在SpringBoot启动过程中,会使用这个值来覆盖Spring中allowBeanDefinitionOverriding的默认值。关于allowBeanDefinitionOverriding,我想大家应该已经知道了