今天我們來聊一聊Java的動態代理模式,這個在很多開源庫中用的比較多的。要講到動態代理我們要先簡單講下靜態代理,一步步遞進。
代理模式其實很常見,比如我們在用第三方庫的時候,不方便去更改代碼,那么我們可以在第三方實例的外面再包一層,在調用第三方實例的方法之前可以做一些準備工作。比如AIDL中Proxy,在調用Sub的onTransact方法之前會先從Parcel中讀取參數,做些準備工作。這樣講可能比較抽象,我們舉個栗子來看看靜態代理。
1.靜態代理
首先是兩個接口:
public interface Fly {
public void fly();
}
public interface Run {
public void run();
}
真實類,這個是真正做工作的地方,實現Fly和Run接口:
public class Animal implements Fly, Run{
public static final String TAG = "ProxyTest";
@Override
public void fly() {
Log.i(TAG, "Animal fly");
}
@Override
public void run() {
Log.i(TAG, "Animal run");
}
}
現在如果要在fly()和run()方法調用之前做些準備工作該怎么辦呢?我們可以實現一個代理類。其實真正做工作的還是animal,只不過用來靜態的代理模式后,我們可以在調用方法或者之后做些其他的工作。
public class AnimalProxy implements Fly, Run{
private Animal animal;
public AnimalProxy(Animal animal) {
this.animal = animal;
}
@Override
public void fly() {
Log.i(Animal.TAG, "In AnimalProxy fly");
animal.fly();
}
@Override
public void run() {
Log.i(Animal.TAG, "In AnimalProxy run");
animal.run();
}
}
運行結果:
靜態代理比較簡單,我們來看看Java的動態代理。
2.Java動態代理
在靜態代理中可以看到Proxy這個類做的工作無非就是在RealSubject的工作之前或之后插入其他的業務代碼,如果每次都手動去實現Proxy類會比較繁瑣,Java中引入了第三個類InvocationHandler,用來統一映射Proxy方法調用到RealSubject上。通過newProxyInstance可以幫助我們動態生成Proxy類實例。
在具體的例子之前我們需要了解下動態代理常用的兩個方法:
JDK通過java.lang.reflect.Proxy包支持動態代理,一般情況下我們使用
//返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序InvocationHandler
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler)
對于InvocationHandler,我們需要實現invoke方法,invoke方法根據代理類傳遞給自己的method參數來區分是什么方法
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable
在代理實例上處理方法調用并返回結果。
還是以上面栗子來做示范,Fly接口和Run接口不變,需要添加InvocationHandler的實現:
public class ProxyHandler implements InvocationHandler{
private Animal mAnimal;
public ProxyHandler(Animal animal){
mAnimal = animal;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("invoke" + method.getName());
return method.invoke(mAnimal, args);
}
}
再就是生成代理類對象:
public static void main(String[] args) {
Animal animal = new Animal();
ClassLoader classLoader = animal.getClass().getClassLoader();
Class[] interfaces = animal.getClass().getInterfaces();
ProxyHandler proxyHandler = new ProxyHandler(animal);
ProxyUtils.generateClassFile(animal.getClass(), "AnimalProxy");
Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, proxyHandler);
Fly fly = (Fly)newProxyInstance;
fly.fly();
Run run = (Run)newProxyInstance;
run.run();
}
可以看出來核心代碼就是
Proxy.newProxyInstance(classLoader, interfaces, proxyHandler);
我們分別來講解下這三個參數分別是什么作用
classLoader:類加載器,我們手動寫的都是java文件,需要編譯成class文件,這個是遵循JVM規范的二進制文件,然后通過classLoader將class文件加載進內存,生成我們需要的class對象,這個class對象通過反射就可以拿到類的所有信息。在這邊的作用其實就是將Java動態生成的class文件進行加載得到動態代理的class對象,以便后面其他操作。
interfaces:這個就是接口,可以看出無論代理或者RealSubject都是實現同樣的接口,Java替我們動態生成的class文件中的方法其實就是接口中的方法。這個其實也是Java動態代理的缺點,即使RealSubject中聲明的方法,但是接口中沒有聲明該方法,那么在生成的代理中就沒有,也就是動態生成的代理類中只有接口中的方法,這個后面看栗子就清楚了。
proxyHandler:就是InvocationHandler的實現類,集成管理Proxy方法的調用映射到RealSubject中,主要就是在invoke中方法實現。在我們這個栗子就是實現將AnimalProxy方法調用映射到Animal對應的方法上。
我們通過Java ProxyGenerator.generateProxyClass來為了生成.class文件,這個在import sun.misc.ProxyGenerator包下面,在Android Studio中是會編譯出錯,應該跟JVM有關,我換成在Java工程中就沒問題。
public static void generateClassFile(Class clazz,String proxyName)
{
//根據類信息和提供的代理類名稱,生成字節碼
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盤中
out = new FileOutputStream(paths+proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
這樣我們可以在工程的bin目錄下找到.class文件,通過反編譯可以看到.class文件
可以看到:
實現Fly和Run接口;
所有方法都是final;
所有方法的調用都是通過invoke方法實現
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class AnimalProxy
extends Proxy
implements Fly, Run
{
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
public AnimalProxy(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void run()
{
try
{
this.h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void fly()
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m4 = Class.forName("Run").getMethod("run", new Class[0]);
m3 = Class.forName("Fly").getMethod("fly", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
可以看出來AnimalProxy中的方法都是接口中的方法,即使在Animal中加入其它方法,也不會出現在AnimalProxy中,我在Animal 中添加了swim方法,生成的AnimalProxy.class文件是一樣的,小伙伴們可以自己試一下,我這邊就不在貼出代碼了。
public void swim(){
System.out.println("Animal is Swiming");
}
上面代碼運行結果:
invokefly
Animal is Flying
invokerun
Animal is running
總結下,動態代理和靜態代理最大的不同就是Java SDK替我們動態生成了代理類代碼,代理方法的調用最后都通過InvocationHandler映射到具體的實現類中。動態代理在Android中應用也是很多的,比如retrofit中的create方法,這個可以參考我前面的文章:http://www.lxweimin.com/p/4d0eae0bc40c
后面我會再結合反射/注解和動態代理來舉個栗子,歡迎關注。
謝謝!
參考鏈接:
http://blog.csdn.net/luanlouis/article/details/24589193
歡迎關注公眾號:JueCode