聊透spring @Configuration配置类
本章节我们来探索Spring中一个常用的注解@Configuration
。我们先来了解一下该注解的作用是:用来定义当前类为配置类
。
那啥是配置类啊,有啥用啊。这个我们得结合实际使用场景来说,通常情况下。加了@Configuration
的配置类内部,都会包含一个或多个@Bean注解的方法
。
为了简化定义,在后续我们称@Bean注解的方法为工厂方法。
配置类的奥秘就在这里,Spring会保证多次调用@Bean标注的工厂方法,不会重复产生新的对象,始终是同一个
,这也贯彻了Spring的单例哲学。
多次调用创建方法,产生的竟然是同一个对象,这貌似违背了编程的基础原理。怎么可能😱,一定是Spring做了什么,那就跟随着贰师兄的脚步一起解开@Configuration
的神秘面纱吧。
这里大家很容易产生一个误解,认为只有在加了@Configuration的配置类中,使用@Bean,才能将自定义创建的对象放入Spring容器中。其实不然,在Spring中:万物皆为配置类。
在任何能够被Spring管理的类
(比如加了@Component的类)中,定义@Bean方法,都能将自定义对象放入到Spring容器,@Bean本身的能力和@Configuration无关哦
。
1. @Configuration的作用
我们先通过一个简单的案例,看一下@Configuration的作用。
需要特别说明的是:
@Configuration继承了@Component
,这意味着@Configuration拥有@Component的全部功能,这也正是只加@Configuration,也能被Spring扫描并处理的原因。
- 情况一:被@Component标识
@Component
public class MarkedByComponent {
@Bean
public A a(){
return new A();
}
@Bean
public B b(){
a();
return new B();
}
}
// 输出信息:
// A created...
// A created...
// B created...
- 情况二:被@Configuration标识
@Configuration
public class MarkedByConfiguration {
@Bean
public A a(){
return new A();
}
@Bean
public B b(){
a();
return new B();
}
}
// 输出信息:
// A created...
// B created...
- 结论
通过上述输出我们发现:在多次调用a()的情况下,被@Component标识的类,A会被创建多次。而被@Configuration标识的配置类,A只会被创建一次。此时我们可以大胆猜测:
在@Configuration标识的配置类中,重复调用@Bean标识的工厂方法,Spring会对创建的对象进行缓存。仅在缓存中不存在时,才会通过工厂方法创建对象。后续重复调用工厂方法创建对象,先去缓存中找,不直接创建对象
。
这里小伙伴可能有个疑惑,a()只在b()被调用一次,为什么说多次呢,其实除了在b()中声明式调用,由于a()被@Bean标识,Spring也会主动调用一次的哦。
2. @Configuration的原理分析
上一章节,我们通过一个简单的案例,了解@Configuration的作用:使@Bean标识的工厂方法,不会重复创建bean对象
。同时也大胆猜测了,Spring对@Configuration标识的配置类,做了缓存处理,从而让@Bean工厂方法,有了"幂等的能力"
。本章节我们一起探索一下,这个增强的缓存逻辑,是怎么做到的?
要对一个类的功能进行增强。代理
这个词是不是已经在脑海中呼之欲出了,是的,就是代理。Spring对@Configuration标识的类做了代理,从而进行功能的增强。
当然,现在我们离直接分析代理功能还有点遥远,毕竟步子不能迈的太大,不然容易扯着蛋。一步步来,首先我们先看看Spring是如何解析@Configuration注解的,毕竟需要先找出来,才能代理增强嘛。
2.1 @Configuration解析
对@Configuration的解析过程是在spring扫描类的时候进行的
。这里需要介绍一下,Spring把加了@Configuration注解的称为全配置类,其他的称为半配置类,两者判别标准是:是否加了@Configuration注解。
// ConfigurationClassParser.java
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
//... 省略部分非相关代码
// 解析是否加了@ComponentScan, 并进行扫描
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// 1: 解析ComponentScan配置信息,完成扫描(扫描出来的类在beanDefinitionMap中)
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 2: 遍历扫描出来的类,检查是不是配置类(配置类需要继续递归解析)
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
// 重点:判断类是不是配置类, 并标注类属性信息(是否为全配置类、@Order值等)
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 3 如果扫描出来的类,是配置类,还需要递归处理扫描出来的配置类
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
//... 省略部分非相关代码
}
//ConfigurationClassUtils.java
// 全配置类标记值
public static final String CONFIGURATION_CLASS_FULL = "full";
// 半配置类标记值
public static final String CONFIGURATION_CLASS_LITE = "lite";
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
//... 省略部分非相关代码
// 2.1 从注解信息中, 获取@Configuration的注解信息(如存在,标记为为全配置类)
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 有@Component,@ComponentScan,@Import,@ImportResource注解,被标记成半配置类
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
//... 省略部分非相关代码
}
我们梳理一下这部分代码的逻辑:
- 获取配置类上
@ComponentScan(basePackages="xxx")
注解,交给组件componentScanParser
解析,该组件会扫描出对应包路径下需要Spring处理的类,并封装成BeanDefinition
,同时返回扫描的类定义信息。 - 遍历扫描出的类,检查是否为配置类,配置类需要继续解析(配置类可能有需要处理的Spring功能)。
- 在判断是否为配置类时,也会给类打上标签,加了@Configuration的标记为
全配置类
,其他的标记为半配置类
。标识的方式就是在BeanDefinition
的attributes
属性中加入XX.configurationClass:full
标识。
判断是否为配置类时,不仅仅只是加了
@Configuration
的为配置类,加了@Component
,@ComponentScan
,@Import
,@ImportResource
等注解的,也是配置类,只是为半配置类而已。上述我们说@Configuration的作用是标记为配置类,这里看其实是不准确的,准确说应该是标记为全配置类。但是这是更内部的逻辑,通常来说,@Configuration标记的就是配置类,其他标记的为普通类。
总结一下,在扫描阶段,Spring会对扫描出来的类进行全配置类还是半配置类的标识
。当然这里也仅仅是标识出来,并没有使用,这是在给后面生成代理对象做准备。
这里我们不得不吐槽一下,作为业界标杆的Spring,在方法命名上也如此混乱,在checkConfigurationClassCandidate()这个表明check动作的方法里,做了很多BeanDefinition属性解析赋值的操作,简直是在混淆视听啊有没有。看来命名确实是编程界最大的难题啊。
2.2 全配置类增强
在上一章节,我们分析了Spring对标注了@Configuration配置类的查找和解析过程,总结起来就是:判断类上是否有@Configuration注解,有就是全配置类,没有就是半配置类。对于全配置的增强,我们也反复提到过,是借助代理来实现的。具体的的调用逻辑我们一起来看一下。
//ConfigurationClassPostProcessor.java
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//...省略部分代码
// 对全配置类进行增强
this.enhanceConfigurationClasses(beanFactory);
}
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
// 1: 查找需要增强的配置类
// 遍历已经注册多的beanName
for (String beanName : beanFactory.getBeanDefinitionNames()) {
// ...省略部分代码
// 1.2 查找全配置类,放入configBeanDefs
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
// ...省略部分代码
// 将符合条件的,放入configBeanDefs中
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
// 2: 对全配置类进行增强
// 2.1 创建一个配置类的增强器
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
Class<?> configClass = beanDef.getBeanClass();
// 2.2 对类进行增强,使用cglib单例
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
// 2.3 修改BeanClass为加强类,这里之所以不使用加强对象,是因为还需要将加强类交给spring实例化
beanDef.setBeanClass(enhancedClass);
}
}
}
我们梳理一下这部分代码的逻辑:
- 查找需要增强的配置类,这里直接找出类
BeanDefinition
的attributes
属性中XX.configurationClass
标识值为full
的即可。 - 对配置类生成代理,进行功能增强。
- 修改
BeanDefinition
的beanClass属性为代理类,后续Spring在产生实例时,使用的就是代理类了。
这里有个重点,就是生成代理类,Spring采用的是CGLIB增强的方式,下面我们顺藤摸瓜,看看Spring是怎么操作的。
2.3 生成代理类
// ConfigurationClassEnhancer.java
// 回调器列表
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
// 回调过滤器
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
//为原始类生成代理类
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
// 1: newEnhancer() 生成生成CGLIB实例
// createClass() 生成代理类
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
// 2: 返回增强后的类
return enhancedClass;
}
// 生成CGLIB实例
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
// 设置接口为EnhancedConfiguration
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
// 给代理类生成一个$$beanFactory字段
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
// 设置回调过滤器为EnhancedConfiguration,能根据传入
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
// 生成代理类
private Class<?> createClass(Enhancer enhancer) {
Class<?> subclass = enhancer.createClass();
// 增加拦截器
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
return subclass;
}
我们直接来看代码,这里其实大量运用了CGLIB相关的知识,感兴趣的小伙伴可以自行百度补课,不补也没关系,这里会带大家简单了解一下:
- 先是创建了一个增强器
Enhancer
,为增强器设置了相关属性,我们看一下核心属性:- superclass: 父类,也就是被代理类,CGLIB的原理是为被代理类生成子类,从而实现功能增强。
- interfaces: 实现的接口,这里硬性指定为
EnhancedConfiguration
。(后续会使用该标识) - namingPolicy:设置代理类的名称策略,默认为
BySpringCGLIB
,这也是Spring生成的代理类类型包含xxBySpringCGLIB的原因。 - strategy:生成策略,这是设置的BeanFactoryAwareGeneratorStrategy,默认逻辑是给代理类动态增加
$$beanFactory
字段(后续会使用)。 - callbackFilter:回调过滤器,在CGLib回调时可以设置不同方法执行不同的回调逻辑,或者根本不执行回调。回调过滤器的功能就是对执行方法选择合适的拦截器。这里一定要区分清楚拦截器和回调过滤器的功能:
- 拦截器
Callback
:在调用目标方法时,CGLib会调用拦截器进行调用拦截,来实现增强的代理逻辑,当然里面可以对应原始方法(在父类中)。 - 回调过滤器
CallbackFilter
:为执行方法选择拦截器,CGLIB可以设置多个拦截器,然后根据具体执行的方法再进行选择分发。
- 拦截器
- 借助增强器
Enhancer
,生成代理类,并注册拦截器。 - 返回代理类。
2.3.1 拦截器的选择
在上述过程,我们已经知道Spring为标注了@Configuration配置类生成了代理类,并指定了回调过滤器
和拦截器
,同时将生成对象的类型修改为了代理类。那Spring在实例化的时候,实例化的就是代理对象
了。在配置类中工厂方法(@Bean标注的方法)调用的时候,会先通过回调过滤器选择合适的拦截器,然后再由拦截器进行拦截、并实现功能增强
。
//ConfigurationClassEnhancer.ConditionalCallbackFilter.java
// 根据方法信息,选择拦截器
@Override
public int accept(Method method) {
for (int i = 0; i < this.callbacks.length; i++) {
Callback callback = this.callbacks[i];
// 重点:拦截器的选择, BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor继承了ConditionalCallback,NoOp.INSTANCE 没有继承
if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {
return i;
}
}
throw new IllegalStateException("No callback available for method " + method.getName());
}
//ConfigurationClassEnhancer.BeanFactoryAwareMethodInterceptor.java
// 匹配逻辑: 只处理setBeanFactory方法
@Override
public boolean isMatch(Method candidateMethod) {
return isSetBeanFactory(candidateMethod);
}
public static boolean isSetBeanFactory(Method candidateMethod) {
return (candidateMethod.getName().equals("setBeanFactory") &&
candidateMethod.getParameterCount() == 1 &&
BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
}
//ConfigurationClassEnhancer.`BeanMethodInterceptor.java
// 匹配逻辑: 只处理方法上加了@Bean注解的,并且不是setBeanFactory()
@Override
public boolean isMatch(Method candidateMethod) {
return (candidateMethod.getDeclaringClass() != Object.class &&
!BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}
通过上述源码,我们可以快速找到回调过滤器选择拦截器的逻辑: if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method))
,我们分析一下这个判断的条件,翻译过来就是:
- 如果是ConditionalCallback类型的拦截器,直接通过回调器isMatch()来判断。
- 如果不是ConditionalCallback类型的拦截器,则直接判断为匹配成功。
初看可能有点懵,我们结合传递进来的拦截器来看,拦截器列表是:BeanMethodInterceptor
、BeanFactoryAwareMethodInterceptor
、NoOp.INSTANCE
,其中前两个都是ConditionalCallback
类型的,最后一个则是默认的空实现。同时传递的回调过滤器列表,是个有序数组,所以会优先匹配BeanMethodInterceptor
和BeanFactoryAwareMethodInterceptor
,如果这两个都不能匹配,则默认匹配到空实现NoOp.INSTANCE
。
关于
BeanMethodInterceptor
、BeanFactoryAwareMethodInterceptor
的匹配逻辑,在代码注释中已经有明确的说明,比较清晰,大家注意看哦。
2.3.2 拦截器的功能
经过上述的不厌其烦的啰嗦,我们已经知道,Spring为标注了@Configuration配置类生成了代理类,在调用配置类的方法时,会先通过回调过滤器选择拦截器,然后由拦截器对方法进行增强。同时也清楚主要是靠BeanMethodInterceptor
和BeanFactoryAwareMethodInterceptor
这两个拦截器器发挥作用,本章节我们一起看一下这两个拦截器的功能。
在分析这两个拦截器的功能之前,我们先来回忆一下@Configuration配置类的作用,让加了@Bean的方法有了"幂等的能力"
,不会重复创建对象。
这里小伙伴需要注意哦,@Bean注解本身的能力就是把我们自己产生的对象,放入到Spring容器中,便于我们依赖注入,这个@Configuration无关,在半配置类下该功能也是正常的哦。@Configuration的加持,只是使其有了幂等性哦。
现在我们已经知道@Configuration的作用,实现原理我们也清楚,通过CGLIB生成代理子类,具体的实现我们猜想是:把@Bean工厂方法生成的对象先放入到Spring容器中缓存,重复调用的时候,从缓存中直接获取
。实际上也确实如此,Spring就是这么做的。
OK,现在一切都真相大白,只剩下最后一层神秘的面纱没有解开,就是Spring怎么做到的。要想达到这样的效果,其实有两个问题需要先解决:
- 对@Bean的工厂方法产生的对象,能够去Spring容器中查重,不存在则生成对象,存则在直接获取的。
- 注入Spring容器,也就是
beanFactory
,因为需要到容器中查重、获取,所以需要先容器对象。
Spring正是用BeanMethodInterceptor
、BeanFactoryAwareMethodInterceptor
这两个拦截器,来解决这两个问题的。我们先来看注入Spring容器beanFactory的实现。
2.3.2.1 给代理类注入bean容器
为了实现@Bean方法的"幂等",我们需要依赖beanFactory,可是没有啊,怎么办呢,要求程序员使用@Autowired BeanFactory
手动放一个?手动是不可能,这辈子都是不可能的,主要是丢不起这个人。手动不行,那就自动放进来吧,这也就是为什么Spring在生产的代理类中,强行加了一个beanFactory属性的原因。
- 为代理类增加beanFactory属性
//ConfigurationClassEnhancer.java
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
// ...省略其他字段的赋值
// 给代理类生成一个$$beanFactory字段
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
return enhancer;
}
private static class BeanFactoryAwareGeneratorStrategy extends
ClassLoaderAwareGeneratorStrategy {
public BeanFactoryAwareGeneratorStrategy(@Nullable ClassLoader classLoader) {
super(classLoader);
}
@Override
protected ClassGenerator transform(ClassGenerator cg) throws Exception {
// 动态添加字段"$$beanFactory"
ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
@Override
public void end_class() {
declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
super.end_class();
}
};
return new TransformingClassGenerator(cg, transformer);
}
}
通过源码,我们可以看到,在生成子类字节码的时候,就借助CGLIB的能力,给代理类动态增加了一个属性$$beanFactory
,其类型正是BeanFactory
类型,这就是自动放入的。
- 使用拦截器BeanFactoryAwareMethodInterceptor给beanFactory属性赋值
上述过程只是增加了属性
$$beanFactory
,还没有赋值呢。其实在属性填充的时候,会进行赋值。我们一起看一下是如何赋值的。
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
// 给$$beanFactory字段赋值
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 代理类中需要存在字段: $$beanFactory
Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
Assert.state(field != null, "Unable to find generated BeanFactory field");
// 通过反射API,为该字段注入spring容器beanFactory
field.set(obj, args[0]);
if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
// 如果注解配置类的父类实现了BeanFactoryAware,传入参数BeanFactory,并执行父类方法
return proxy.invokeSuper(obj, args);
}
return null;
}
}
通过源码,我们可以看到,直接通过反射给$$beanFactory字段赋值,那么在后续其他流程中,就可以正常使用了,so happy。
其实这里并不happy,原理和实现,我们都知道了,但是大家有没有疑惑,这个拦截器中直接从参数args[0]获取了beanFactory,为什么?传递进来的参数到底是什么?又是什么时候回调这个拦截器的。笔者在网上搜了不少文章,好像并没有人提及或者说清这个事情,可能确实有点难度吧,贰师兄仔细研究了下,限于篇幅和对Spring整体体系的限制,本节我们不展开,末尾写个选读吧,大家可以自行决定是否挑战一下哦。
2.3.2.2 保证@Bean工厂方法"冥等"
// 测试代码
@Configuration
public class MarkedByConfiguration {
@Bean
public A a(){
return new A();
}
@Bean
public B b(){
a();
return new B();
}
}
我们根据测试案例中代码b()->a()
来看一下拦截器BeanMethodInterceptor
是如何保证幂等的。我们还是先看源码,有个整体印象:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
// 从增强后的注解配置类实例中,获取BeanFactory(也就是成员变量$$beanFactory)
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
// ① 判断当前调用到的方法,是否是正在执行创建的@Bean工厂方法
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 如果当前执行的是工厂方法来实例化bean,那就执行父类中的方法
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// ② 如果当前执行的方法不是工厂方法,此时就去spring容器中获取@Bean对应的bean实例
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
private boolean isCurrentlyInvokedFactoryMethod(Method method) {
// 获取执行中的@Bean方法,在执行@Bean方法的创建时,会将当前@Bean方法放入ThreadLocal中,这里再取出来
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
// 判断执行的@Bean方法,和正在回调的方法是否相同
return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
}
}
这里我们只分析核心方法,第一步就是 if (isCurrentlyInvokedFactoryMethod(beanMethod))
这个判断逻辑:
- 获取当前执行的工厂方法(@Bean方法)。在Spring执行b()工厂方法时,就会
将b()放入到ThreadLocal中
,标识该工厂方法正在创建中。 - 匹配
当前正在执行的工厂方法,和当前执行的方法,是否是同一个方法
。这里需要注意区别,比如在执行b()时,执行到第一行a()的调用了,进入a()方法后,当前执行的方法是a(),正在执行的@Bean工厂方式仍然是b(),两者肯定不是同一个方法,返回的就是false。
理解了这个方法的判断逻辑,下面我们再来分析执行b()的具体流程:
- 执行到b方法时,
将b()放入到ThreadLoacl中,标识正在创建
。 - 执行b(),由于此时b()所在的配置类,已经为代理类实例了,所以会执行拦截器的回调,且b()是@Bean修饰的工厂方法,回调过滤器会选择
BeanMethodInterceptor
,并执行intercept()拦截逻辑。 - 执行isCurrentlyInvokedFactoryMethod()判断,也就是①的代码,此时ThreadLocal中是b(),正在执行的也是b(),
二者相同,条件成立
,需要执行②处的代码。 开始执行父类中的方法
,也就是原始的,我们自己编写的方法。注意:此时已经从拦截器的方法中跳出来了,执行的是原始的代码。根据执行的顺序,首先执行的就是a()的创建。- 执行a(),由于此时a()所在的配置类,也是代理类实例,且为被@Bean标识的工厂,所以
也会被BeanMethodInterceptor拦截,此时也会执行isCurrentlyInvokedFactoryMethod()判断,但是由于此时ThreadLocal中获取到的方法还是b(),而正在执行的方法是a(),二者不同
,需要执行③处的代码。 - ③处的代码逻辑,主要是
去容器中获取bean实例
,如果不存在,会先创建bean,放入容器后返回。这不是我们讨论的核心问题,我们假设a()之前已经执行过了,在容器中是存在的,此时直接返回了a对象,并没有再次创建。 - 继续执行原始a()的第二行及后面的代码,无关我们的分析,可以忽略。
我们在代码中加入注释,让大家加深一下印象:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
// 从增强后的注解配置类实例中,获取BeanFactory(也就是成员变量$$beanFactory)
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
// ① 判断当前调用到的方法,是否是正在执行创建的@Bean工厂方法
// 1.1: b()执行时,已经将y放入ThreadLocal中,这里获取到的是b,条件成立
// 2.1 执行到b()调用a()了,a()也被代理了,也会执行到这里,此时ThreadLocal中是y,条件不成立,执行②
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 1.2 执行父类中的方法,也就是@Bean工厂方法的逻辑,会调用a()方法呢
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// ② 如果当前执行的方法不是工厂方法,此时就去spring容器中获取@Bean对应的bean实例
// 2.2 去Spring容器中查找a对象,不存在会实例化的,这个可以返回
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
}
在上面的流程分析时。我们发现,该拦截器确实做到了不会重复创建@Bean工厂方法产生的bean,核心原理是对@Bean方法的每次调用都会拦截,然后先去容器查重,存在直接返回。正是这个操作,保证了@Bean工厂方法的幂等。
至此,我们已经明白了在@Configuration配置类的加持下,为什么@Bean工厂方法可以保持幂等的秘密。希望小伙伴们能有所收获,这是对贰师兄最大的安慰哈。
3. 增强代码的调用(选读)
3.1 BeanFactoryAwareMethodInterceptor的调用
上述在提到代理类中,动态添加的属性$$beanFactory
是通过拦截器BeanFactoryAwareMethodInterceptor反射赋值的。并且在拦截器BeanMethodInterceptor直接使用了。但是没有提提及BeanFactoryAwareMethodInterceptor的被调用时机和调用参数,原因是需要小伙伴对Spring的生命周期和Bean的生命周期需要比较熟悉的理解。大家需要补充对应的知识。
关于Bean的生命周期,聊透Spring bean的生命周期有着比较详细的分析。但是对Spring的生命周期,还没有来得及分析,感兴趣的小伙伴,可以留言催更哦。
- 注册BeanPostProcessor,触发拦截器
在上述的分析中,我们已经知道,对于全配置类生成代理子类的逻辑在ConfigurationClassPostProcessor#postProcessBeanFactory()
中,该方法除了生成代理类外,还手动添加了ImportAwareBeanPostProcessor这个Bean的后置处理器,该后置处理器的作用就是调用setBeanFactory方法(会被拦截器BeanFactoryAwareMethodInterceptor拦截,进而执行拦截器的内容),最后为属性$$beanFactory赋值
。
//ConfigurationClassPostProcessor.java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// ...其他代码
// 对全配置类进行增强
enhanceConfigurationClasses(beanFactory);
// 手动添加一个BeanPostProcessor,用来设置setBeanFactory
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
private static class ImportAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
@Override
public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
// 调用setBeanFactory()
if (bean instanceof EnhancedConfiguration) {
((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
}
return pvs;
}
}
我们可以看到,该后置处理器postProcessProperties()的逻辑就是调用setBeanFactory(),并且只调用类型是EnhancedConfiguration子类的哦。小伙伴们还记不记得,Spring为全配置类生成的代理类,手动加了实现EnhancedConfiguration接口的哦,这不就对上了嘛,你说巧不巧😁。
还有小伙伴需要注意,该后置处理器一旦调用,执行到setBeanFactory()时,就会被BeanFactoryAwareMethodInterceptor拦截,此时传递的参数是this.beanFactory
,也就是Spring的bean容器,所以在拦截器直接args[0]反射赋值,才是没有问题的。
- ImportAwareBeanPostProcessor后置处理器的调用 现在我们已经知道通过后置处理器ImportAwareBeanPostProcessor进行setBeanFactory()的回调,也知道传递的参数是Spring的bean容器。那么问题又来了,这个后置处理器是什么时候调用,毕竟他调用了,拦截器才能执行,属性$$beanFactory才能被赋值啊。
好烦啊,有没有,没办法,Spring的代码就是这样,功能实现的链路很长,也很分散,一环扣一环,想串联起来很难,这也是笔者写这个系列文章的初衷,想给大家整合起来,明白来龙去脉,这样一来文章篇幅势必很长,没办法的,大家忍耐一下。好了,不啰嗦了,我们继续。
仔细看笔者聊透Spring bean的生命周期这篇文章的小伙伴可能有印象,在属性填充这一步,回调的后置处理器,有ImportAwareBeanPostProcessor,我们在一起看一下:
OK,现在这个这个链路也清晰了,我们一起总结一下:
3.2 BeanMethodInterceptor的调用
关于@Bean工厂方法的调用时机,其实是在bean的生命周期的初始化bean阶段
,不清楚的小伙伴自行查看吧。啰嗦不动了。
由于BeanFactoryAwareMethodInterceptor拦截器的调用在bean的生命周期的属性填充阶段
,BeanMethodInterceptor的调用在初始化bean阶段
,所以是能保证BeanFactoryAwareMethodInterceptor
拦截器的触发早于BeanMethodInterceptor
拦截器的,这也是BeanMethodInterceptor
能够直接使用的原因。
最后我们一起总结一下吧,希望大家多学习,多进步,最后祝大家新年快乐,发大财。