你自我介紹說很懂Spring配置類,那你怎麼解釋這個現象?

當大潮退去,才知道誰在裸泳。
作者:A哥(YourBatman)
公衆號:BAT的烏托邦(ID:BAT-utopia)
文末是否有彩蛋:有

前言

各位小夥伴大家好,我是A哥。本專欄/系列講解到這裏,關於Spring的@Configuration配置類,應該是可以完成95%以上工作上的使用以及問題的解決。你也絕對算得上是一個“懂它”的Java Coder了,面試自然也就不在話下,基本可以實現“吊打面試官”。

建議剛“翻開”本專欄的同學去我公衆號往前翻翻,前幾篇文章能助你投入精力較少,收穫大不一樣

雖然你已經可以搞定95%的問題,但還剩5%呢?不要了麼?然而殘酷的現實卻是這樣的,能解決那5%問題的纔是真正的王者,他們的薪資往往能高出你一個甚至多個Level,並且在你眼中還好像還“不怎麼幹活”,不信你品,你細品…這就是不可替代性/稀缺性的價值…

在這裏插入圖片描述

如何提高自己的不可替代性?對於三無的我們,沒有辦法只能衝着那5%出發唄。對於鍾愛於面向工資編程的我們,一般還是有更高追求的嘛,畢竟在趨同的程序員視界裏,要的就是不一樣,所以需要繼續打怪升級。

接下來的兩篇內容會比較深入,可能會讓一些“初學者”感到不適(若感覺不適趕緊往前翻翻補課),希望堅持,畢竟這最終都會反應到你每個月的工資上,做難事必有所得嘛

我粗淺的認爲,對於大多數人來說,工資是衡量個人市場價值的唯一/最重要標準。工資20k和22k的兩人可認爲是差不多的,但40k的人肯定比前者價值高出一截


版本約定

本文內容若沒做特殊說明,均基於以下版本:

  • JDK:1.8
  • Spring Framework:5.2.2.RELEASE

在這裏插入圖片描述

正文

如果說前面是些武功招式,那麼接下來就到了內功修煉階段了。走得多塊得看你手腳是否能靈活會用,而走得多遠是由你的體力(內功)決定的。下面我先以一個示例(可當面試題)開始本文的內容。


配置類在Full模式下的“能力”展示

配置類(標註有@Configuration註解,屬於Full模式):

@Configuration
public class AppConfig {

}

case1:

先來個簡單點的。

public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

    AppConfig appConfig = context.getBean(AppConfig.class);

    System.out.println(appConfig.getClass());
    System.out.println(appConfig.getClass().getSuperclass() == AppConfig.class);
    System.out.println(AopUtils.isCglibProxy(appConfig));
}

結果輸出:

class com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$d38ead10
true
false

結果解釋:

  1. Full模式的配置類被CGLIB增強了,所以最終放進容器內的實際是代理對象
  2. 代理類是由CGLIB生成的子類,所以父類必然就是目標類
  3. 這個爲何是false???其實這個和AopUtils.isCglibProxy()的實現有關(建議你源碼點進去瞄一眼一切都明白了),這個配置類僅僅是被CGLIB代理了,和AOP沒毛關係

case2:

這個case會進階一些。

public static void main(String[] args) throws IllegalAccessException {
    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

    AppConfig appConfig = context.getBean(AppConfig.class);

    Field $$beanFactoryField = ReflectionUtils.findField(appConfig.getClass(), "$$beanFactory");
    BeanFactory beanFactory = (BeanFactory) $$beanFactoryField.get(appConfig);

    System.out.println(beanFactory == context.getAutowireCapableBeanFactory());
    System.out.println(beanFactory == context);
    System.out.println(appConfig instanceof BeanFactoryAware);
    System.out.println(appConfig.getClass().getInterfaces()[0]);
}

結果輸出:

true
false
true
interface org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration

結果解釋:

  1. CGLIB字節碼提升時,會自動給代理類新增一個名爲$$beanFactory的字段/屬性,在運行期間給其賦值。所以通過反射可以從代理實例裏拿到這個屬性值,並且值就是當前BeanFactory
    1. 小細節:一定只能寫成(appConfig.getClass(), "$$beanFactory")而不能是(AppConfig.class, "$$beanFactory")哦,因爲這個Field屬於代理類而非目標類
  2. 這個結果是false,和配置類本身並無關係,考察的知識點是Spring上下文Bean工廠和內建Bean工程的區別,這裏先混個臉熟,下個專欄會詳解的
  3. 結果爲true。你是否想動粗:“勞資”的AppConfig配置類明明就沒有實現BeanFactoryAware接口,爲毛你給我返回true呢?
  4. 解釋同上

如果面試官通過這樣的題目來考你(其實目的是想讓你“降薪”),你是否招架得住,成爲那5%呢?本文將帶你一起繼續深挖Spring @Configuration配置裏面的“玄機”,看完後你再回來看這幾個題目就會感嘆了:so easy。


何時創建代理?

我們已然知道Full模式的配置類最終會被CGLIB字節碼提升,從而最終放一個代理類對象到Spring容器裏。那麼我們先來弄清楚創建代理的時機在哪兒~

Spring容器在refresh()啓動步驟的AbstractApplicationContext#invokeBeanFactoryPostProcessors這一步會執行所有的BeanFactoryPostProcessor處理器,而此時BeanFactory纔剛剛準備好,容器內除了ConfigurationClassPostProcessor之外,並無任何其它BeanFactoryPostProcessor,截圖示例如下:

在這裏插入圖片描述

可能你會問:既然這麼早期,那這個處理器是什麼時候放進去的呢?我只能回答:在Bean容器“開山階段”同幾個開山鼻祖一起放進去的。如果你繼續追問很多爲什麼的話,那我只能回答:這不是本專欄講解的重點所在,放在下個專欄詳解,請關注我公衆號即可

既然這樣,那麼接下來就會會ConfigurationClassPostProcessor這個後置處理器嘍。


ConfigurationClassPostProcessor

用於引導處理@Configuration配置類。該後置處理器的優先級是較高的,屬於PriorityOrdered分類。

說明:PriorityOrdered的優先級肯定比Order接口的高

// @since 3.0
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
		...
}

它是一個BeanDefinitionRegistryPostProcessor處理器,所以在容器啓動過程中會先後執行如下兩個方法:


postProcessBeanDefinitionRegistry()

從註冊進來的配置類(可能是Full模式,可能是Lite模式)裏進一步派生bean定義。簡而言之:收集到所有的BeanDefinition(後簡稱爲bd)存儲起來,包括@Import、@Component等等組件。並且做出標註:是Full模式的還是Lite模式的配置類(若非配置組件就不標註哦)。

ConfigurationClassPostProcessor:

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		// 生成一個id,放置後面再重複執行
		int registryId = System.identityHashCode(registry);
		// 若重複執行  就拋出異常
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);
		}

		// 表示此registry裏的bd收集動作,已經做了  避免再重複收集此registry
		this.registriesPostProcessed.add(registryId);

		// 根據配置類,收集到所有的bd信息
		// 並且做出mark標註:是Full模式還是Lite模式,和很重要很重要
		processConfigBeanDefinitions(registry);
	}

執行完此方法,已經完成了bd的收集和標記,那接下來就是本文的主菜了:幫你解答上面case的結果


postProcessBeanFactory()

此方法的作用用一句話可概括爲:爲Full模式的Bean使用CGLIB做字節碼提升,確保最終生成的是代理類實例放進容器內

ConfigurationClassPostProcessor:

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		int factoryId = System.identityHashCode(beanFactory);
		// 防止重複處理
		if (this.factoriesPostProcessed.contains(factoryId)) {
			throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + beanFactory);
		}
		this.factoriesPostProcessed.add(factoryId);

		// 在執行postProcessBeanDefinitionRegistry方法的時就已經將
		// 這個id添加到registriesPostProcessed集合中了
		// 所以到這裏就不會再重複執行配置類的解析了(解析@Import、@Bean等)
		if (!this.registriesPostProcessed.contains(factoryId)) {
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}

		// 從名字上看,這個方法應該就是爲配置類創建代理用的嘍
		enhanceConfigurationClasses(beanFactory);
		
		// 添加了一個後置處理器 它是個SmartInstantiationAwareBeanPostProcessor
		// 它不是本文重點,略
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	}

達到這一步之前,已經完成了bd的收集和標記(見上一步)。對bd進行實例化之前,針對於Full模式的配置類這步驟裏會做增強處理,那就是enhanceConfigurationClasses(beanFactory)這個方法。


enhanceConfigurationClasses(beanFactory)

對一個BeanFactory進行增強,先查找配置類BeanDefinition,再根據Bean定義信息(元數據信息)來決定配置類是否應該被ConfigurationClassEnhancer增強。具體處理代碼如下:

ConfigurationClassPostProcessor:

	public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
		// 最終需要做增強的Bean定義們
		Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
		
		for (String beanName : beanFactory.getBeanDefinitionNames()) {
			BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
			Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
		
			... // 省略其它情況以及異常情況的處理代碼
			
			// 如果是Full模式,纔會放進來
			if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
				configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
			}
		}
		if (configBeanDefs.isEmpty()) {
			// nothing to enhance -> return immediately
			return;
		}

		// ConfigurationClassEnhancer就是對配置類做增強操作的核心類,下面詳解
		ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
		// 對每個Full模式的配置類,一個個做enhance()增強處理
		for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
			AbstractBeanDefinition beanDef = entry.getValue();
			
			// 如果代理了@Configuration類,則始終代理目標類
			// 該屬性和自動代理時是相關的,具體參見Spring的自動代理章節描述
			beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
			
			// CGLIB是給父類生成子類對象的方式實現代理,所以這裏指定“父類”類型
			Class<?> configClass = beanDef.getBeanClass();
			// 做增強處理,返回enhancedClass就是一個增強過的子類
			Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
			// 不相等,證明代理成功,那就把實際類型設置進去
			// 這樣後面實例化配置類的實例時,實際實例化的就是增強子類嘍
			if (configClass != enhancedClass) {
				beanDef.setBeanClass(enhancedClass);
			}
		}
	}

值得注意的是,雖然此方法被設計爲public的,但是隻被一處使用到。Spring這麼做是爲了給提供鉤子,方便容器開發者做擴展時使用

步驟總結:

  1. 從BeanFactory拿出所有的bd信息,一個個判斷
  2. 如果是配置類並且是Full模式,就先存儲起來,後面會對它做字節碼提升。最終如果一個Full模式的配置類都木有,那直接return,此方法結束。否則繼續
  3. 對收集到的每一個 Full模式的配置類,使用ConfigurationClassEnhancer增強器進行字節碼提升,生成一個CGLIB子類型
    1. 小細節:此處顯示標註了AOP自動代理爲:始終代理目標類
  4. 把CGLIB生成的子類型設置到元數據裏去:beanDef.setBeanClass(enhancedClass)。這樣Spring在最後實例化Bean時,實際生成的是該代理類型的實例,從而達到代理/增強的目的

該方法執行完成後,執行“結果”我截了張圖,供以參考:

在這裏插入圖片描述
這段代碼是不難的,理解起來十分簡單。但是,我們仍舊還只知道結果,並不清楚原因。憑它還無法解釋上文中兩個case的現象,所以我們應該端正態度繼續深入,看看ConfigurationClassEnhancer增強器到底做了什麼事。

在介紹ConfigurationClassEnhancer之前,希望你對CGLIB的使用有那麼一定的瞭解,這樣會輕鬆很多。當然不必過於深究(否則容易懷疑人生),但應該能知道如何使用Enhancer增強器去增強/代理目標類,如何寫攔截器等。

因爲之前文章介紹過了CGLIB的基本使用,限於篇幅,此處就不再囉嗦。


ConfigurationClassEnhancer源碼分析

得打起精神了,因爲接下來纔是本文之精華,讓你出彩的地方。

@since 3.0。通過生成一個CGLIB子類來增強@Configuration類與Spring容器進行交互,每個這樣的@Bean方法都會被生成的子類所複寫。這樣子當遇到方法調用時,纔有可能通過攔截從而把方法調用引回容器,通過名稱獲得相應的Bean。

建立在對CGLIB的使用有一定了解的基礎上,再來閱讀本文會變得輕鬆許多。該類有且僅有一個 public方法,如下所示:

在這裏插入圖片描述

ConfigurationClassEnhancer:

	public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
		// 如果已經實現了該接口,證明已經被代理過了,直接返回唄~
		if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
			return configClass;
		}

		// 若沒被代理過。就先調用newEnhancer()方法創建一個增強器Enhancer
		// 然後在使用這個增強器,生成代理類字節碼Class對象
		Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
		return enhancedClass;
	}

巨簡單有沒有。

爲何Spring的源碼是開源軟件的範本?因爲它各種封裝、設計模式用得都非常好,甚至對初學者都是友好的,所以說Spring易學難精。

該public方法的核心,在下面這兩個個私有方法上。


newEnhancer()和createClass()

創建一個新的CGLIB Enhancer實例,並且做好相應配置。

ConfigurationClassEnhancer:

	private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
		Enhancer enhancer = new Enhancer();
		// 目標類型:會以這個作爲父類型來生成字節碼子類
		enhancer.setSuperclass(configSuperClass);
		// 讓代理類實現EnhancedConfiguration接口,這個接口繼承了BeanFactoryAware接口
		enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
		
		// 設置生成的代理類不實現org.springframework.cglib.proxy.Factory接口
		enhancer.setUseFactory(false);
		// 設置代理類名稱的生成策略:Spring定義的一個生成策略
		// 你名稱中會有“BySpringCGLIB”字樣
		enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
		enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));

		// 設置攔截器/過濾器
		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;
	}

Enhancer是CGLIB的最核心API,通過方法名應該基本清楚了每一步都有什麼作用吧。

Enhancer屬於CGLIB的核心API,但你發現它的包名是xxx.springframework.xxx 。這是因爲CGLIB在Spring內太常用了(強依賴),因此Spring索性就自己fork了一份代碼過來~

本方法我們需要關注對Enhancer實例的配置,有如下關注點:

  • 通過它增強的每個類都實現了EnhancedConfiguration接口,並且它還是BeanFactoryAware的子接口
    • 統一實現接口,這和Spring AOP創建代理是不是如出一轍?想一想
    • 實現了BeanFactoryAware接口,這樣Spring在創建代理類實例的時候會給注入BeanFactory
  • 使用SpringNamingPolicy策略來生成類名稱。這就是解釋了爲何代理類的名你都能看到BySpringCGLIB字樣
  • 對於代理最爲重要的當屬過濾器/攔截器org.springframework.cglib.proxy.Callback,它們是實現功能的核心。配置此增強器時設置了CALLBACK_FILTER共三個攔截器

關於CALLBACK_FILTER,我們發現在類ConfigurationClassEnhancer最開始處就申明瞭三個攔截器放進去了:

ConfigurationClassEnhancer:

	private static final Callback[] CALLBACKS = new Callback[] {
			new BeanMethodInterceptor(),
			new BeanFactoryAwareMethodInterceptor(),
			NoOp.INSTANCE
	};

如果說前面都是做準備工作,那麼攔截器纔是運行期真正幹活的“人”了。它能夠解答我們今天的疑問~


攔截器分析

什麼是動態代理?用通俗的話理解就是:代理的核心邏輯就是依賴於攔截器實現的,可見攔截器(也叫增強)之於代理類是何等重要。

上面的三個攔截器中,NoOp.INSTANCE代表什麼都沒做,因此我們只需要關注前兩個。他倆均是MethodInterceptor接口的實現類,均實現了intercept()方法來做具體的攔截操作(他倆均是私有靜態內部類喲)。

說明:本文的兩個case用第一個攔截器即可解釋,鑑於第二個攔截器非常的複雜,所以我把它放在下篇文章詳解(已寫得差不多了,因爲太複雜,篇幅比本文還長)


BeanFactoryAwareMethodInterceptor

顧名思義,它表示的是BeanFactoryAware方法的攔截器,所以靠猜應該能猜到它攔截的是setBeanFactory(beanFactory)方法。

說明:Spring所有的攔截器實現的攔截都是方法級別的。雖然也支持構造器的攔截,但並沒有內置實現,需要使用者自行擴展(比較複雜,一般並無使用場景)

相較於下文要講的第二個攔截器,這個攔截器比較簡單。但是它實現的功能可不簡約哦,因爲它能夠解釋文首提出的兩個case,讓你談薪更有底氣

既然是攔截器,就應該按如下兩步去了解它:執行時機 + 做了何事


執行時機

執行時機決定了增強邏輯何時執行,畢竟一般來說都不可能是增強所有的嘛。

BeanFactoryAwareMethodInterceptor:

		// 當執行到setBeanFactory(xxx)方法時匹配成功
		@Override
		public boolean isMatch(Method candidateMethod) {
			return isSetBeanFactory(candidateMethod);
		}
		// 此方法標記爲public static 是因爲下面這個攔截器也會用到
		public static boolean isSetBeanFactory(Method candidateMethod) {
			return (candidateMethod.getName().equals("setBeanFactory") &&
					candidateMethod.getParameterCount() == 1 &&
					BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
					BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
		}

我們知道setBeanFactory()方法是由Spring容器在初始化Bean時回調調用的,而代理類實現了EnhancedConfiguration接口(間接實現了BeanFactoryAware接口),所以該攔截器的執行時機爲:在Spring初始化代理類實例時執行攔截

說明:isSetBeanFactory()判斷方法做這麼“複雜”主要是爲了容錯,“擔心”你自己定義了一個名爲setBeanFactory的方法而“搞錯了”。


做了何事

作爲一個攔截器,增強邏輯纔是它的核心。

BeanFactoryAwareMethodInterceptor:

		@Override
		@Nullable
		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");
			field.set(obj, args[0]);

			// 如果用戶類(也就是你自己定義的類)自己實現了該接口,那麼別擔心,也會給你賦值上
			if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
				return proxy.invokeSuper(obj, args);
			}
			return null;
		}

從執行時機知道了,它攔截的是setBeanFactory()方法的執行。所以這裏的Method就代表的是setBeanFactory()方法,Object[] args的值是當前容器的BeanFactory工廠(注意理解這句話)實例。

此攔截器增強完成後,結果截圖如下:

在這裏插入圖片描述
好了,介紹到這裏本文就先告一段落。如果你是認真的看完了本文的分析,那麼現在你再“回到頂部”理解那兩個case的結果,你就豁然開朗了。

建議一定弄懂,我覺得已經很明朗了,所以就不再廢話。若有不清楚,可以下方掃碼加我微信私聊我吧(或者文末留言)


總結

又是一篇關於Spring配置類的長文,只希望對你有幫助纔有意義。最爲核心的兩個增強/攔截器,迫於篇幅和讀者的腦力(畢竟理解起來還是比較費勁的),今天只講一個。我把另外一個更爲重要、更爲複雜、更爲多金的部分放在了下文專文闡述,你可關注我公衆號保持“收看”。

下篇碼字已經碼得差不多了,80%吧。手累了,今天先休息,明天再搞,內容很精彩😄


關於A哥

  • 專欄式學習咱們小衆聊,知識星球誠邀您掃碼入駐(提示:請務必先關注公衆號,後臺回覆“知識星球”領取優惠券後再輕裝入駐)
  • 私人微信,掃碼加A哥好友,邀你進入 Java高工、架構師 系列純技術羣(或者關注公衆號,後臺回覆“加羣”亦可直接加入)
  • 文章在公衆號首發,其它平臺慢1-2天。你也可關注我的個人博客:https://www.yourbatman.cn

    碼字非常不易,不可以白嫖,點個在看/關注就是對A哥的支持嘍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章