JAVA动态代理详解

1 动态代理和静态代理

1.1 静态代理

代理模式最主要的就是有一个公共接口(Subject),一个具体的类(RealSubject),一个代理类(Proxy),代理类持有具体类的实例。当Client调用时接口时,实际是Proxy代为执行具体类的实例方法(request)。


image.png

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
静态代理在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

1.2 动态代理

相比于静态代理来说,动态代理更加灵活。不需要针对每个目标类都单独创建一个代理类,并且也不需要实现接口直接代理实现类。动态代理类的字节码在程序运行时,运用反射机制动态创建而成。
动态代理在日志、监控、事务中,主流框架中都有着广泛的应用。动态代理一般有两种实现方式:

  • JDK动态代理:利用接口实现代理
  • CGLIB动态代理:利用继承的方式实现代理

2 JDK动态代理类

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
调用处理器
这样对目标类中的所有方法的调用都会变为对invoke的调用,可以在invoke方法中添加统一的处理逻辑

创建一个动态代理对象步骤主要有两步:

2.1 创建InvocationHandler调用处理器

每个目标类(被代理类)都有一个与之关联的 InvocationHandler 实现类(调用处理器);如果目标类的方法被调用,那么代理便会转发给 InvocationHandler 实现类(调用处理器)的invoke方法。

public interface InvocationHandler {
    Object invoke(Object proxy, Method var2, Object[] args) throws Throwable;
}

在此接口中只有一个 invoker 方法。该方法里有三个参数:

  • proxy 代理对象
  • method 目标类的方法
  • args 目标类方法的参数
public class InvocationHandlerImpl implements InvocationHandler {
    // 目标类(被代理类)
    private Object target;

    public TargetInvoker(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行前...");
        Object result = method.invoke(target, args);
        System.out.println("执行后");
        return result;
    }
}

2.2 创建代理对象

代理对象由Proxy 类进行创建,代理类继承了Proxy类。

public class Proxy implements Serializable {
   // 静态方法,返回的是代理类,代理类是Proxy的子类
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
        Objects.requireNonNull(h);
        Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass();
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
        return newProxyInstance(caller, cons, h);
    }
}

newProxyInstance方法中的三个参数

  • loader 代理类的类加载器
  • interfaces 代理类要实现的接口(需要被代理的目标类方法)
  • h 目标类所关联的调用处理器(InvocationHandler)

2.3 实现示例

代理的接口
public interface Hello {
    public void sayHello();
}
代理的目标类
public class HelloService implements Hello {
    public void sayHello() {
        System.out.println("Hello World...");
    }
}
调用处理器实现类
public class HelloInvocationHandler implements InvocationHandler {
    private Object target;
    public HelloInvocationHandler(Object target) {
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("before hello ... ");
        Object result = method.invoke(this.target, args);
        System.out.println("after hello... ");
        return result;
    }
}
代理类
   @Test
    public void proxyHello() throws Exception {
        //1. 创建被代理的目标对象
        HelloService helloService= new HelloService();
        //2. 创建调用处理器
        HelloInvocationHandler handler = new HelloInvocationHandler(new HelloService());
        //3. 获取对应的 ClassLoader
        ClassLoader classLoader = helloService.getClass().getClassLoader();
        //4. 获取所有接口的interface
        Class[] interfaces = helloService.getClass().getInterfaces();
        //5. 创建代理类
        Hello hello = (Hello) proxy.proxyInstance(classLoader, interfaces, handler);
        //5. 调用方法
        hello.sayHello();
    }

2.4 原理

Proxy#newProxyInstance

Proxy类的newProxyInstance方法创建了一个动态代理对象。主要的代码逻辑如下:

// 1. 获取代理类
 Class<?> cl = getProxyClass0(loader, intfs);
...
 // 2. 获取代理类的构造方法
 final Constructor<?> cons = cl.getConstructor(constructorParams);
...
// 3. InvocationHandler 对象入参,反射调用构造方法生成动态代理对象
 final InvocationHandler ih = h;
 return cons.newInstance(new Object[]{h});
getProxyClass0方法

上面步骤中最为核心的是 获取代理类的getProxyClass0方法。getProxyClass0的主要逻辑如下:

  • 从一个 WeakCache 中去获取代理类,如果缓存中存在代理类则从缓存直接获取;
  • 如果不存在,则通过代理类工厂创建代理类
代理类的字节码生成

代理类的产生就是整个动态代理的关键。其步骤如下:

  • 首先通过指定的类加载器去验证目标接口是否可被其加载
  • 通过接口所在包等条件决定代理类所在包及代理类的全限定名称,代理类名称是包名+$Proxy+id
  • 通过 ProxyGenerator.generateProxyClass() 生成字节码数组,然后调用 native 方法 defineClass0() 将其动态生成的代理类字节码加载到内存中
生产的proxy文件

我们可以通过下面的方法将动态生成的类输出成 class 文件:

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", HelloService.class.getInterfaces());
        String path = "C:/dynamic//proxy/HelloProxy.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("write success...");
        } catch (Exception e) {
           System.out.println("write fail...");
        }

使用反编译工具对这个class文件进行反编译, 可以查看代理类的源码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;

public final class $Proxy0 extends Proxy implements Hello
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  * 生成代理类的构造方法,方法参数为InvocationHandler
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //静态块
   static
  {
    try
    {
      //sayHello通过反射得到的名字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("com.proxy.Hello").getMethod("sayHello", 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());
    }
  }
 
  /**
  *这里调用代理对象的sayHello方法,直接就调用了InvocationHandler中的invoke方法
  */
  public final void sayHello()
    throws 
  {
    try
    {
      // m3 即为sayHello
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

   //...
}

从源码可以看出,通过 static 代码块将被代理类中每一个方法封装为 Method 对象;代理类对象构造时传入了InvocationHandler,执行同名方法时,通过InvocationHandler的invoke(方法,完成动态代理。

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

推荐阅读更多精彩内容

  • 后端Java编程 创建时间:2018-12-21 01:46 字数:5,622阅读:1706评论:[http://...
    idaretobee阅读 435评论 0 0
  • 一,打破砂锅问到底 什么事代理模式? 什么是静态代理,有啥缺陷? 什么是动态代理? JDK动态代理是如何动态生成类...
    JayDroid阅读 948评论 0 58
  • 动态代理使用方法 无论是jdk的还是cglib的,基本使用方法都一样.即Proxy代理类提供基础的几个方法. 1....
    lmxy1990阅读 337评论 0 0
  • 俗话说:Coder不知动态代理,走在路上没人理!!!所以本文尝试说明白java代理模式,代理中的静态代理和动态代理...
    chanyi阅读 5,294评论 1 5
  • 其他更多java基础文章:java基础学习(目录) 经过上一节我们讲了Class对象和反射机制,这节就来讲一下反射...
    Hiwayz阅读 1,064评论 0 6