關於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); } ...
可以看到,CGLIB生成的類為原始類的子類,第一次經過的方法會列印一次invoke,然後呼叫原物件方法,如果這個方法裡又嵌套了方法,這時,子類也複寫了這個方法,所以仍然通過代理物件呼叫,所以會再走一次invokepublic 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; } }
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動態代理產生的類