今天一個偶然的機會我突然想看看JDK的動態代理,因為以前也知道一點,而且只是簡單的想測試一下使用,使用很快裡就寫好了這麼幾個接口和類:
介面類別:UserService.java
複製代碼代碼如下:
package com.yixi.proxy;
public interface UserService {
public int save() ;
public void update(int id);
}
實作類別:UserServiceImpl.java
複製代碼代碼如下:
package com.yixi.proxy;
public class UserServiceImpl implements UserService {
@Override
public int save() {
System.out.println("user save....");
return 1;
}
@Override
public void update(int id) {
System.out.println("update a user " + id);
}
}
然後猴急猴急的就寫好了自己要的InvocationHandler:這個的功能是很簡單的就是記錄一下方法執行的開始時間和結束時間
TimeInvocationHandler.java
複製代碼代碼如下:
package com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("startTime : " +System.currentTimeMillis());
Object obj = method.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
return obj;
}
}
所有的準備工作都搞好了當然要開始寫測驗了!
Test.java
複製代碼代碼如下:
package com.yixi.proxy;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) { 9 TimeInvocationHandler timeHandler = new TimeInvocationHandler();
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
愉快地Run了一下,不過它並不給你面子結果是滿屏幕的異常:
複製代碼代碼如下:
startTime : 1352877835040
startTime : 1352877835040
startTime : 1352877835040
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at $Proxy0.update(Unknown Source)
在 com.yixi.proxy.Test.main(Test.java:11)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
在 com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)
... 2 more
com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)異常明確告訴了是在TimeInvocationHandle的12行的問題:也就是
複製代碼代碼如下:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("startTime : " +System.currentTimeMillis());
Object obj = method.invoke(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
return obj;
}
從方法來看沒什麼錯誤!因為在invoke()這個方法上貌似提供了method.invoke(Object,Object[])所要的所有的參數,我們會理所當然的去使用它,如果你真那樣想的話那你就中了JDK的陷阱了,先看正確的寫法吧防止有些同學沒心情看後面的至少給個正確的解法:
修改TimeInvocationHandler.java
複製代碼代碼如下:
package com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
private Object o;
public TimeInvocationHandler(Object o){
this.o = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("startTime : " +System.currentTimeMillis());
Object obj = method.invoke(o, args);
System.out.println("endTime : " +System.currentTimeMillis());
return obj;
}
}
修改Test.java
複製代碼代碼如下:
package com.yixi.proxy;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
現在是正確的輸出結果:
複製代碼代碼如下:
startTime : 1352879531334
update a user 2
endTime : 1352879531334
startTime : 1352879531334
user save....
endTime : 1352879531335
如果想程式碼少一點的話可以直接寫匿名類別:
package com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("startTime : " +System.currentTimeMillis());
Object obj = method.invoke(usi, args);
System.out.println("endTime : " +System.currentTimeMillis());
return obj;
}
});
u.update(2);
u.save();
}
}
既然method.invoke(target,args);中第一個參數是傳入的是目標物件那麼invocationHandler的Invoke方法要個Object proxy參數幹嘛? 還是往下看吧!
對於最重要的invoke這個方法(個人覺得)我們看下JDK是怎麼說的:
複製代碼代碼如下:
invoke
Object invoke(Object proxy,
Method method,
Object[] args)
throws Throwable在代理實例上處理方法呼叫並傳回結果。在與方法關聯的代理實例上呼叫方法時,將在呼叫處理程序上呼叫此方法。
參數:
proxy - 在其上呼叫方法的代理實例
method - 對應於在代理實例上呼叫的介面方法的Method 實例。 Method 物件的聲明類別將是在其中聲明方法的接口,該接口可以是代理類別賴以繼承方法的代理接口的超接口。
args - 包含傳入代理實例上方法呼叫的參數值的物件數組,如果介面方法不使用參數,則為null。基本類型的參數被包裝在適當基本包裝器類別(如java.lang.Integer 或java.lang.Boolean)的實例中。
proxy - 在其上呼叫方法的代理實例? 這句話是什麼意思呢? 代理? method是代理的方法? 那我執行代理程式的method不是就應該是Object obj = method.invoke(proxy, args);嗎? 當時我也沒轉過彎來,去討論群,去google都沒找到什麼靈感,想想還是這個看看源碼吧也許能看到點什麼!
開啟Proxy類別的原始碼發現有怎麼一個建構方法:
複製代碼代碼如下:
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
把InvocationHandler作為Proxy的建構方法的參數....那它要InvocationHandler做什麼用呢?跟InvocationHandler中的invoke()方法有什麼關聯嗎?
我第一個想到的是Proxy內部會呼叫下面的語句:
複製代碼代碼如下:
h.invoke(this,methodName,args);
因為總得去呼叫invoke方法才能執行對應的method方法吧,
我們先來看下這個
這裡你會發現看起來有點感覺了:當u.update(2)時àProxy就會呼叫handler.invoke(proxyClass,update,2) à 也就是呼叫了proxyClass.update(2);
當u.save();時àProxy就會呼叫handler.invoke(proxyClass,save,null) à也就是呼叫了proxyClass.save();
當Test.java改成這樣:
複製代碼代碼如下:
public class Test {
public static void main(String[] args) {
final UserServiceImpl usi = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
u.update(2);
u.save();
}
}
注意這時候的匿名類別的方法的回傳的是null,運行一下就會發現:
複製代碼代碼如下:
Exception in thread "main" java.lang.NullPointerException
at $Proxy0.save(Unknown Source)
在 com.yixi.proxy.Test.main(Test.java:17)
17行有空指針也就是這裡的u.save()方法有為null的元素難道是u是空的? 不應該啊如果u是null的話那麼u.update(2)在那裡就會報空指標異常了,當我把17行註解掉以後異常沒了說明u.update()能正常執行。那這到底是為什麼呢?
其實這就是invoke方法回傳null的緣故:
注意一下UserService類別中的兩個方法:
複製代碼代碼如下:
public interface UserService {
public int save() ;
public void update(int id);
}
Save()方法回傳的是int型的而update方法回傳的是void型的;根據上面的猜測是handler.invoke()是實作proxyClass.update(2);的,invoke方法中的return方法的是對應的代理方法的回傳值,
所以在invoke方法回傳null的時候代理的update方法接收到回傳值是null,而它本來就是回傳void 所以沒有報異常, 而代理save必須回傳int型的數值我們這回傳的還是null,JVM無法將null轉化為int型所以就報了異常了這樣解釋就能解釋通了,也能相對證明前面的猜測。
InvocationHandler中invoke方法中第一個參數proxy看似只是為了讓Proxy類別能給自己的InvocationHandler物件的引用呼叫方法時能傳入代理物件proxyClass的引用,來完成proxyClass需要完成的業務。
文采不行!能力有限!希望大家指正...