java 中 JDK 代理模式和 GGLIK 代理模式分別有什麼不同呢?
目的
兩種代理方式都是爲了使得方法的調用具有可拓展性,易拓展型,而使用了不同的方式實現了大致相同的需求,在要求方面也有所不同
代碼邏輯
現在有一個類Methods,繼承於接口IMethods,我們需要拓展的就是這個類中的方法
add 和 mul,接口和類代碼如下。
public interface IMethods {
public int add(int x,int y);
public int mul(int x,int y);
}
public class Methods implements IMethods {
@Override
public int add(int x,int y) {
System.out.println("函數結果爲:"+(x+y));
return x+y;
}
@Override
public int mul(int x,int y) {
System.out.println("函數結果爲:"+(x*y));
return x*y;
}
通過兩種方式來一更簡便地拓展這個類中地的方法內容。
JDK代理模式
自己創建的封裝好的JDK代理工具類:
public class JdkProxyFactory {
static Object object = null;
static public Object get(Object object) throws InstantiationException, IllegalAccessException {
JdkProxyFactory.object = object.getClass().newInstance();
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{
System.out.println(method.getName()+" before!!!!!!!!!!!!!!!!!!!!!!!!");
int re = (int) method.invoke(object, args);
System.out.println(method.getName()+" after!!!!!!!!!!!!!!!!!!!!!!!!");
return re;
}
});
}
}
在測試類中調用:
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
IMethods methods = (IMethods) JdkProxyFactory.get(new Methods());
int re = methods.add(1, 0);
re = methods.mul(1, 0);
}
}
運行結果:
add before!!!!!!!!!!!!!!!!!!!!!!!!
函數結果爲:1
add after!!!!!!!!!!!!!!!!!!!!!!!!
mul before!!!!!!!!!!!!!!!!!!!!!!!!
函數結果爲:0
mul after!!!!!!!!!!!!!!!!!!!!!!!!
代碼邏輯:
JdkProxyFactory 封裝類的 get 函數:static public Object get(Object object)
想要拓展一個已知的類中的方法,只要獲取到這個類的對象,就足以封裝好一個拓展的方法。
JdkProxyFactory.object = object.getClass().newInstance();
將獲取到的對象通過反射創建一個新的對象,兩個對象屬於一個Class,賦值給類中屬性object 。
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),new InvocationHandler(){})
通過java.lang.reflect中的Proxy,也就是代理類,調用其靜態方法產生一個代理類對象,並傳出函數,三個參數分別是類加載方式,實現的接口數組,接口InvocationHandler的實現對象。通過這三個參數即可以產生一個動態代理類,類加載方式並不重要,其實現的接口數組和InvocationHandler的實現對象纔是如何形成一個代理類的關鍵。
通過反編譯方式找到這個動態代理類的代碼。
public final class $Proxy0 extends Proxy
implements IMethods
通過類名即看到了重要的信息:這個類implements於IMethods接口,繼承於Proxy類。也就是需要我們進行代理的類Methods所implements的接口。接口的特性是什麼,其實現類均具有其抽象方法,也就是說,產生的抽象方法從方法數量和名字上來說已經達到了我們的需求。那麼它又是如何對原來的方法中的內容進行拓展的呢?
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
這個是類中的屬性,是不是很熟悉,就是我們在創建動態代理類時傳入的參數InvocationHandler對象,這個屬性有大用處!!!
public final int add(int i, int j)
{
try
{
return ((Integer)super.h.invoke(this, m3, new Object[] {
Integer.valueOf(i), Integer.valueOf(j)
})).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
終於找到了方法中的代碼。
return ((Integer)super.h.invoke(this, m3, new Object[] {
Integer.valueOf(i), Integer.valueOf(j)
})).intValue();
通過一堆亂七八糟的調用大概意思就是調用父類中屬性InvocationHandler中的invoke方法,前面說了,它繼承於Proxy,還有前面的super(invocationhandler);繞了個彎,這個InvocationHandler還是創建動態代理類傳進來的參數啊!!!知道爲啥前面說它很重要了吧。也就是說,到現在位置,方法的執行過程在於我們傳進來的InvocationHandler中的invoke方法。
再找到我們傳入參數時實現的這個invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{
System.out.println(method.getName()+" before!!!!!!!!!!!!!!!!!!!!!!!!");
int re = (int) method.invoke(object, args);
System.out.println(method.getName()+" after!!!!!!!!!!!!!!!!!!!!!!!!");
return re;
}
前面和後面都是我們拓展的輸出語句,而最重要的是調用原方法中的內容:
int re = (int) method.invoke(object, args);
使用反射通過從動態代理類傳過來的method去調用object對象中的這個方法,後面的參數是方法的參數,這個object對象就是前面JdkProxyFactory.object = object.getClass().newInstance();傳遞過來的需要我們進行動態代理的Methods類對象,以此來執行原方法中的內容。
GGLIK代理模式
自己創建的封裝好的GGLIK代理工具類:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import t1.Computer;
public class CGLibProxyFactory {
public static Callback callback = null;
public static Object get(Object object) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
callback = new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println(arg1.getName()+" before!!!!!!!!!!!!!!!!!!!!!!!!");
Object re = arg3.invoke(arg0, arg2);
System.out.println(arg1.getName()+" after!!!!!!!!!!!!!!!!!!!!!!!!");
return re;
}
};
enhancer.setCallback(callback);
return enhancer.create();
}
}
在測試類中調用:
public class Test {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Methods methods2 = (Methods) CGLibProxyFactory.get(new Methods());
int re2 = methods.add(1, 0);
re2 = methods.mul(1, 0);
}
}
運行結果:
add before!!!!!!!!!!!!!!!!!!!!!!!!
函數結果爲:1
add after!!!!!!!!!!!!!!!!!!!!!!!!
mul before!!!!!!!!!!!!!!!!!!!!!!!!
函數結果爲:0
mul after!!!!!!!!!!!!!!!!!!!!!!!!
代碼邏輯
兩種方式的實現細節方面很是相似,我們只通過大致的邏輯來對比:
enhancer.setSuperclass(object.getClass());
這裏傳進來的直接就是Methods對象,而不是它實現的接口,這裏我們在後面比較的時候詳細說。
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println(arg1.getName()+" before!!!!!!!!!!!!!!!!!!!!!!!!");
Object re = arg3.invoke(arg0, arg2);
System.out.println(arg1.getName()+" after!!!!!!!!!!!!!!!!!!!!!!!!");
return re;
}
通過arg3.invoke(arg0, arg2);來還原原方法中的代碼,與上面類似。
比較
1.實現邏輯
JDK是通過動態生成實現目標類實現的接口數組的類,有點繞口,也就是說新的類和目標類實現的接口都一樣,但兩個類直接並沒有直接的關係,新的類繼承自Proxy。
GGLIK是通過生成目標類的子類,子類擁有父類的所有方法,通過這種方式實現所需的功能。
2.使用要求
JDK動態代理方式的實現要求更高,更加複雜,由於它是通過接口來實現方法的拓展,因此如果目標類沒有繼承任何接口,將無法使用這種方式。且即使目標類繼承了接口,如果自身仍有接口中沒有的方法,那麼這些方法也無法拓展,相反GGLIK就沒有這麼多的要求了。
3.獲取形式
從上面兩個類之間的關係,在獲取這些代理類對象的時候要尤其注意,並不能隨意的轉型。
首先兩種方法不管是實現目標類實現的接口還是目標類的子類,總之,它們都實現了接口,因此,用接口類型獲取這些對象是毫無問題的。
而如果用目標類獲取動態類對象的時候,就有問題了,JDK動態代理方式獲取的類與目標類同級,因此不能用目標類獲取動態類。
IMethods methods = (IMethods) JdkProxyFactory.get(new Methods());
IMethods methods2 = (IMethods) CGLibProxyFactory.get(new Methods());
// Methods methods3 = (Methods) JdkProxyFactory.get(new Methods()); //錯誤
Methods methods4 = (Methods) CGLibProxyFactory.get(new Methods());