javascript
beanpostprocessor使用场景_Spring因动态代理使用不注意导致的诡异现象
筆者在新的定時任務項目中,限定一個類只能寫一個Job,類似于寫腳本,一個Job一個腳本。對于簡單的任務我們并不約定一定要有Service層,但在Job中我們可能需要將某些數據庫操作放到事務中執行,為讓注解事務生效,我們不能直接使用this調用事務方法。
調用本類事務方法有兩種方式可以讓注解事務生效:
一是通過在類中注入自己,也就是循環依賴注入;
二是在需要時再從bean工廠中獲取bean;
假設現有類A,在類A的methodA方法中,先從Spring的bean工廠獲取到類A的實例,再調用類A的methodB方法,這樣做的目的是使事務生效。代碼如下:
@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);
// ......調用某些事務方法
thisRef.methodB();
}
private void methodB() {
if (fieldValue == null) {
System.out.println("methodB NPE...");
}
}
}
外部調用methodA方法:proxyObjFieldNpe.methodA();
結果輸出的是:"methodB NPE..."
為什么methodA方法獲取到fieldValue字段的值不為空,而methodB方法獲取到的fieldValue卻為空呢?這就是筆者遇到的問題。細心的朋友,你有沒有看出原因呢?
實際項目中調試的結果截圖如下:
圖中AutoCloseTimeoutOrderJob實例的字段都為空,這些字段都是聲明自動注入的Mapper與Service,不可能為空。但從調試結果我們可以看出,從bean工廠獲取到的是AutoCloseTimeoutOrderJob的代理對象,并非AutoCloseTimeoutOrderJob。
在ProxyObjFieldNpe的例子中,我們從bean工廠獲取到的也是ProxyObjFieldNpe的代理對象,該代理對象繼承ProxyObjFieldNpe。因此,與上面截圖一樣,代理對象的字段都是NULL。
外部調用ProxyObjFieldNpe的methodA方法調用的是代理類的methodA方法,那為什么methodA方法拿到字段的值非空,而methodB方法拿到的是空值呢?
因為methodB方法被聲明為private了,代理類沒法重寫該方法。因此thisRef.methodB();實際調用的是代理類父類的methodB方法。methodB方法中獲取fieldValue獲取的是代理類對象的,這就是methodA方法獲取到fieldValue字段的值不為NULL,而methodB方法獲取到fieldValue字段的值為NULL的原因。
“methodB方法中獲取fieldValue獲取的是代理類對象的 ",這句我們稍后從字節碼層面理解。
問題:
為什么methidB方法的訪問標志是private,代理對象是ProxyObjFieldNpe的子類,卻能調用其父類的methidB方法?
為什么代理對象的字段為NULL?
因為調用訪問標志為private的methodB方法是在ProxyObjFieldNpe類的methodA方法中調用的,而不是在代理類的methodA方法中調用的,內部調用當然有訪問權限。
代理類繼承ProxyObjFieldNpe,外部調用代理類的methodA方法時,最終經過方法攔截器調用代理類父類的methodA方法,因此methodA方法中調用methodB方法實際上是在父類中調用的。
ProxyObjFieldNpe的methodA方法編譯后生成的字節碼如下(部分):
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指令將代理對象類型強制轉為父類類型。
偏移量為24、25兩條字節碼實現調用methodB方法,非靜態方法的第一個隱式參數為this引用,此處傳的是代理類對象的引用,因此在methodB方法中,使用this(代理對象的引用)獲取到的字段都是空的。
為什么代理對象的字段為NULL?如果熟悉Spring Bean生命周期,那么就不難理解。
bean的創建過程如下:
1、反射創建bean;
2、為bean注入屬性;
3、調用*Aware接口的方法;
4、調用BeanPostProcessor的postProcessBeforeInitialization方法;
5、調用初始化方法,afterPropertiesSet或自定義的初始化方法;
6、調用BeanPostProcessor的postProcessAfterInitialization方法;
代理對象是在上述步驟的第六步創建的,即調用某個BeanPostProcessor的postProcessAfterInitialization方法之后,返回代理對象,如果是單例對象,則會將該對象保存到bean工廠(容器)中。也就是說,bean工廠中存儲的是代理對象。
下面兩張圖是我在項目中調試Spring代碼的截圖。(圖中的小紅點下方有個問號,這是條件斷點,只有滿足條件時才會停在斷點處。條件的設置可右擊小紅點,在彈出框中輸出條件,條件的編寫與在代碼中添加一個if語句是一樣的。)
在調用BeanPostProcessor的postProcessAfterInitialization方法之前,?bean還是原生的bean。
在調用BeanPostProcessor的postProcessAfterInitialization方法之后,bean已經變成代理對象了。
因此,使用cglib生成的代理對象? (繼承方式),在父類中,通過代理對象調用父類私有方法不會報錯,但字段都是空的。
公眾號:Java藝術掃碼關注最新動態總結
以上是生活随笔為你收集整理的beanpostprocessor使用场景_Spring因动态代理使用不注意导致的诡异现象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工行车主信用卡调额
- 下一篇: pythonsuper_用__init