1. 程式人生 > >關於JDK動態代理方法的呼叫

關於JDK動態代理方法的呼叫

為什麼想到這個?來源於http://blog.csdn.net/y943623901/article/details/50847334

這篇文章關於JDK動態代理的演示讓我有點迷惑,又重新看了下動態代理

1.首先JDK動態代理是針對介面的,
用提供的被代理物件獲得該物件所有實現了的介面,重新生成的一個類

針對這個,像spring註解事務應該放在介面的方法上,這個代理類實現介面方法時是可以獲得介面上方法的註釋的

2.仔細想,在invoke方法裡執行method.invoke(target, args),即使裡面有巢狀方法,也是通過target物件呼叫的,而不是代理物件,所以巢狀方法上的事務註解是無效的

3.為什麼CGLIB裡的巢狀方法有效

// CGLIB代理類具體實現
public class HelloConcrete$$EnhancerByCGLIB$$e3734e52  extends HelloConcrete
  implements Factory
{
  ...
  private MethodInterceptor CGLIB$CALLBACK_0; // ~~
  ...
   
  public final String sayHello(String paramString)
  {
    ...
    MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      // 將請求轉發給MethodInterceptor.intercept()方法。
      return (String)tmp17_14.intercept(this, 
              CGLIB$sayHello$0$Method, 
              new Object[] { paramString }, 
              CGLIB$sayHello$0$Proxy);
    }
    return super.sayHello(paramString);
  }
  ...
public class BookFacadeCglib implements MethodInterceptor {  
	   
    private Object target;  
   
    /** 
    * 建立代理物件 
    *  
    * @param target 
    * @return 
    */  
    public Object getInstance(Object target) {  
       this.target = target;  
       Enhancer enhancer = new Enhancer();  
       enhancer.setSuperclass(this.target.getClass());  
       // 回撥方法  
       enhancer.setCallback(this);  
       // 建立代理物件  
       return enhancer.create();  
    }  
   
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
       Annotation annotation = obj.getClass()  
                                  .getSuperclass()  
                                  .getDeclaredMethod(method.getName(), method.getParameterTypes())  
                                  .getAnnotation(Transaction.class);  
       System.out.println("invoke");  
       if (annotation == null) {  
           proxy.invoke(target, args);  
       } else {  
           System.out.println("事務開始");  
           proxy.invokeSuper(obj, args);  //這裡明顯去呼叫object父類也就是被代理物件的方法
           System.out.println("事務結束");  
       }  
       return null;  
    }

}  
可以看到,CGLIB生成的類為原始類的子類,第一次經過的方法會列印一次invoke,然後呼叫原物件方法,如果這個方法裡又嵌套了方法,這時,子類也複寫了這個方法,所以仍然通過代理物件呼叫,所以會再走一次invoke


4.我們在自定義連線池,建立時動態代理要求用這種new Class[]{Connection.class} 方式獲取介面集合,原因在於,資料庫驅動返回的connection物件繼承了某個類,而這個類或這個類的父類之前實現的介面通過connection物件的getInterfaces()是獲取不到的,可以測試下,那麼問題來到了,如果這個Class陣列是個空陣列,建立的代理物件是怎樣的

// 對con建立其代理物件

Connection proxy = (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(),    // 類載入器

//con.getClass().getInterfaces(),  

new Class[]{Connection.class},      // 目標物件實現的介面

new InvocationHandler() {

	for(Class c:con.getClass().getInterfaces())
		{
		  System.out.println(c);
		}       

    System.out.println(con.getClass().getInterfaces().length);// 這邊輸出是0 

})


4.具體不做分析,主要追究代理物件生成的原始碼,一般情況下,interface[]length不為0,生成的代理類如下


而,像connection這種集合為0的情況,生成的類,只會$Proxy0 extends Proxy ,不實現任何介面,這樣自然不能像介面轉型,會報cast異常

main方法中加入System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"),這樣就會把生成的代理類Class檔案儲存在本地磁碟上,然後再反編譯可以得到代理類的原始碼


System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");  --該設定用於輸出cglib動態代理產生的類
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");   --該設定用於輸出jdk動態代理產生的類