今天,有个新来的同事问了我一个关于spring中bean的别名问题,他说如果一个bean的别名跟其他bean的名字一样,那根据名字获取的对象到底是哪一个?碰到这种问题,我们最好就是自己写个demo测试一下,demo程序如下:
@Configuration
public class AppleConfig {
@Bean
public Apple redApple() {
return new Apple("红苹果");
}
@Bean(name = "blackApple")
public Apple whiteApple() {
return new Apple("白苹果");
}
@Bean
public Apple blackApple() {
return new Apple("黑苹果");
}
}
按照上述demo的配置,黑苹果对象名字应该叫blackApple,虽然白苹果对象的名字叫whiteApple,但是它有一个blackApple的别名,那如果我通过容器获取名字是blackApplede对象,拿出来的是白苹果还是黑苹果呢?
答案是白苹果。
首先我们要知道,容器中可以通过applicationContext.getBean(beanName)方法获取bean,那我们首先看一下这个方法,这个方法定义在AbstractApplicationContext类中
@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
通过该方法可以看出,这个方法最终是委托给AbstractBeanFactory的getBean(beanName)方法执行
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
getBean方法又会调用doGetBean方法,进入doGetBean方法就可以看到获取bean的核心逻辑
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
今天我们不是研究容器获取bean的完整过程,只是看看别名对于获取对象的影响,那主要的逻辑就是在tranformBeanName方法中(我将方法的注释稍作翻译)
/**
* Return the bean name, stripping out the factory dereference prefix if necessary,
* and resolving aliases to canonical names.
* @param name the user-specified name
* @return the transformed bean name
* 返回bean的名字,如果名字中含有工厂对象的前缀,去除前缀,并且如果是别名,转换成真正的bean名字
*/
protected String transformedBeanName(String name) {
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
BeanFactoryUtils.transformedBeanName(name)方法的作用,如果传入的名字带有"&"前缀,去除该前缀,在此我们不过多赘述。
进入canonicalName(beanName)方法,可知该方法是在SimpleAliasRegistry类中定义,看类的名字可知这是别名注册中心管理类,所有的别名都是由这个类管理。
/**
* Determine the raw name, resolving aliases to canonical names.
* @param name the user-specified name
* @return the transformed name
* 方法作用:解析别名转换成真正的bean名字
*/
public String canonicalName(String name) {
String canonicalName = name;
// Handle aliasing...
String resolvedName;
do {
resolvedName = this.aliasMap.get(canonicalName);
if (resolvedName != null) {
canonicalName = resolvedName;
}
}
while (resolvedName != null);
return canonicalName;
}
所有的别名是放在aliasMap中,这个map以别名作为key,bean真正的名字作为value,通过这种数据结构可以推测出一个bean可以有多个别名,这与我们平时了解的是一致的。
现在分析我们的demo例子,如果要获取的bean名字是blackApple,即这边的name=blackApple,因为whiteApple的别名是blackApple,在容器启动的时候已经注册到了这个SimpleAliasRegistry中,所以aliasMap中有blackApple=whiteApple的键值对,那么经过第一轮do循环,resolvedName=whiteApple,canonicalName=whiteApple,由于此时while条件成立,会进入第二次do循环,aliasMap并没有键为whiteApple的键值对存在,所以resolvedName=null,canonicalName=whiteApple保持不变,while条件判断不成立跳出循环,返回canonicalName的值为whiteApple。
最终会根据名字whiteApple去单例对象池中取出对象并返回,具体的获取步骤这边不做赘述。
经过以上分析,可以知道最终返回的是白苹果对象,我们可以测试一下
private ApplicationContext ac;
@GetMapping("/testAppleName")
public String testAppleName() {
return ((Apple) ac.getBean("blackApple")).getName();
}
调用该接口,页面展示白苹果,说明我们的分析正确