您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)Spring中的@Configuration配置類是怎樣的,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
在這里我不得不感慨Spring的代碼的完善與優(yōu)秀,從之前看源碼迷迷糊糊到現(xiàn)在基本了解Spring的部分源碼后,愈來愈發(fā)現(xiàn)Spring開發(fā)者的思慮之周全!
之前說過學(xué)習(xí)源碼的目的在哪?對于Spring的了解僅僅局限于使用遠(yuǎn)遠(yuǎn)不夠,Spring作為一個國內(nèi)絕大多數(shù)java開發(fā)者使用的一個項目管理框架,他是一個生態(tài),什么是生態(tài)?比如現(xiàn)在的SpringBoot
、SpringCloud
,他們是什么?是Spring生態(tài)中的一個組成部分!他們利用Spring生態(tài)中提供的各種擴(kuò)展點,一步一步的封裝,成就了現(xiàn)在Spring快速啟動
、自動配置
等亮眼的功能!作為Spring的使用者,我們理應(yīng)了解Spring的實現(xiàn)和各種擴(kuò)展點,從而能夠真正的深入Spring生態(tài)!深入了,再去研究生態(tài)中的組成部分如:SpringBoot
之流的框架,也就水到渠成了!
相信大部分開發(fā)者對于Spring的使用都是水到渠成的!那么下面一段代碼大家一定很熟悉!
/**
* 全局配置類
*
* @author huangfu
*/
@Configuration
public class ExpandRunConfig {
@Bean
public TestService testService() {
return new TestServiceImpl();
}
@Bean
public UserService userService() {
testService();
return new UserServiceImpl();
}
}
可以很清楚的看到,這里交給Spring管理了兩個類TestService
,UserService
,但是在userService()
里面又引用了testService()
! 那么問題來了,你覺得TestService
會被實例化幾次?
相信有不少同學(xué),張口就說一次
,對,沒錯,但是為什么呢?我當(dāng)時對這里的問題深深的感到自我懷疑!甚至一度懷疑自己的java基礎(chǔ),明明這里調(diào)用了另外一個方法,但是為什么沒有進(jìn)行兩次實例化呢?
我問了很多同事、朋友,他們只知道這樣寫是沒有問題的!但是具體原因不知道!為什么呢?我們帶著這個問題往下看!
我們從bean容器里面把這個配置類取出來,看一下有什么不一樣!
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ExpandRunConfig.class);
ExpandRunConfig bean = ac.getBean(ExpandRunConfig.class);
System.out.println(bean);
}
我們debug看一下,我們?nèi)〕鰜砹藗€什么玩意!
果然,他不是他了,他被(玷污)代理了,而且使用的代理是cglib
,那么這里就可以猜測一個問題,在Bean方法中調(diào)用另外一個Bean方法,他一定是通過代理來做的,從而完成了多次調(diào)用只實例化一次的功能!
到這里,解決了,原來是這樣!那么現(xiàn)在有兩個疑問:
下面我們就帶著兩個疑問,去追一下Spring源碼,看看到底是如何進(jìn)行的!
這張圖我放出來,如果你沒有了解過的話,一定是很迷惑,沒關(guān)系,后面會用源碼解釋,而且源碼看完之后,我們會大概手寫一個,幫助你理解!
不妨猜一下,看過我以前的文章的讀者都應(yīng)該了解!Spring創(chuàng)建bean實例的時候,所需要的信息是在beanDefinitionMap
里面存放的,那么在初始化的時候解析bean的bd的時候,一定是替換了配置類bd里面的類對象,才會使后面實例化config的時候變成了一個代理對象,所以我們的入口應(yīng)該在這里:
那么這里面的代碼是在哪增強的呢?
/**
* 準(zhǔn)備配置類以在運行時為Bean請求提供服務(wù)
* 通過用CGLIB增強的子類替換它們。
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
..................忽略對應(yīng)的邏輯................
//字節(jié)碼增強配置類 貌似用的cglib
enhanceConfigurationClasses(beanFactory);
..................忽略對應(yīng)的邏輯................
}
調(diào)用配置類的增強邏輯 enhanceConfigurationClasses
/**
* 對BeanFactory進(jìn)行后處理以搜索配置類BeanDefinitions; 然后,任何候選人都將通過{@link ConfigurationClassEnhancer}.
* 候選狀態(tài)由BeanDefinition屬性元數(shù)據(jù)確定。
* @see ConfigurationClassEnhancer
*/
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
// 最終需要做增強的Bean定義們
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
//什么是Full類,簡單來說就是加了 @Configuration 的配置類
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
.....忽略日志打印......
//// 如果是Full模式,才會放進(jìn)來
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// 沒有什么可增強的->立即返回
return;
}
//配置類增強器
// ConfigurationClassEnhancer就是對配置類做增強操作的核心類
//初始化會初始化兩個chlib攔截類 BeanFactoryAwareMethodInterceptor 和 BeanMethodInterceptor
//這個是重點 這個類里面的方法會產(chǎn)生最終的代理類
//這個方法里面有個
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
//對每個Full模式的配置類,一個個做enhance()增強處理
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// 如果@Configuration類被代理,請始終代理目標(biāo)類
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
try {
// 設(shè)置用戶指定的bean類的增強子類
//CGLIB是給父類生成子類對象的方式實現(xiàn)代理,所以這里指定“父類”類型
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
if (configClass != null) {
//做增強處理,返回enhancedClass就是一個增強過的子類
//這個是重點,這個會構(gòu)建一個cglib的增強器,最終返回被代理完成的類對象!
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
//不相等,證明代理成功,那就把實際類型設(shè)置進(jìn)去
if (configClass != enhancedClass) {
..... 忽略日志打印 ....
//這樣后面實例化配置類的實例時,實際實例化的就是增強子類嘍
//這里就是替換 config類的beanClass對象的!
beanDef.setBeanClass(enhancedClass);
}
}
}
catch (Throwable ex) {
。。。。。忽略異常處理。。。。。。。
}
}
}
這個類至關(guān)重要,總共做了這樣幾件事:
@Configuration
的配置類才會被增強!enhancer.enhance
構(gòu)建一個增強器,返回增強后的代理類對象!那么,我們最關(guān)心的是如何實現(xiàn)的,肯定要看enhancer.enhance
里面的邏輯~
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
// 如果已經(jīng)實現(xiàn)了該接口,證明已經(jīng)被代理過了,直接返回
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
。。。。忽略日志打印。。。。
return configClass;
}
//沒被代理過。就先調(diào)用newEnhancer()方法創(chuàng)建一個增強器Enhancer
//然后在使用這個增強器,生成代理類字節(jié)碼Class對象
//創(chuàng)建一個新的CGLIB Enhancer實例,并且做好相應(yīng)配置
//createClass是設(shè)置一組回調(diào)(也就是cglib的方法攔截器)
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
if (logger.isTraceEnabled()) {
。。。。忽略日志打印。。。。
}
return enhancedClass;
}
這是一個過度方法,真正去構(gòu)建一個代理增強器的是newEnhancer
方法,我們似乎接近了我們要的答案!
/**
* 創(chuàng)建一個新的CGLIB {@link Enhancer}實例。
*/
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
// 目標(biāo)類型:會以這個作為父類型來生成字節(jié)碼子類
enhancer.setSuperclass(configSuperClass);
//代理類實現(xiàn)EnhancedConfiguration接口,這個接口繼承了BeanFactoryAware接口
//這一步很有必要,使得配置類強制實現(xiàn) EnhancedConfiguration即BeanFactoryAware 這樣就可以輕松的獲取到beanFactory
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
// 設(shè)置生成的代理類不實現(xiàn)org.springframework.cglib.proxy.Factory接口
enhancer.setUseFactory(false);
//設(shè)置代理類名稱的生成策略:Spring定義的一個生成策略 你名稱中會有“BySpringCGLIB”字樣
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
//設(shè)置攔截器/過濾器 過濾器里面有一組回調(diào)類,也就是真正的方法攔截實例
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
如果你熟悉cglib的話,肯定對這幾行代碼熟悉無比,主要做了這樣幾件事!
EnhancedConfiguration
,注意這個是一個很騷的操作,他是能夠保證最終類能夠從beanFactory返回的一個重要邏輯,為什么?因為 EnhancedConfiguration
是 BeanFactoryAware
的子類,Spring會回調(diào)他,給他設(shè)置一個 beanFactory ,如果你看不懂不妨先把和這個記下來,等看完在回來仔細(xì)品味一下!剛剛也說了,我們需要重點關(guān)注的是這一組攔截方法,我們進(jìn)入到攔截器里面,找到對應(yīng)的回調(diào)實例!
CALLBACK_FILTER
:常量對應(yīng)的是一個過濾器,我們看它如何實現(xiàn)的:
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
那么此時 CALLBACKS
就是我么要找的回調(diào)方法,點進(jìn)去可以看到:
// 要使用的回調(diào)。請注意,這些回調(diào)必須是無狀態(tài)的。
private static final Callback[] CALLBACKS = new Callback[] {
//這個是真正能夠Bean方法多次調(diào)用返回的是一個bean實例的實際攔截方法,這個攔截器就是完全能夠說明,為什么多次調(diào)用只返回
//一個實例的問題
new BeanMethodInterceptor(),
//攔截 BeanFactoryAware 為里面的 setBeanFactory 賦值
//剛剛也說了,增強類會最終實現(xiàn) BeanFactoryAware 接口,這里就是攔截他的回調(diào)方法 setBeanFactory方法,獲取bean工廠!
new BeanFactoryAwareMethodInterceptor(),
//這個說實話 真魔幻 我自己實現(xiàn)cglib的時候一直在報錯 報一個自己拋出的異常,異常原因是沒有處理object里面的eques等
//方法,這個就是為了處理那些沒有被攔截的方法的實例 這個些方法直接放行
//這個實例里面沒有實現(xiàn)任何的東西,空的,代表著不處理!
NoOp.INSTANCE
};
具體里面每一個攔截器究竟是干嘛的,注釋說的很明白,我們從第二個說起!為什么不從第一個呢?第一個比較麻煩,我們由淺入深,逐步的說!
BeanFactoryAwareMethodInterceptor
/**
* 攔截對任何{@link BeanFactoryAware#setBeanFactory(BeanFactory)}的調(diào)用 {@code @Configuration}類實例,用于記錄{@link BeanFactory}。
* @see EnhancedConfiguration
*/
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
@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]);
// 實際的(非CGLIB)超類是否實現(xiàn)BeanFactoryAware?
// 如果是這樣,請調(diào)用其setBeanFactory()方法。如果沒有,請退出。
//如果用戶類(也就是你自己定義的類)自己實現(xiàn)了該接口,那么別擔(dān)心,也會給你賦值上
if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
return proxy.invokeSuper(obj, args);
}
return null;
}
/**
* 執(zhí)行到setBeanFactory(xxx)方法時匹配成功
* @param candidateMethod 當(dāng)前執(zhí)行的方法
* @return
*/
@Override
public boolean isMatch(Method candidateMethod) {
//判斷方法是不是 `setBeanFactory` 方法
return isSetBeanFactory(candidateMethod);
}
.........忽略不必要邏輯.........
}
不知道你注意沒有,在最終生成的代理配置類里面有一個 $$beanFactory
屬性,這個屬性就是在這里被賦值的!再把圖片放出來,看最后一個屬性!
這個攔截器的主要作用:
setBeanFactory
方法,為 $$beanFactory
賦值!好了,這個攔截器介紹完了,功能大家也記住了,那么,我們分析下一個攔截器,這個是重點!
BeanMethodInterceptor
/**
* 增強{@link Bean @Bean}方法以檢查提供的BeanFactory中的 這個bean對象的存在。
* @throws Throwable 作為所有在調(diào)用時可能引發(fā)的異常的統(tǒng)籌 代理方法的超級實現(xiàn),即實際的{@code @Bean}方法
* 當(dāng)該方法經(jīng)過匹配成功后 會進(jìn)入到這個攔截方法 這個是解決bean方法只被創(chuàng)建一次的重要邏輯
*/
@Override
@Nullable
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
//通過反射,獲取到Bean工廠。也就是 $$beanFactory 這個屬性的值
//也就是上一個攔截器被注入的值
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
//拿到Bean的名稱
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// 確定此bean是否為作用域代理
//方法頭上是否標(biāo)注有@Scoped注解
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
。。。。。。忽略與本題無關(guān)的代碼。。。。。。。。。。
// 檢查給定的方法是否與當(dāng)前調(diào)用的容器相對應(yīng)工廠方法。
// 比較方法名稱和參數(shù)列表來確定是否是同一個方法
// 怎么理解這句話,參照下面詳解吧
//在整個方法里面,我認(rèn)為這個判斷是核心,為什么說他是核心,因為只有這個判斷返回的是false的時候他才會真正的走增強的邏輯
//什么時候會是false呢?
//首先 spring會獲取到當(dāng)前使用的方法 其次會獲取當(dāng)前調(diào)用的方法,當(dāng)兩個方法不一致的時候會返回false
//什么情況下胡不一致呢?
//當(dāng)在bean方法里面調(diào)用了另一個方法,此時當(dāng)前方法和調(diào)用方法不一致,導(dǎo)致返回課false然后去執(zhí)行的增強邏輯
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 這是個小細(xì)節(jié):若你@Bean返回的是BeanFactoryPostProcessor類型
// 請你使用static靜態(tài)方法,否則會打印這句日志的~~~~
// 因為如果是非靜態(tài)方法,部分后置處理失效處理不到你,可能對你程序有影像
// 當(dāng)然也可能沒影響,所以官方也只是建議而已~~~
if (logger.isInfoEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
...... 忽略日志打印......
}
// 這表示:當(dāng)前方法,就是這個被攔截的方法,那就沒啥好說的
// 相當(dāng)于在代理代理類里執(zhí)行了super(xxx);
// 但是,但是,但是,此時的this依舊是代理類
//這個事實上上調(diào)用的是本身的方法 最終會再次被調(diào)用到下面的 resolveBeanReference 方法
//這里的設(shè)計很奇妙 為什么這么說呢?
//了解這個方法首先要對cglib有一個基礎(chǔ)的認(rèn)識 為什么這么說唄?
//首先要明白 cglib是基于子類集成的方式去增強的目標(biāo)方法的
//所以在不進(jìn)行增強的時候就可以以很輕松的調(diào)用父類的原始方法去執(zhí)行實現(xiàn)
//當(dāng)前調(diào)用的方法和調(diào)用的方法是一個方法的時候 就直接調(diào)用cglib父類 也就是原始類的創(chuàng)建方法直接創(chuàng)建
//當(dāng)不一樣的時候 會進(jìn)入到下面的方法 直接由beanFactory返回 精妙?。?br/> return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//方法里調(diào)用的實例化方法會交給這里來執(zhí)行
//這一步的執(zhí)行是真正的執(zhí)行方式,當(dāng)發(fā)現(xiàn)該方法需要代理的時候不調(diào)用父類的原始方法
//而是調(diào)用我需要代理的邏輯去返回一個對象,從而完成對對象的代理
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
乍一看,是不是好多,沒事我們一點一點分析:
if (isCurrentlyInvokedFactoryMethod(beanMethod))
這個判斷是很重要的!他就是從 ThreadLocal
里面取出本次調(diào)用的工廠方法,前面提到過很多次工廠方法,什么是工廠方法?就是你寫的那個@Bean對應(yīng)的方法,我們就叫做工廠方法,我們以上面 開篇一問
里的那個代碼為例!UserServiceImpl
的時候,會先存儲當(dāng)前的方法對象也就是
UserServiceImpl
的方法對象,也就是放置到
ThreadLocal
里面去!ThreadLocal
里面的方法是一致的,然后就放行,開始調(diào)用真正的
userService()
方法,執(zhí)行這個方法的時候,方法內(nèi)部調(diào)用了
testService();
方法!testService()
又是一個代理對象,于是又走代理邏輯,然后走到這個判斷,判斷發(fā)現(xiàn)當(dāng)前攔截的方法是
testService
而ThreadLocal里面的方法卻是
userService
,此時判斷就失敗了,于是就走到另外一個分支!return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
這個是當(dāng)攔截的方法是工廠方法的時候直接放行,執(zhí)行父類的邏輯,為什么是父類!Cglib是基于繼承來實現(xiàn)的,他的父類就是原始的那個沒有經(jīng)過代理的方法,相當(dāng)于調(diào)用 super.userService()
去調(diào)用原始邏輯!resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
這個也是一會我們要看的代碼邏輯,這個就是當(dāng)判斷不成立,也就是發(fā)現(xiàn)工廠方法里面還調(diào)用了另外一個工廠方法的時候,會進(jìn)入到這里面!那我們看一下這里面的邏輯吧!resolveBeanReference方法邏輯
private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
ConfigurableBeanFactory beanFactory, String beanName) {
。。。。。。。。。忽略不必要代碼。。。。。。。。。
//通過getBean從容器中拿到這個實例
//這個beanFactory是哪里來的,就是第一個攔截器里面注入的`$$beanFactory`
Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
beanFactory.getBean(beanName));
。。。。。。。。。忽略不必要代碼。。。。。。。。。
return beanInstance;
}
}
這里面的主要邏輯就是從beanFactory里面獲取這個方法對應(yīng)的bean對象,直接返回!而不是再去調(diào)用對應(yīng)的方法創(chuàng)建!這也就是為什么多次調(diào)用,返回的實例永遠(yuǎn)只是一個的原因!
整個過程比較繞,讀者可以自己跟著文章調(diào)試一下源碼,相信經(jīng)過過深度思考,你一定有所收獲!
整個過程分為兩大部分:
@Configuration
注解的配置類!以上就是Spring中的@Configuration配置類是怎樣的,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。