JDK動態代理中的問題——調用proxy的toString方法引起的棧溢出

Java代碼  
  1. import java.lang.reflect.InvocationHandler;  
  2. import java.lang.reflect.Method;  
  3. import java.lang.reflect.Proxy;  
  4.   
  5. public class Test {  
  6.   
  7.     public static void main(String[] args) {  
  8.   
  9.         UserManager target = new UserManagerImpl();  
  10.           
  11.         UserManager proxy = (UserManager) Proxy.newProxyInstance(target.getClass().getClassLoader(),  
  12.                 target.getClass().getInterfaces(), new UserManagerProxy(target));  
  13.         proxy.addUser();  
  14.     }  
  15.   
  16. }  
  17.   
  18.   
  19. interface UserManager {  
  20.       
  21.     public void addUser();  
  22. }  
  23.   
  24. class UserManagerImpl implements UserManager {  
  25.   
  26.     @Override  
  27.     public void addUser() {  
  28.         System.out.println("add user...");  
  29.     }  
  30.       
  31. }   
  32.   
  33. class UserManagerProxy implements InvocationHandler {  
  34.   
  35.     private UserManager target;  
  36.       
  37.     public UserManagerProxy(UserManager target) {  
  38.         this.target = target;  
  39.     }  
  40.       
  41.     @Override  
  42.     public Object invoke(Object proxy, Method method, Object[] args)  
  43.             throws Throwable {  
  44.           
  45.         System.out.println("check privilege " + proxy);  
  46.           
  47.         method.invoke(target, args);  
  48.           
  49.         return null;  
  50.     }  
  51.       
  52. }  


在執行代理處理類的System.out.println("check privilege " + proxy);時候,出現了java.lang.StackOverflowError錯誤。原因可以初步定位在proxy的toString方法上。 
但是調試後發現proxy屬於$Proxy0類,而$Proxy0這個Class是運行時生成的類,網上有一個牛人貼出了$Proxy0的源碼: 

Java代碼  
  1. import java.lang.reflect.InvocationHandler;   
  2. import java.lang.reflect.Method;  
  3. import java.lang.reflect.Proxy;   
  4. import java.lang.reflect.UndeclaredThrowableException;  
  5.    
  6. public final class $Proxy0 extends Proxy implements UserManager {  
  7.     private static Method m1;  
  8.     private static Method m0;  
  9.     private static Method m3;  
  10.     private static Method m2;  
  11.   
  12.     static {  
  13.         try {  
  14.             m1 = Class.forName("java.lang.Object").getMethod("equals",  
  15.                     new Class[] { Class.forName("java.lang.Object") });  
  16.             m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
  17.                     new Class[0]);  
  18.             m3 = Class.forName("cn.edu.jlu.proxy.UserManager").getMethod("addUser",  
  19.                     new Class[0]);  
  20.             m2 = Class.forName("java.lang.Object").getMethod("toString",  
  21.                     new Class[0]);  
  22.         } catch (NoSuchMethodException nosuchmethodexception) {  
  23.             throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
  24.         } catch (ClassNotFoundException classnotfoundexception) {  
  25.             throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
  26.         }  
  27.     }  
  28.   
  29.     public $Proxy0(InvocationHandler invocationhandler) {  
  30.         super(invocationhandler);  
  31.     }  
  32.   
  33.     @Override  
  34.     public final boolean equals(Object obj) {  
  35.         try {  
  36.             return ((Boolean) super.h.invoke(this, m1, new Object[] { obj }))  
  37.                     .booleanValue();  
  38.         } catch (Throwable throwable) {  
  39.             throw new UndeclaredThrowableException(throwable);  
  40.         }  
  41.     }  
  42.   
  43.     @Override  
  44.     public final int hashCode() {  
  45.         try {  
  46.             return ((Integer) super.h.invoke(this, m0, null)).intValue();  
  47.         } catch (Throwable throwable) {  
  48.             throw new UndeclaredThrowableException(throwable);  
  49.         }  
  50.     }  
  51.   
  52.     @Override  
  53.     public final String toString() {  
  54.         try {  
  55.             return (String) super.h.invoke(this, m2, null);  
  56.         } catch (Throwable throwable) {  
  57.             throw new UndeclaredThrowableException(throwable);  
  58.         }  
  59.     }  
  60.   
  61.     @Override  
  62.     public void addUser() {  
  63.         try {  
  64.             super.h.invoke(this, m3, null);  
  65.             return;  
  66.         } catch (Error e) {  
  67.         } catch (Throwable throwable) {  
  68.             throw new UndeclaredThrowableException(throwable);  
  69.         }  
  70.   
  71.     }  
  72. }  

可以看到,調用toString方法的時候,調用了h的invoke方法,而h就是InvocationHandler的實例,所以是遞歸調用,所以就會出現上述所說的java.lang.StackOverflowError錯誤。 

這裏順便說一下簡單編寫java動態代理的過程: 

1.定義一個接口I 
2.編寫該接口I的實現類Impl 
3.編寫InvocationHandler接口的實現類H,構造H類對象的時候可以把要代理的對象target傳入,target完成實際的動作。在裏面的invoke方法裏編寫自己想實現的邏輯,然後再調用實際要完成的動作就可以。 
4.調用Proxy.newProxyInstance方法,傳遞的三個參數分別是代理類的類加載器(可以用Impl實例的getClass().getClassLoader()) 
、代理類要實現的接口列表(可以用Impl實例getClass().getInterfaces())、InvocationHandler實現類的實例。 

這樣就生成了$Proxy0類的對象,由於$Proxy0類實現了I接口,所以可以將對象強制轉型成I。 


再說一下Proxy.newProxyInstance方法的實際過程: 
1.使用傳入的InvocationHandler實例參數將Proxy類的h實例初始化,注意,如果傳入空對象的話,會拋出空指針錯誤,即h不能爲空。 
2.運行時生成代理Class,即$Proxy0 
3.利用上面動態生成的$Proxy0類,構造出該類的對象,並返回。 


來源:http://chaisencs.iteye.com/blog/1649826


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章