因為不懂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


分享到:


相關文章: