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

点击关注公众号,实用技术文章及时了解75ea5a6b8dc326df5d7f328fd40f00a7.png


来源:cnblogs.com/youzhibing/p/15354706.html

疑虑背景

疑虑描述

最近,在进行开发的过程中,发现之前的一个写法,类似如下

b855541035c505496e10da3e51af5ca5.png

以我的理解,@Configuration@Bean 会创建一个 userName 不为 null 的 UserManager 对象,而 @Component 也会创建一个 userName 为 null 的 UserManager 对象

那么我们在其他对象中注入 UserManager 对象时,到底注入的是哪个对象?

因为项目已经上线了很长一段时间了,所以这种写法没有编译报错,运行也没有出问题

后面去找同事了解下,实际是想让

83a47fa87637951e196b3d7f2de53fbb.png

生效,而实际也确实是它生效了

那么问题来了:Spring 容器中到底有几个 UserManager 类型的对象?

Spring Boot 版本

项目中用的 Spring Boot 版本是:2.0.3.RELEASE

对象的 scope 是默认值,也就是 singleton

结果验证

验证方式有很多,可以 debug 跟源码,看看 Spring 容器中到底有几个 UserManager 对象,也可以直接从 UserManager 构造方法下手,看看哪几个构造方法被调用,等等

我们从构造方法下手,看看 UserManager 到底实例化了几次

92cf6a7bb9d38b9786d8196642153f5e.gif

只有有参构造方法被调用了,无参构造方法岿然不动(根本没被调用)

果想了解的更深一点,可以读读:Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗

既然 UserManager 构造方法只被调用了一次,那么前面的问题:到底注入的是哪个对象

答案也就清晰了,没得选了呀,只能是 @Configuration@Bean 创建的 userName 不为 null 的 UserManager 对象

问题又来了:为什么不是 @Component 创建的 userName 为 null 的 UserManager 对象?

源码解析

@Configuration@Component 关系很紧密

ca490730839f4be9698ea7efb050dd43.png

所以@Configuration 能够被 component scan

其中 ConfigurationClassPostProcessor@Configuration 息息相关,其类继承结构图如下:

c923a3e2671ddef21f7cf806bf810b07.png

它实现了 BeanFactoryPostProcessor 接口和 PriorityOrdered 接口,关于 BeanFactoryPostProcessor,可以看看:

https://www.cnblogs.com/youzhibing/p/10559337.html

那么我们从 AbstractApplicationContext 的 refresh 方法调用的 invokeBeanFactoryPostProcessors(beanFactory)开始,来跟下源码

f56f56e419d59173d090c8fecbfebcad.gif

此时完成了 com.lee.qsl 包下的 component scancom.lee.qsl 包及子包下的 UserConfig 、 UserController 和 UserManager 都被扫描出来

注意,此刻@Bean 的处理还未开始, UserManager 是通过@Component 而被扫描出来的;此时 Spring 容器中 beanDefinitionMap 中的 UserManager 是这样的

2f2d7aa32aaff1f856cd3af962facfe3.png

接下来一步很重要,与我们想要的答案息息相关

20d9b3ff1c59fc655d02c0fe004d43ac.gif

4f8f1925dfc758600515f6b038340298.png

循环递归处理 UserConfig 、 UserController 和 UserManager ,把它们都封装成 ConfigurationClass ,递归扫描 BeanDefinition

循环完之后,我们来看看 configClasses

33c7cfb6b1292466e5860d4a5f3dd6da.gif

UserConfig bean 定义信息中 beanMethods 中有一个元素 [BeanMethod:name=userManager,declaringClass=com.lee.qsl.config.UserConfig]

然后我们接着往下走,来仔细看看答案出现的环节

e01fa3d67ef7fb68912f05b8f775aa77.gif

是不是有什么发现?@Component 修饰的 UserManager 定义直接被覆盖成了 @Configuration + @Bean 修饰的 UserManager 定义

Bean 定义类型也由 ScannedGenericBeanDefinition 替换成了 ConfigurationClassBeanDefinition

后续通过 BeanDefinition 创建实例的时候,创建的自然就是 @Configuration + @Bean 修饰的 UserManager ,也就是会反射调用 UserManager 的有参构造方法

自此,答案也就清楚了

Spring 其实给出了提示

2021-10-03 20:37:33.697  INFO 13600 --- [           
main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'userManager' with a different definition: replacing [Generic bean: class [com.lee.qsl.manager.UserManager]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\qsl-project\spring-boot-bean-component\target\classes\com\lee\qsl\manager\UserManager.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=userConfig; factoryMethodName=userManager; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/lee/qsl/config/UserConfig.class]]

只是日志级别是 info ,太不显眼了

Spring 升级优化

可能 Spring 团队意识到了 info 级别太不显眼的问题,或者说意识到了直接覆盖的处理方式不太合理

所以在 Spring 5.1.2.RELEASE (Spring Boot 则是 2.1.0.RELEASE )做出了优化处理

我们来具体看看

3eb3359f69f995b47ef53a30d1cf6821.gif

启动直接报错,Spring 也给出了提示

The bean 'userManager', defined in class path resource [com/lee/qsl/config/UserConfig.class], could not be registered. A bean with that name has already been defined in file [D:\qsl-project\spring-boot-bean-component\target\classes\com\lee\qsl\manager\UserManager.class] and overriding is disabled.

我们来跟下源码,主要看看与 Spring 5.0.7.RELEASE 的区别

3cb4e64c82fc2af1ae1a28eb19881513.gif

新增了配置项 allowBeanDefinitionOverriding 来控制是否允许 BeanDefinition 覆盖,默认情况下是不允许的

我们可以在配置文件中配置:spring.main.allow-bean-definition-overriding=true ,允许 BeanDefinition 覆盖

这种处理方式是更优的,将选择权交给开发人员,而不是自己偷偷的处理,已达到开发者想要的效果

总结

Spring 5.0.7.RELEASESpring Boot 2.0.3.RELEASE ) 支持@Configuration + @Bean@Component 同时作用于同一个类

启动时会给 info 级别的日志提示,同时会将@Configuration + @Bean 修饰的 BeanDefinition 覆盖掉@Component 修饰的 BeanDefinition

也许 Spring 团队意识到了上述处理不太合适,于是在 Spring 5.1.2.RELEASE 做出了优化处理

增加了配置项:allowBeanDefinitionOverriding ,将主动权交给了开发者,由开发者自己决定是否允许覆盖

补充

关于 allowBeanDefinitionOverriding ,前面讲的不对,后面特意去翻了下源码,补充如下

Spring 1.2 引进 DefaultListableBeanFactory 的时候就有了 private boolean allowBeanDefinitionOverriding = true;,默认是允许 BeanDefinition 覆盖

ae63d2bd584e61b73714fcc120f4fc49.png

Spring 4.1.2 引进了 isAllowBeanDefinitionOverriding()方法

9d42f977f9393739b9cefbc8b7ba7a1b.png

Spring 自始至终默认都是允许 BeanDefinition 覆盖的,变的是 Spring Boot , Spring Boot 2.1.0 之前没有覆盖 Spring 的 allowBeanDefinitionOverriding 默认值,仍是允许 BeanDefinition 覆盖的

Spring Boot 2.1.0 中 SpringApplication 定义了私有属性:allowBeanDefinitionOverriding

a658a8092835c7e9afb92c5268288eff.png

没有显示的指定值,那么默认值就是 false ,之后在 Spring Boot 启动过程中,会用此值覆盖掉 Spring 中的 allowBeanDefinitionOverriding 的默认值

3a7b1a7acf6b7d1765e63f9c162efb87.gif

关于 allowBeanDefinitionOverriding  ,我想大家应该已经清楚了

推荐

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

<think>嗯,用户想了解@CloudService注解的功能和使用方法。首先,我需要确认这个注解是否是Spring Cloud的一部分,或者是否是某个特定框架或云服务商提供的。根据提供的引用内容,用户之前接触过Spring Cloud的注解,比如@FeignClient、@Autowired、@Value等,但@CloudService并没有在引用中提到。这可能意味着这个注解不是Spring Cloud的核心注解,或者是用户自定义的,或者是第三方库提供的。 接下来,我应该检查常见的云服务相关的框架,比如阿里云的Spring Cloud Alibaba,或者腾讯云的Spring Cloud集成库。例如,阿里云的Spring Cloud Alibaba中有一些注解用于服务发现、配置管理等,但通常不叫@CloudService。或者可能用户提到的@CloudService是一个示例注解,用于教学或内部框架中。 由于引用中没有直接提到@CloudService,我需要假设这个注解可能属于某个特定的云服务框架,或者是用户项目中的自定义注解。在这种情况下,我应该建议用户查阅相关框架的官方文档,或者检查注解的源代码以了解其属性和用法。 另外,根据引用中的内容,用户可能对Spring Cloud的常见注解比较熟悉,比如@FeignClient用于声明Feign客户端,@Value用于注入配置值。如果@CloudService是似作用的注解,可能用于标记某个服务为云服务,或者集成特定的云功能,比如服务注册、配置管理、分布式跟踪等。 我还需要提醒用户,如果这是内部或第三方库的注解,正确的用法可能依赖于具体的上下文和配置。例如,可能需要配合其他注解或配置一起使用,或者需要设置特定的属性如服务名称、区域等。 最后,建议用户提供更多上下文或示例代码,以便更准确地解答。如果用户无法提供,引导他们查看官方文档或注解的源代码,通常会是最可靠的途径。</think>### @CloudService 注解功能解析 根据您提供的引用内容,目前已知的 Spring Cloud 相关注解(如 `@FeignClient`、`@Value`)主要用于服务调用、配置注入等功能。但**`@CloudService` 并非 Spring Cloud 官方标准注解**,可能是以下两种可能性: --- #### **可能性 1:第三方框架/云平台自定义注解** 某些云服务商(如阿里云、腾讯云)或框架会扩展自定义注解来简化云服务集成。 **假设功能可能包括:** 1. **服务注册发现** 自动将当前服务注册到云平台的注册中心(似 `@EnableDiscoveryClient` 的增强版)。 ```java @CloudService(serviceId = "order-service", region = "cn-east-1") public class OrderController { ... } ``` 2. **云资源配置绑定** 自动注入云数据库、消息队列等服务的客户端实例。 ```java @CloudService(resourceType = "OSS") private OssClient ossClient; ``` 3. **链路追踪/监控集成** 自动集成云平台的监控指标上报或分布式追踪功能。 --- #### **可能性 2:项目内部自定义注解** 可能是团队内部封装的注解,用于统一管理云服务相关配置。 **典型实现方式参考 `@FeignClient`[^3]:** 1. 定义注解属性: ```java @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CloudService { String endpoint() default ""; int timeout() default 5000; } ``` 2. 通过切面或处理器解析注解,生成云服务客户端代理。 --- ### 使用建议 1. **查阅框架文档** 若属于第三方框架(如 Spring Cloud Alibaba),需参考其官方文档确认注解属性。 2. **分析注解源码** 直接查看 `@CloudService` 的源码定义,观察其元注解(如 `@Component`)和属性字段。 3. **调试跟踪逻辑** 使用 IDE 调试功能,观察注解被处理时的行为(如是否生成 Bean 或发起网络调用)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值