动态代理

今天我们来聊一聊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();

    }
}

运行结果:

静态代理.png

静态代理比较简单,我们来看看Java的动态代理。

2.Java动态代理

在静态代理中可以看到Proxy这个类做的工作无非就是在RealSubject的工作之前或之后插入其他的业务代码,如果每次都手动去实现Proxy类会比较繁琐,Java中引入了第三个类InvocationHandler,用来统一映射Proxy方法调用到RealSubject上。通过newProxyInstance可以帮助我们动态生成Proxy类实例。

Java动态代理.png

在具体的例子之前我们需要了解下动态代理常用的两个方法:
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文件


动态生成代理文件.png

可以看到:

实现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.jianshu.com/p/4d0eae0bc40c

后面我会再结合反射/注解和动态代理来举个栗子,欢迎关注。

谢谢!

参考链接:
http://blog.csdn.net/luanlouis/article/details/24589193

欢迎关注公众号:JueCode

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容