问题起因
由于服务器资源紧张,想部署一台 ElasticSearch
给所有环境(开发环境、测试环境、生产环境...)使用。
因 ElasticSearch
中的索引概念对标 MySQL
中的数据库概念,所以想通过动态索引,进行区分不同环境之间的数据。比如文章,开发环境使用 local-article
,测试环境使用 beta-article
等等...
然后在使用 @Document
结合 SpEL
表达式动态创建索引时,报找不到 bean
👇
Could not resolve bean reference against BeanFactory
Consider defining a bean named 'xxx' in your configuration.
场景复现
使用动态索引
在实体类标记 @Document
注解,需要指定 indexName
参数,该参数可通过 SpEL 表达式进行编写。以及 createIndex
参数默认会自动创建索引。我在这里使用 #{@baseConfig.active}-article
进行编写。
INFO
SpEL
表达式中,通过#{@xxx}
or#{@'xxx'}
来引用bean
,后续如果需要引用属性,可在通过.字段
的方式实现。
在项目启动后,看到索引确实有动态创建。
启动报错
后续笔者想到,BaseConfig
可以删除掉,因为之前自己写了一个外部依赖,也可以获取环境变量,故将 SpEL
表达式中的 @baseConfig
改成 @systemConfig
。
两个类差异如下:
SystemConfig | BaseConfig |
---|---|
外部 spring.factories 文件 EnableAutoConfiguration 自动装配的 bean | 本工程下 ComponentScan 自动扫描包装配的 bean |
bean 名为 systeonConfig | bean 名为 baseConfig |
后续执行启动,结果报错 👇
排查错误
笔者通过排除法进行排查错误,先从最基础的排错顺序依次排查。
是否没有装配该 bean ?
通过构造器注入 bean
,调用接口 debug 排查时,确实有注入该 bean
。
也就意味着该 bean
是有自动装配上的。
是否类名是关键词/敏感词,导致错误?
通过修改成其他类名,也一样是报错的情况,该步骤就不细讲。
排查 bean 创建过程
通过错误日志得知,在创建最上层 ConsoleArticleController
bean
时,就报错了,那么就追踪该 bean
的创建过程。
Spring
会调用org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.Strin
方法,进行创建bean
。
- 往内部接着
debug
,会进入org.springframework.beans.factory.support.AbstractBeanFactory#markBeanAsCreated
方法,bean
如果没有创建的话,会进行创建。
- 在这里注意到
org.springframework.beans.factory.support.AbstractBeanFactory#mergedBeanDefinitions
字段,该Map
记录着bean
名到具体bean
类。
通过窥探得知:
key
是bean
的名称,value
是具体的类。- 引用
bean
的话,是通过key
获取的。 key
的由来,如果是ComponentScan
扫描注册的bean
,那么只有一个类名,如果是通过EnableAutoConfiguration
自动装配的bean
那么就是全限定类名。
结合之前的情况,baseConfig
获取的到 bean
,systemConfig
获取不到 bean
,会不会是这个 key
的问题。
故就是 SpEL
表达式引用 bean
时,写的是类名,而不是全限定类名,导致找不到该 bean
。
问题解决
将该 bean
的类名改为全限定类名,后续启动验证,没有报错,并且成功创建动态索引。
结语
初期遇到该问题时困扰了一下午,一直在反复排查该 bean
是否有成功装配。后续通过深入 bean
创建过程以及结合 SpEL
表达式引用才定位到该问题的解决方案,收获颇丰。😊
Q.E.D.