@Bean 是如何将bean注入到ioc容器中去的

缘起

@Configuration 注解和 @Bean 注解我们在进入 Java Config 时代之后就替代了 xml 和 \ 标签了. 那么@Bean 注解为什么能向ioc容器中注入bean呢? 带着这种好奇心,探究了一下 @Bean注入bean的原理

分析

本文以 DEMO【1】为切入点. 首先看看AnnotationConfigApplicationContext的构造器源码

1
2
3
4
5
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}

​ 源码1

在源码的第二行和第三行都会触发

org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(String, BeanDefinition) 方法

进行bean定义的注册——即往beanDefinitionNames中添加beanName.

, 但是我们关心的 @Bean方法注册bean的定义在refresh中. 这一点在【2】中也谈到了.

调用栈是

1
2
refresh中的invokeBeanFactoryPostProcessors
-->ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod -->DefaultListableBeanFactory.registerBeanDefinition

我们来看看DefaultListableBeanFactory.registerBeanDefinition的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();

// Do we need to mark the bean as skipped by its condition?
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) { // 这里做的就是根据条件@Conditional(例如@Profile的匹配性)跳过. 在【2】中也分析过了.
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}

// Consider name and any aliases
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
...
this.registry.registerBeanDefinition(beanName, beanDefToRegister);

其中第17行就拿到了bean的name(即 @Bean的name属性), 这个就是要注册的bean的id. 所谓注册就是往往DefaultListableBeanFactory的beanDefinitionNames中添加beanName即可,这些在【2】中也谈到了.

而是怎么拿到bean的name的呢? 本文最关键的代码来了——第15行. 注意到了 Bean.Class入参. 这个就是 @Bean注解. 这里在从metadata中拿 @Bean注解的元信息!!!

而且从第17行,我们知道,如果names是空(即@Bean上没写 name属性),则直接拿方法名做beanName,这些都是我们平时用的时候的做法,现在源码看到实现了哦!

最后将 @Bean 注解的bean的id通过第19行——调用DefaultListableBeanFactory.registerBeanDefinition方法加入到beanDefinitionNames中去. 完成bean定义的注册.

后面和【2】中的最后说的一样了,通过for循环初始化bean再加入到ioc容器中去

整个@Bean的工作原理至此弄清楚了.

参考

【1】https://yfsyfs.github.io/2019/06/13/spring-AOP-%E6%B3%A8%E8%A7%A3%E5%8E%9F%E7%90%86/

【2】https://yfsyfs.github.io/2019/06/13/Profile-%E5%8E%9F%E7%90%86/

DEMO

【1】https://github.com/yfsyfs/backend/tree/master/spring-annotation