因为不懂Spring 的@Configuration 配置类,我被面试官绝地反杀了

一、前言

在这里我不得不感慨Spring的代码的完善与优秀,从之前看源码迷迷糊糊到现在基本了解Spring的部分源码后,愈来愈发现Spring开发者的思虑之周全!

之前说过学习源码的目的在哪?正如我特别喜欢的一句话,有道无术,术尚可求也!有术无道,止于术!,对于Spring的了解仅仅局限于使用远远不够,Spring作为一个国内绝大多数java开发者使用的一个项目管理框架,他是一个生态,什么是生态?比如现在的SpringBoot、SpringCloud,他们是什么?是Spring生态中的一个组成部分!他们利用Spring生态中提供的各种扩展点,一步一步的封装,成就了现在Spring快速启动、自动配置等亮眼的功能!作为Spring的使用者,我们理应了解Spring的实现和各种扩展点,从而能够真正的深入Spring生态!深入了,再去研究生态中的组成部分如:SpringBoot之流的框架,也就水到渠成了!

二、开篇一问

相信大部分开发者对于Spring的使用都是水到渠成的!那么下面一段代码大家一定很熟悉!

<code> 
 

public

class

ExpandRunConfig

{

public

TestService

testService

()

{

return

new

TestServiceImpl(); }

public

UserService

userService

()

{ testService();

return

new

UserServiceImpl(); } } /<code>

可以很清楚的看到,这里交给Spring管理了两个类TestService,UserService,但是在userService()里面又引用了testService()! 那么问题来了,你觉得TestService会被实例化几次?

相信有不少同学,张口就说一次,对,没错,但是为什么呢?我当时对这里的问题深深的感到自我怀疑!甚至一度怀疑自己的java基础,明明这里调用了另外一个方法,但是为什么没有进行两次实例化呢?

我问了很多同事、朋友,他们只知道这样写是没有问题的!但是具体原因不知道!为什么呢?我们带着这个问题往下看!

三、你看到的配置类是真的配置类吗?

我们从bean容器里面把这个配置类取出来,看一下有什么不一样!

<code>

public

static

void

main

(String[] args)

{ AnnotationConfigApplicationContext ac =

new

AnnotationConfigApplicationContext(ExpandRunConfig

.

class

)

; ExpandRunConfig bean = ac.getBean(ExpandRunConfig

.

class

)

; System.out.println(bean); } /<code>

我们debug看一下,我们取出来了个什么玩意!

因为不懂Spring 的@Configuration 配置类,我被面试官绝地反杀了


被代理的Spring配置类

果然,他不是他了,他被(玷污)代理了,而且使用的代理是cglib,那么这里就可以猜测一个问题,在Bean方法中调用另外一个Bean方法,他一定是通过代理来做的,从而完成了多次调用只实例化一次的功能!

到这里,解决了,原来是这样!那么现在有两个疑问:

  1. 什么时候给配置类加的代理?
  2. 代理逻辑里面是怎么完成多次调用返回同一个实例的功能的?

下面我们就带着两个疑问,去追一下Spring源码,看看到底是如何进行的!

四、代理图示

因为不懂Spring 的@Configuration 配置类,我被面试官绝地反杀了

这张图我放出来,如果你没有了解过的话,一定是很迷惑,没关系,后面会用源码解释,而且源码看完之后,我们会大概手写一个,帮助你理解!

五、源码详解

不妨猜一下,看过我以前的文章的读者都应该了解!Spring创建bean实例的时候,所需要的信息是在 beanDefinitionMap里面存放的,那么在初始化的时候解析bean的bd的时候,一定是替换了配置类bd里面的类对象,才会使后面实例化config的时候变成了一个代理对象,所以我们的入口应该在这里:

因为不懂Spring 的@Configuration 配置类,我被面试官绝地反杀了

那么这里面的代码是在哪增强的呢?

<code> 
@

Override

public

void

postProcessBeanFactory

(ConfigurableListableBeanFactory beanFactory)

{ ..................忽略对应的逻辑................ enhanceConfigurationClasses(beanFactory); ..................忽略对应的逻辑................ } /<code>

调用配置类的增强逻辑 enhanceConfigurationClasses

<code> 
public 

void

enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {

Map

<

String

, AbstractBeanDefinition> configBeanDefs =

new

LinkedHashMap<>();

for

(

String

beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);

if

(ConfigurationClassUtils.isFullConfigurationClass(beanDef)) { .....忽略日志打印...... configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef); } }

if

(configBeanDefs.isEmpty()) {

return

; } 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);

try

{ Class> configClass = beanDef.resolveBeanClass(

this

.beanClassLoader);

if

(configClass !=

null

) { Class> enhancedClass = enhancer.enhance(configClass,

this

.beanClassLoader);

if

(configClass != enhancedClass) { ..... 忽略日志打印 .... beanDef.setBeanClass(enhancedClass); } } }

catch

(Throwable ex) { 。。。。。忽略异常处理。。。。。。。 } } } /<code>

这个类至关重要,总共做了这样几件事:

  1. 筛选配置类,只有加了 @Configuration的配置类才会被增强!
  2. 使用enhancer.enhance构建一个增强器,返回增强后的代理类对象!
  3. 替换配置类原始的beanClass,为代理后的class!

那么,我们最关心的是如何实现的,肯定要看enhancer.enhance里面的逻辑~

<code>

public

Class> enhance(Class> configClass, ClassLoader classLoader) {

if

(EnhancedConfiguration

.

class

.

isAssignableFrom

(configClass)) { 。。。。忽略日志打印。。。。

return

configClass; } Class> enhancedClass = createClass(newEnhancer(configClass, classLoader));

if

(logger.isTraceEnabled()) { 。。。。忽略日志打印。。。。 }

return

enhancedClass; } /<code>

这是一个过度方法,真正去构建一个代理增强器的是newEnhancer方法,我们似乎接近了我们要的答案!

<code> 

private

Enhancer

newEnhancer

(Class> configSuperClass, @Nullable ClassLoader classLoader)

{ Enhancer enhancer =

new

Enhancer(); enhancer.setSuperclass(configSuperClass); enhancer.setInterfaces(

new

Class>[] {EnhancedConfiguration

.

class

})

; enhancer.setUseFactory(

false

); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setStrategy(

new

BeanFactoryAwareGeneratorStrategy(classLoader)); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());

return

enhancer; } /<code>

如果你熟悉cglib的话,肯定对这几行代码熟悉无比,主要做了这样几件事!

  1. 设置需要代理的类
  2. 设置生成的代理类需要实现的接口,这里设置实现了EnhancedConfiguration,注意这个是一个很骚的操作,他是能够保证最终类能够从beanFactory返回的一个重要逻辑,为什么?因为EnhancedConfiguration是BeanFactoryAware的子类,Spring会回调他,给他设置一个 beanFactory ,如果你看不懂不妨先把和这个记下来,等看完在回来仔细品味一下!
  3. 设置过滤器,过滤器里面其实是一组回调方法,这个回调方法是最终方法被拦截后执行的真正逻辑,我们一会要分析的也是过滤器里面这一组回调实例!
  4. 返回最终的增强器!

刚刚也说了,我们需要重点关注的是这一组拦截方法,我们进入到拦截器里面,找到对应的回调实例!

CALLBACK_FILTER:常量对应的是一个过滤器,我们看它如何实现的:

<code>

private

static

final

ConditionalCallbackFilter CALLBACK_FILTER =

new

ConditionalCallbackFilter(CALLBACKS); /<code>

那么此时 CALLBACKS 就是我们要找的回调方法,点进去可以看到:

<code> 

private

static

final

Callback[] CALLBACKS =

new

Callback[] {

new

BeanMethodInterceptor(),

new

BeanFactoryAwareMethodInterceptor(), NoOp.INSTANCE }; /<code>

具体里面每一个拦截器究竟是干嘛的,注释说的很明白,我们从第二个说起!为什么不从第一个呢?第一个比较麻烦,我们由浅入深,逐步的说!

BeanFactoryAwareMethodInterceptor

<code> 

private

static

class

BeanFactoryAwareMethodInterceptor

implements

MethodInterceptor

,

ConditionalCallback

{

public

Object

intercept

(Object obj, Method method, Object[] args, MethodProxy proxy)

throws

Throwable

{ Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD); Assert.state(field !=

null

,

"Unable to find generated BeanFactory field"

); field.set(obj, args[

0

]);

if

(BeanFactoryAware

.

class

.

isAssignableFrom

(

ClassUtils

.

getUserClass

(

obj

.

getClass

().

getSuperclass

())))

{

return

proxy.invokeSuper(obj, args); }

return

null

; }

public

boolean

isMatch

(Method candidateMethod)

{

return

isSetBeanFactory(candidateMethod); } .........忽略不必要逻辑......... } /<code>

不知道你注意没有,在最终生成的代理配置类里面有一个 $$beanFactory属性,这个属性就是在这里被赋值的!再把图片放出来,看最后一个属性!

因为不懂Spring 的@Configuration 配置类,我被面试官绝地反杀了

这个拦截器的主要作用:

  1. 拦截 setBeanFactory方法,为 $$beanFactory赋值!

好了,这个拦截器介绍完了,功能大家也记住了,那么,我们分析下一个拦截器,这个是重点!

BeanMethodInterceptor

<code> 
 
 

public

Object

intercept

(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy)

throws

Throwable

{ ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance); String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

if

(BeanAnnotationHelper.isScopedProxy(beanMethod)) { String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);

if

(beanFactory.isCurrentlyInCreation(scopedBeanName)) { beanName = scopedBeanName; } } 。。。。。。忽略与本题无关的代码。。。。。。。。。。

if

(isCurrentlyInvokedFactoryMethod(beanMethod)) {

if

(logger.isInfoEnabled() && BeanFactoryPostProcessor

.

class

.

isAssignableFrom

(

beanMethod

.

getReturnType

()))

{ ...... 忽略日志打印...... }

return

cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); }

return

resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName); } /<code>

乍一看,是不是好多,没事我们一点一点分析:

  1. 首先我们看那个判断if (isCurrentlyInvokedFactoryMethod(beanMethod))这个判断是很重要的!他就是从ThreadLocal里面取出本次调用的工厂方法,前面提到过很多次工厂方法,什么是工厂方法?就是你写的那个@Bean对应的方法,我们就叫做工厂方法,我们以上面开篇一问里的那个代码为例!当创建 UserServiceImpl的时候,会先存储当前的方法对象也就是 UserServiceImpl的方法对象,也就是放置到ThreadLocal里面去!然后发现是一个代理对象,进入到代理逻辑,在代理逻辑里面,走到这个判断逻辑,发现本次拦截的方法和ThreadLocal里面的方法是一致的,然后就放行,开始调用真正的 userService()方法,执行这个方法的时候,方法内部调用了testService();方法!发现testService()又是一个代理对象,于是又走代理逻辑,然后走到这个判断,判断发现当前拦截的方法是testService而ThreadLocal里面的方法却是userService,此时判断就失败了,于是就走到另外一个分支!另外一个分支就不再执行这个方法了,而是直接去beanFactory去取这个bean,直接返回!
  2. return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);这个是当拦截的方法是工厂方法的时候直接放行,执行父类的逻辑,为什么是父类!Cglib是基于继承来实现的,他的父类就是原始的那个没有经过代理的方法,相当于调用super.userService()去调用原始逻辑!
  3. resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);这个也是一会我们要看的代码逻辑,这个就是当判断不成立,也就是发现工厂方法里面还调用了另外一个工厂方法的时候,会进入到这里面!那我们看一下这里面的逻辑吧!

resolveBeanReference方法逻辑

<code>

private

Object

resolveBeanReference(Method beanMethod,

Object

[] beanMethodArgs, ConfigurableBeanFactory beanFactory,

String

beanName) { 。。。。。。。。。忽略不必要代码。。。。。。。。。

Object

beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) : beanFactory.getBean(beanName)); 。。。。。。。。。忽略不必要代码。。。。。。。。。

return

beanInstance; } } /<code>

这里面的主要逻辑就是从beanFactory里面获取这个方法对应的bean对象,直接返回!而不是再去调用对应的方法创建!这也就是为什么多次调用,返回的实例永远只是一个的原因!

六、总结

整个过程比较绕,读者可以自己跟着文章调试一下源码,相信经过过深度思考,你一定有所收获!

整个过程分为两大部分:

  1. 增强配置类检测加了@Configuration注解的配置类!创建代理对象(BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor)作为增强器的回调方法!返回代理后的类对象!设置进配置类的beanClass!
  2. 创建bean发现该bean创建的时候依附配置类(也就是加了@Bean的方法)!回调增强配置类的方法,并记录该方法!判断拦截的方法和记录的方法是否一致一致的话就走原始的创建逻辑!不一致,就从bean工厂获取!返回创建好的bean

收工!

大家看完有什么不懂的可以在下方留言讨论也可以关注.

谢谢你的观看。

觉得文章对你有帮助的话记得关注我点个赞支持一下!

链接:https://juejin.im/post/6860387888413343757


分享到:


相關文章: