Java的靜態代理和動態代理
什麼是代理 什麼是代理呢,其實很好理解,就是不直接訪問目標,而是通過一箇中間層來訪問,就好像下面這樣:
Java的靜態代理 舉個例子,如果我們一些水果,比如:香蕉、蘋果等,寫成Java程式碼,大概是下面這個樣子:
//Fruit.java /** * 水果的介面 */ public interface Fruit { /** * 獲取水果的名字 */ public String getName(); } //Apple.java public class Apple implements Fruit { @Override public String getName() { return "蘋果"; } } //Banana.java public class Banana implements Fruit { @Override public String getName() { return "香蕉"; } }
但是吃水果,你要削皮吧,你不能每個水果都寫一個子類,類處理削皮這個事情吧。因此,我們可以做一個代理 ,吃蘋果之前,先把它削皮。 就像下面這樣,把原來的水果包一層:
//PeelFruitProxy.java /** * 代理,讓每個水果去皮 */ public class PeelFruitProxy implements Fruit { private Fruit mFruit; public PeelFruit(Fruit fruit) { this.mFruit = fruit; } @Override public String getName() { System.out.println("proxt:" + proxy.getClass().getName()); return "去皮的" + mFruit.getName(); } }
添加了測試類,測試類如下:
//Main.java public class Main { public static void main(String[] args) { Apple apple=new Apple();//原始的蘋果 Banana banana=new Banana();//原始的香蕉 PeelFruitProxy peelApple=new PeelFruitProxy(apple);//代理,新增去皮功能 PeelFruitProxy peelBanana=new PeelFruitProxy(banana);//代理,新增去皮功能 System.out.println(peelApple.getName()); System.out.println(peelBanana.getName()); } }
以上就是Java的靜態代理,簡單點說,就是把原來的目標物件包一層,加入新東西再去呼叫目標本身。 但是如果只是這樣的靜態代理,一個介面,就需要一個代理,實現起來是不是很繁瑣。
Java的動態代理 在Java中,有一個幹這個事情的類,叫做Proxy,可以直接使用反射方式,代理攔截。 先簡單的介紹一下這個類,其實最常用的只有一個靜態方法Proxt.newProxyInstance(),是這樣的:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
首先我們要實現InvocationHandler,實現其中的invoke方法,在呼叫目標物件的時候,會先呼叫到invoke方法,需要實現者在這個方法中,在主動呼叫被呼叫者方法。
//FruitInvocationHandler.java
/**
* 呼叫方法攔截器
*/
public class FruitInvocationHandler implements InvocationHandler {
private Fruit mFruit;
public FruitInvocationHandler(Fruit fruit) {
this.mFruit = fruit;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String result = (String) method.invoke(mFruit, args);//需要在這個方法裡面,主動呼叫被代理的物件。
return "去皮的" + result;
}
}
執行一下:
//Main.Java
public class Main {
public static void main(String[] args) {
Apple apple = new Apple();
Fruit proxyApple = (Fruit) Proxy.newProxyInstance(Fruit.class.getClassLoader(), new Class[]{Fruit.class}, new FruitInvocationHandler(apple));
System.out.println(proxyApple.getClass().getName());
System.out.println(proxyApple.getName());
Banana banana = new Banana();
Fruit proxyBanana = (Fruit) Proxy.newProxyInstance(Fruit.class.getClassLoader(), new Class[]{Fruit.class}, new FruitInvocationHandler(banana));
System.out.println(proxyApple.getClass().getName());
System.out.println(proxyBanana.getName());
}
}
這個方法,就是生成一個上文中的PeelFruitProxy(當然,我們看到的他名字叫:com.sun.proxy.$Proxy0),動態的生成,避免每次都需要寫,這個也是叫他動態代理的原因,因為我們可以在執行時代理任意類。 很多程式中的AOP就是這樣實現的,但是我們發現一些特點,newProxyInstance()的第二個引數,是一個interfaces的列表,為啥要有這個這個列表呢?
因為我們動態生成的代理類,也需要實現介面,這樣才方便向下轉型,使用其中的方法,不然,生成的類,類名就是com.sun.proxy.$Proxy0這種,並且是在記憶體中,無法呼叫生成的方法。 所以,這種動態代理的方法,有一個致命的缺點,那就是被代理的類,必須要實現介面。
CGLib代理
cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.
另一個大名鼎鼎的Java代理實現,就是CGLib(Code Generation Library),一個基於ASM的程式碼生成框架,可以用他來動態生成類,然後實現對方法的攔截,就可以避開JDK的動態代理, 必須要目標類實現介面的問題了。 也就是說,可以用CGLib來生成上文中的PeelFruitProxy。
簡單介紹一下怎麼用,首先這個CGLib是一個三方的庫,我們要把它依賴進來:
compile 'cglib:cglib:3.2.8'
//FruitMethodInterceptor.java
/**
* CGLib代理的方法攔截器
*/
public class FruitMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String result = (String) proxy.invokeSuper(obj, args);//主要,這裡呼叫的是夫類,也就是說, 生成的類和原始類是繼承關係
return "去皮的"+result;
}
}
//Main.java
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Apple.class);
enhancer.setCallback(new FruitMethodInterceptor());
Apple apple = (Apple) enhancer.create();
System.out.println(apple.getClass().getName());
System.out.println(apple.getName());
}
}
執行效果如下: CGLib代理 我們看到,實現了同樣的功能,但是,Apple已經不是原來的Apple類了,變成了com.zjiecode.learn.java.proxy.Apple44ade224,沒錯,我們正真使用的是這個類,而不是原來的Apple了,這個類繼承自Apple,最後實現了對Apple類的代理。 這種方式,因為使用的是繼承,所以,無需被代理的類實現介面。當然,他也可以通過介面來實現代理。
總結
- 第一種代理,就不說了,只適合單一的一個介面的代理,在編譯時就決定好了。
- 第二、三種代理,都是動態時代理 ,但是我們看到也有差別:
- JDK的動態代理 ,只能實現介面代理,並且是包裝的被代理物件(類的例項),也就是說,在代理的過程中,有2個物件,一個代理物件,一個目標物件,目標物件被包裝在代理物件裡面。
- CGLib的代理,是繼承目標物件,生成了一個新的類,然後來實現代理,這樣,在記憶體中就是有代理物件,沒有目標物件了,使用的是直接整合的方式
- 生成代理類是在執行時,有別於javapoet在編譯時生成類。
參考資料
https://blog.csdn.net/danchu/article/details/70238002
https://blog.csdn.net/lovejj1994/article/details/78080124
https://www.jianshu.com/p/9a61af393e41?from=timeline&isappinstalled=0
https://github.com/cglib/cglib/wiki