代碼
Java代碼??
@Component ??public ?class ?B?{??????void ?test()?{?? ????????System.out.println("hello" );?? ????}?? }??
Java代碼??
@Component ??public ?class ?A?{??????@Autowired ?? ????private ?B?b;?? ????public ?final ?void ?test()?{?? ????????b.test();?? ????}?? }??
?
Java代碼??
@Component ??@Aspect ??public ?class ?MyAspect?{??????@Before ("execution(*?*(..))" )?? ????public ?void ?before()?{?? ?? ????}?? }??
?
Java代碼??
@Configuration ??@ComponentScan ??@EnableAspectJAutoProxy (proxyTargetClass?=?true )??public ?class ?Test?{??????public ?static ?void ?main(String[]?args)?{?? ????????AnnotationConfigApplicationContext?ctx?=?? ????????????????new ?AnnotationConfigApplicationContext(Test.class );?? ????????A?a?=?ctx.getBean(A.class );?? ????????a.test();?? ????}?? }??
?
問題?
1、A通過字段注入方式注入B ;
2、A的test方法是final的,因此該方法不能被代理;
3、被代理的對象的調用順序:
? ? Proxy.test()
? ? ? ?--->Aspect Before/Around Advice
? ? ? ---->Target.test()
? ? ? ---->Aspect After/Around Advice
即當某個目標對象被代理后,我們首先調用代理對象的方法,其首先調用切面的前置增強/環(huán)繞增強,然后調用目標對象的方法,最后調用后置/環(huán)繞增強完成整個調用流程。
?
但是我們知道如果是基于CGLIB的代理:
final的類不能生成代理對象;因為final的類不能生成代理對象;
final的方法不能被代理;但是還是能生成代理對象的;
?
在我們的示例里,A的test方法是無法被代理的,但是A還是會生成一個代理對象(因為我們的切入點是execution(* *(..)),還是可以對如toString()之類的方法代理的):
?
即如果調用a.toString()相當于:
? ?proxy.toString() [com.github.zhangkaitao.A$$EnhancerByCGLIB$$73d79efe]
? ?---->MyAspect.before()?
? ----->target.toString() [com.github.zhangkaitao.A]
?
但是如果調用a.test()相當于:
? ?proxy.test() [com.github.zhangkaitao.A$$EnhancerByCGLIB$$73d79efe]
?
?
當我們直接調用生成的代理對象的test方法。接著會得到空指針異常:
寫道
Exception in thread "main" java.lang.NullPointerException at com.github.zhangkaitao.A.test(A.java:16) at com.github.zhangkaitao.Test.main(Test.java:21) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
從異??梢钥闯鍪茿.test()方法中的b對象是空;
?
但是我們發(fā)現(xiàn)b對象是注入了,但是注入給的是目標對象,而代理對象是沒有注入的,請看debug信息:
?
從上圖可以看出,目標對象的b注入了;而生成的代理對象的b是沒有值的;又因為我們調用“代理對象.final方法()”是屬于編譯期綁定,所以會拋出如上的空指針異常。也就是此問題還是因為對象與方法的綁定問題造成的。
?
調用綁定
所謂調用綁定,即當我們使用“對象.字段”/“對象.方法()”調用時,對象與字段/方法之間是如何綁定的;此處有兩種綁定:編譯期綁定與運行期綁定。
?
編譯期綁定:對象與字段/方法之間的綁定關系發(fā)生在寫代碼期間(即編譯期間),即它們的關系在編譯期間(寫完代碼)就確定了,如:
Java代碼??
public ?class ?StaticBindTest?{??????static ?class ?A?{?? ????????public ?int ?i?=?1 ;?? ????????public ?static ?void ?hello()?{?? ????????????System.out.println("1" );?? ????????}?? ????}?? ????static ?class ?B?extends ?A?{?? ????????public ?int ?i?=?2 ;?? ????????public ?static ?void ?hello()?{?? ????????????System.out.println("2" );?? ????????}?? ????}?? ?? ????public ?static ?void ?main(String[]?args)?{?? ????????A?a?=?new ?B();?? ????????System.out.println(a.i);?? ????????a.hello();?? ????}?? }??
如上代碼將輸出1,即A的i值,而不是B的i值;這就是所謂的編譯期綁定,即訪問的字段/方法綁定到聲明類型上,而不是運行時的那個對象的類型上。
?
還有如:
Java代碼??
public ?class ?StaticBindTest2?{??????static ?class ?A?{?? ????????public ?void ?hello(Number?i)?{?? ????????????System.out.println("Number" );?? ????????}?? ????????public ?void ?hello(Integer?i)?{?? ????????????System.out.println("Integer" );?? ????????}?? ????????public ?void ?hello(Long?i)?{?? ????????????System.out.println("Long" );?? ????????}?? ????}?? ????public ?static ?void ?main(String[]?args)?{?? ????????A?a?=?new ?A();?? ????????Number?i?=?Integer.valueOf(1 );?? ????????Number?l?=?Long.valueOf(1L);?? ????????a.hello(i);?? ????????a.hello(l);?? ????}?? }??
都講輸出Number,而不是Integer和Long;這也是編譯期綁定;即方法參數(shù)綁定時根據(jù)聲明時的類型進行綁定也叫做靜態(tài)綁定/早綁定。
?
如果我們使用“a.hello(null);”調用會發(fā)生什么情況呢?此時就會發(fā)生二義性,即綁定到Integer/Long參數(shù)上都可以的,所以我們應該使用“a.hello((Integer)null);”來強制調用。還有在綁定時都是先子類型(Integer/Long)到父類型(Number)進行綁定。
?
編譯期綁定 :調用的都是聲明的類型的字段/方法或者根據(jù)參數(shù)聲明時類型調用重載方法;靜態(tài)字段/方法、private/final方法、實例對象的字段/重載方法都是編譯期綁定,即除了方法覆蓋都是編譯期綁定;也可以說成除了運行期綁定之外的綁定都是編譯期綁定。為什么這么說呢?接著往下看。
?
運行期綁定“對象.方法()”是根據(jù)程序運行期間對象的實際類型來綁定方法的,如:
Java代碼??
public ?class ?DynamicBindTest?{??????static ?class ?A?{?? ????????public ?void ?hello()?{?? ????????????System.out.println("a" );?? ????????}?? ????}?? ????static ?class ?B?extends ?A?{?? ????????public ?void ?hello()?{?? ????????????System.out.println("b" );?? ????????}?? ????}?? ?? ????public ?static ?void ?main(String[]?args)?{?? ????????A?a?=?new ?B();?? ????????a.hello();?? ????}?? }??
如上代碼將輸出b,即說明了hello()方法調用不是根據(jù)聲明時類型決定,而是根據(jù)運行期間的那個對象類型決定的;也叫做動態(tài)綁定/遲綁定。
?
運行期綁定 :“對象.方法()”是根據(jù)運行期對象的實際類型決定的;即new的哪個對象就綁定該方法到那個對象類型上;只有方法覆蓋是運行期綁定;其他都是編譯期綁定;該機制用于實現(xiàn)多態(tài)。
?
在Java中,除了方法覆蓋是運行期綁定,其他都是靜態(tài)綁定就好理解了。
?
單分派與雙分派
單分派:調用對象的方法是由對象的類型決定的;
多分派:調用對象的方法是由對象的類型決定的和其他因素(如方法參數(shù)類型)決定的;雙分派是多分派的特例。
?
Java是一種單分派語言,可以通過如訪問者設計模式來模擬多分派。
?
比如之前的重載的編譯期綁定,和覆蓋的運行期綁定,都是根據(jù)對象類型(不管是聲明時類型/運行時類型)決定調用的哪個方法;跟方法參數(shù)實際運行時類型無關(而與聲明時類型有關)。
?
接下來看一個雙分派的例子:
Java代碼??
public ?class ?DoubleDispatchTest?{???? ????static ?interface ?Element?{?? ????????public ?void ?accept(Visitor?v);?? ????}?? ????static ?class ?AElement?implements ?Element?{?? ????????public ?void ?accept(Visitor?v)?{?? ????????????v.visit(this );?? ????????}?? ????}?? ????static ?class ?BElement?implements ?Element?{?? ????????public ?void ?accept(Visitor?v)?{?? ????????????v.visit(this );?? ????????}?? ????}?? ?? ????static ?interface ?Visitor?{?? ????????public ?void ?visit(AElement?aElement);?? ????????public ?void ?visit(BElement?bElement);?? ????}?? ?? ????static ?class ?Visitor1?implements ?Visitor?{?? ????????public ?void ?visit(AElement?aElement)?{?? ????????????System.out.println("1A" );?? ????????}?? ????????public ?void ?visit(BElement?bElement)?{?? ????????????System.out.println("1B" );?? ????????}?? ????}?? ?? ????static ?class ?Visitor2?implements ?Visitor?{?? ????????public ?void ?visit(AElement?aElement)?{?? ????????????System.out.println("2A" );?? ????????}?? ????????public ?void ?visit(BElement?bElement)?{?? ????????????System.out.println("2B" );?? ????????}?? ????}?? ?? ?? ????public ?static ?void ?main(String[]?args)?{?? ????????Element?a?=?new ?AElement();?? ????????Element?b?=?new ?BElement();?? ????????Visitor?v1?=?new ?Visitor1();?? ????????Visitor?v2?=?new ?Visitor2();?? ????????a.accept(v1);?? ????????a.accept(v2);?? ????????b.accept(v1);?? ????????b.accept(v2);?? ????}?? }??
此處可以看出如"a.accept(v)",根據(jù)Element類型和Visitor類型來決定調用的是哪個方法。
總結
以上是生活随笔 為你收集整理的一段Spring代码引起的调用绑定总结 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內容還不錯,歡迎將生活随笔 推薦給好友。