javascript
beanpostprocessor使用场景_Spring因动态代理使用不注意导致的诡异现象
筆者在新的定時(shí)任務(wù)項(xiàng)目中,限定一個(gè)類只能寫一個(gè)Job,類似于寫腳本,一個(gè)Job一個(gè)腳本。對(duì)于簡(jiǎn)單的任務(wù)我們并不約定一定要有Service層,但在Job中我們可能需要將某些數(shù)據(jù)庫(kù)操作放到事務(wù)中執(zhí)行,為讓注解事務(wù)生效,我們不能直接使用this調(diào)用事務(wù)方法。
調(diào)用本類事務(wù)方法有兩種方式可以讓注解事務(wù)生效:
一是通過在類中注入自己,也就是循環(huán)依賴注入;
二是在需要時(shí)再?gòu)腷ean工廠中獲取bean;
假設(shè)現(xiàn)有類A,在類A的methodA方法中,先從Spring的bean工廠獲取到類A的實(shí)例,再調(diào)用類A的methodB方法,這樣做的目的是使事務(wù)生效。代碼如下:
@Componentpublic class ProxyObjFieldNpe {
@Value("${field_value}")
private String fieldValue;
public void methodA() {
if (fieldValue == null) {
System.out.println("methodA NPE...");
}
// 從bean工廠取,使AOP生效
ProxyObjFieldNpe thisRef = OnionXxlJobApplicationContent.getBean(ProxyObjFieldNpe.class);
// ......調(diào)用某些事務(wù)方法
thisRef.methodB();
}
private void methodB() {
if (fieldValue == null) {
System.out.println("methodB NPE...");
}
}
}
外部調(diào)用methodA方法:proxyObjFieldNpe.methodA();
結(jié)果輸出的是:"methodB NPE..."
為什么methodA方法獲取到fieldValue字段的值不為空,而methodB方法獲取到的fieldValue卻為空呢?這就是筆者遇到的問題。細(xì)心的朋友,你有沒有看出原因呢?
實(shí)際項(xiàng)目中調(diào)試的結(jié)果截圖如下:
圖中AutoCloseTimeoutOrderJob實(shí)例的字段都為空,這些字段都是聲明自動(dòng)注入的Mapper與Service,不可能為空。但從調(diào)試結(jié)果我們可以看出,從bean工廠獲取到的是AutoCloseTimeoutOrderJob的代理對(duì)象,并非AutoCloseTimeoutOrderJob。
在ProxyObjFieldNpe的例子中,我們從bean工廠獲取到的也是ProxyObjFieldNpe的代理對(duì)象,該代理對(duì)象繼承ProxyObjFieldNpe。因此,與上面截圖一樣,代理對(duì)象的字段都是NULL。
外部調(diào)用ProxyObjFieldNpe的methodA方法調(diào)用的是代理類的methodA方法,那為什么methodA方法拿到字段的值非空,而methodB方法拿到的是空值呢?
因?yàn)閙ethodB方法被聲明為private了,代理類沒法重寫該方法。因此thisRef.methodB();實(shí)際調(diào)用的是代理類父類的methodB方法。methodB方法中獲取fieldValue獲取的是代理類對(duì)象的,這就是methodA方法獲取到fieldValue字段的值不為NULL,而methodB方法獲取到fieldValue字段的值為NULL的原因。
“methodB方法中獲取fieldValue獲取的是代理類對(duì)象的 ",這句我們稍后從字節(jié)碼層面理解。
問題:
為什么methidB方法的訪問標(biāo)志是private,代理對(duì)象是ProxyObjFieldNpe的子類,卻能調(diào)用其父類的methidB方法?
為什么代理對(duì)象的字段為NULL?
因?yàn)檎{(diào)用訪問標(biāo)志為private的methodB方法是在ProxyObjFieldNpe類的methodA方法中調(diào)用的,而不是在代理類的methodA方法中調(diào)用的,內(nèi)部調(diào)用當(dāng)然有訪問權(quán)限。
代理類繼承ProxyObjFieldNpe,外部調(diào)用代理類的methodA方法時(shí),最終經(jīng)過方法攔截器調(diào)用代理類父類的methodA方法,因此methodA方法中調(diào)用methodB方法實(shí)際上是在父類中調(diào)用的。
ProxyObjFieldNpe的methodA方法編譯后生成的字節(jié)碼如下(部分):
15: ldc #6 // class com/wujiuye/test/ProxyObjFieldNpe17: invokestatic #7 // Method com/wujiuye/test/OnionXxlJobApplicationContent.getBean:(Ljava/lang/Class;)Ljava/lang/Object;
20: checkcast #6 // class com/wujiuye/test/ProxyObjFieldNpe
23: astore_1
24: aload_1
25: invokespecial #8 // Method com/wujiuye/test/ProxyObjFieldNpe.methodB:()V
偏移量為15、17、20三條指令是:從bean工廠獲取代理bean,并使用checkcast指令將代理對(duì)象類型強(qiáng)制轉(zhuǎn)為父類類型。
偏移量為24、25兩條字節(jié)碼實(shí)現(xiàn)調(diào)用methodB方法,非靜態(tài)方法的第一個(gè)隱式參數(shù)為this引用,此處傳的是代理類對(duì)象的引用,因此在methodB方法中,使用this(代理對(duì)象的引用)獲取到的字段都是空的。
為什么代理對(duì)象的字段為NULL?如果熟悉Spring Bean生命周期,那么就不難理解。
bean的創(chuàng)建過程如下:
1、反射創(chuàng)建bean;
2、為bean注入屬性;
3、調(diào)用*Aware接口的方法;
4、調(diào)用BeanPostProcessor的postProcessBeforeInitialization方法;
5、調(diào)用初始化方法,afterPropertiesSet或自定義的初始化方法;
6、調(diào)用BeanPostProcessor的postProcessAfterInitialization方法;
代理對(duì)象是在上述步驟的第六步創(chuàng)建的,即調(diào)用某個(gè)BeanPostProcessor的postProcessAfterInitialization方法之后,返回代理對(duì)象,如果是單例對(duì)象,則會(huì)將該對(duì)象保存到bean工廠(容器)中。也就是說,bean工廠中存儲(chǔ)的是代理對(duì)象。
下面兩張圖是我在項(xiàng)目中調(diào)試Spring代碼的截圖。(圖中的小紅點(diǎn)下方有個(gè)問號(hào),這是條件斷點(diǎn),只有滿足條件時(shí)才會(huì)停在斷點(diǎn)處。條件的設(shè)置可右擊小紅點(diǎn),在彈出框中輸出條件,條件的編寫與在代碼中添加一個(gè)if語句是一樣的。)
在調(diào)用BeanPostProcessor的postProcessAfterInitialization方法之前,?bean還是原生的bean。
在調(diào)用BeanPostProcessor的postProcessAfterInitialization方法之后,bean已經(jīng)變成代理對(duì)象了。
因此,使用cglib生成的代理對(duì)象? (繼承方式),在父類中,通過代理對(duì)象調(diào)用父類私有方法不會(huì)報(bào)錯(cuò),但字段都是空的。
公眾號(hào):Java藝術(shù)掃碼關(guān)注最新動(dòng)態(tài)總結(jié)
以上是生活随笔為你收集整理的beanpostprocessor使用场景_Spring因动态代理使用不注意导致的诡异现象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工行车主信用卡调额
- 下一篇: gradle idea java ssm