现象
使用@Bean注解,给出bean多个名字时如
@Bean(name = {"aliasTestService", "aliasTestServiceDemo"})时,只有第一个名字才是bean的名字,其余的是别名。
而当我们以map<String,类接口名>注入时,别名不会存在与map的keyset之中。
现象场景
在动态切数据源情形下,用org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource实现。有时候我们提供给其他组的jar包,包含一些业务逻辑,需要注入接入方一个数据源datasource实例。
代码语言:javascript复制org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#setTargetDataSources
代码语言:javascript复制public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
AbstractRoutingDataSource需要注入所有数据源的实例:
代码语言:javascript复制Map<Object, Object> targetDataSources
为了减少数据库的连接,业务方的数据源名称已定性,需要别名我们jar包中需要的数据源名称,但别名加上之后,我们jar包中的数据源路由时就会找不到。为了临时解决,只能实例化一个重复连接,bean名字是jar包需要的名字。
原因
看下spring的几个比较重要的处理点。
- 获取bean名称,放入容器时
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
代码语言:javascript复制// Consider name and any aliases
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
// Register aliases even when overridden
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
代码语言:javascript复制 this.registry.registerBeanDefinition(beanName, beanDefToRegister);
@Bean name数组的第一个为beanName,其他的为aliases name,注入容器以beanName为准。
- 以类型获取bean实例过程
org.springframework.beans.factory.support.DefaultListableBeanFactory#doGetBeanNamesForType
代码语言:javascript复制// Check all bean definitions.
for (String beanName : this.beanDefinitionNames) {
// Only consider bean as eligible if the bean name is not defined as alias for some other bean.
if (!isAlias(beanName)) {
.....
}
}
代码语言:javascript复制org.springframework.core.SimpleAliasRegistry#isAlias
代码语言:javascript复制@Override
public boolean isAlias(String name) {
return this.aliasMap.containsKey(name);
}
当已类型注入时,!isAlias(beanName)即别名不会被处理。以map<String,Object>注入,map的keys不会存在别名。
结论
bean有别名时,以map<String,Object>注入,map的keys不会存在别名。
如何解决
若我们必须以map<String,Object>注入,别名也要存在其中,动态数据源切换才能成功,我们必须怎么做?
示例:
代码语言:javascript复制@Configuration
public class AliasTestConfiguration {
@Bean(name = {"aliasTestService", "aliasTestServiceDemo"})
public AliasTestService aliasTestService1() {
return new AliasTestServiceImpl();
}
@Bean
public AliasTestService aliasTestService2() {
return new AliasTestServiceImpl2();
}
@Bean
public AutowireTest autowireTest(Map<String, AliasTestService> aliasTestServiceMap, ApplicationContext context) {
Map<String, AliasTestService> serviceHashMap = new HashMap<>(aliasTestServiceMap);
for (Map.Entry<String, AliasTestService> serviceEntry : aliasTestServiceMap.entrySet()) {
String beanName = serviceEntry.getKey();
AliasTestService service = serviceEntry.getValue();
String[] aliases = context.getAliases(beanName);
for (String alias : aliases) {
serviceHashMap.put(alias, service);
}
}
return new AutowireTest(serviceHashMap);
}
}
重新把丢掉的别名找回来,放入map中。
博文涉及到的Spring版本
代码语言:javascript复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>