Java动态代理学习

一.代理模式

      代理模式是一种常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
      代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
      简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途,为目标对象提供额外的访问方式,在不修改目标对象的前提下,扩展目标对象的额外功能。
      用张图来形象的表示一下:

image.png

      代理模式分为两种:静态代理和动态代理

二.静态代理

      由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

1.实例实现

      a.创建接口类:

public interface Person {
    void submitMoney();
}

      b.创建实现类:

public class Student implements Person {
    @Override
    public void submitMoney() {
        Log.e("Seven", "-------submit money!");
    }
}

      c.创建代理类:

/**
 * 代理类,需要实现Person接口
 */
public class StudentProxy implements Person {

    Student stu;

    public StudentProxy(Person st) {
        stu = (Student) st;
    }

    @Override
    public void submitMoney() {
        //通过代理类,可以在调用被代理类方法前执行自己的逻辑
        //addSelfMethod();
        Log.e("Seven", "------this student grows up");
        stu.submitMoney();
    }
}

      d.客户端使用:

//被代理的对象学生tom
Person tom = new Student();
//代理对象,并将tom传给代理对象
Person monitor = new StudentProxy(tom);
//代理对象执行操作
monitor.submitMoney();

      运行结果:

1-20 16:08:21.671 E/Seven   (23703): ------this student grows up
1-20 16:08:21.671 E/Seven   (23703): -------submit money!

      客户端在使用时没有直接通过tom(被代理对象)来执行操作,而是通过Monitor(代理对象)来代理执行了。
      代理模式最主要的就是有一个公共接口(Person),一个具体的实现类(Student),一个代理类(StudentProxy),代理类持有具体实现类的实例,代为执行具体类实例方法。
      上面提到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。比如上述中addSelfMethod()或输出日志。

2.静态代理优缺点:

优点:
      1、业务类student只需要关注业务逻辑本身,保证了业务类的重用性。
      2、客户端Client和业务类student之间没有直接依赖关系,对客户的而言屏蔽了具体实现。
缺点:
      1、代理对象的一个接口只服务于一种接口类型的对象,静态代理在程序规模稍大时就无法使用。
      2、如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

三.动态代理

      代理类在程序运行时创建的代理方式被称为动态代理。 上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

1.实例实现

      a.创建接口类:

public interface Person1 {
    void submitMoney();
}

      b.创建实现类:

public class Student1 implements Person1 {
    @Override
    public void submitMoney() {
        Log.e("Seven", "-------submit money!");
    }
}

      c.创建StuInvocationHandler类实现InvocationHandler接口
      创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法,在invoke方法中执行被代理对象target的相应方法。

public class StuInvocationHandler<T> implements InvocationHandler {
    //被代理对象实例
    T target;
    
    public StuInvocationHandler(T t) {
        target = t;
    }
    
    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.e("Seven", "proxy execute " + method.getName() + " method start");
        //通过反射调用目标类的方法
        Object result = method.invoke(target, args);
        Log.e("Seven", "proxy execute " + method.getName() + " method end");
        return result;
    }
}

      d.客户端使用:

//创建实例对象,该对象就是被代理的对象
Person1 jack = new Student1();
//创建一个与代理对象相关联的InvocationHandler
StuInvocationHandler stuInvocationHandler = new StuInvocationHandler<>(jack);
//创建一个代理对象stuProxy来代理jack,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person1 stuProxy = (Person1) Proxy.newProxyInstance(Person1.class.getClassLoader(),
                new Class<?>[]{Person1.class}, stuInvocationHandler);
//代理对象执行操作
stuProxy.submitMoney();

      运行结果:

1-20 16:08:29.345 E/Seven   (23703): proxy execute submitMoney method start
1-20 16:08:29.345 E/Seven   (23703): -------submit money!
1-20 16:08:29.345 E/Seven   (23703): proxy execute submitMoney method end

      动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。上例中,所有的被代理对象执行的方法都会打印日志,然而只是在invoke()做了很少的代码量,没有在所有的方法中修改代码。
      动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体实现,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理。下面来看一下具体实现。

四.动态代理分析

a.创建动态代理类

      上例中使用Proxy.newProxyInstance(Person1.class.getClassLoader(),new Class<?>[]{Person1.class}, stuInvocationHandler)来创建了动态代理实例,看一下具体实现:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
                                  InvocationHandler h) throws IllegalArgumentException {
        Objects.requireNonNull(h);
        //step 1 对class 进行clone
        final Class<?>[] intfs = interfaces.clone();
        ......

        // step 2 获取到代理类
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            ......
            // step 3 获取到代理类的构造方法
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            ......
            // step 4 通过构造方法创建代理类实例,传入已经创建的InvocationHandler对象
            return cons.newInstance(new Object[]{h});
        } 
        ......
}
b.代理类分析

      从step 2可以看到生成了代理类[可以通过generateProxyClass("$Proxy0", Student.class.getInterfaces())获取到代理类的class文件],

public final class $Proxy0 extends Proxy implements Person1
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  

  //生成代理类的构造方法,方法参数为InvocationHandler类型
  //该参数是在创建实例时step 4 传入的已经创建的StuInvocationHandler
  //代理对象持有一个InvocationHandler对象
  public $Proxy0(InvocationHandler paramInvocationHandler) throws {
    //调用父类Proxy的构造方法
    super(paramInvocationHandler);
  }
 
  ......
  public final void submitMoney() throws {
    try {
      //直接调用InvocationHandler中的invoke方法,并把m3传了进去,invoke()调用的就是m3对应的方法
      //InvocationHandler对象持有一个被代理对象,最终会调用被代理对象的方法
      super.h.invoke(this, m3, null);
      return;
    } catch (Error|RuntimeException localError) {
      throw localError;
    } catch (Throwable localThrowable) {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 .......
 static
  {
    try
    {
      //submitMoney()通过反射得到的名字m3
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("dynamicProxy.Person1").getMethod("submitMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    .....
  }
}

      通过对这个生成的代理类源码的查看,可以看出:
      Proxy0继承了Proxy,实现了自定义的目标接口Person1;
      Proxy0定义了接口Person1中的方法submitMoney(),以及Object对象的几个方法equals()、toString()、hashCode();
      构造方法中传了InvocationHandler对象,并且在定义的方法中调用了它的invoke()方法;
      Proxy0继承了Proxy,因此也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。

关键点 具体流程
创建InvocationHandler接口实现类,并传入目标类对象 实现InvocationHandler类,实现invoke()方法
Proxy.newProxyInstance()创建代理类,并传入InvocationHandler接口实例 1.通过为Prxoy类指定类加载器和一组接口,从而创建动态代理类的字节码,根据字节码创建动态代理类;

2.通过反射机制获取到动态代理类的构造方法;

3.通过构造方法创建代理类实例(传入InvocationHandler接口实例) ;
通过调用代理对象方法来调用目标对象方法 1.动态代理类实现了与目标类一样的接口,并实现了需要目标类对象需要调用的方法;

2.调用方法时,相当于调用父类Proxy的h.invoke()方法;h是在创建代理类时传入的InvocationHandler接口实例;

3.在invoke对象中通过反射机制调用目标类对象的方法;

      动态代理实现的具体过程:
      可以把InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
      代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
      本文参考了java动态代理实现与原理详细分析
      相关实例请看Retrofit2原理分析

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

推荐阅读更多精彩内容