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