代理-jdk动态代理

摘要

根据代理类生成的时机,代理类在运行时生成,为动态代理;
本文介绍:

  1. 如何通过JDK实现的动态代理,并详细介绍使用方式(Proxy, InvocationHandler);
  2. 实现原理ProxyGenerator。

一、代理模式回顾

代理模式

二、基于JDK的动态代理

首先,已有主题接口Subject,以及真正主题对象类 RealSubject:

Subject

/**
 * Subject主题接口
 */
public interface Subject {
    void doTask();
}

RealSubject:

/**
 * 具体类,真正具有接口实现逻辑的类
 */
public class RealSubject implements Subject {
    @Override
    public void doTask() {
        try {
            Thread.sleep(5000L);
            System.out.println("task completed.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这就像在实际编码中,已经有了一个基于面向接口编程,优良设计的组件,组件实际提供功能的是类RealSubject。
现在要求在计算组件核心业务的耗时,我们不能侵入地修改核心业务提供类RealSubject的源码,代理模式就发挥作用了。

静态代理已经介绍过,这里使用基于JDK的动态代理来完成这个需求。

1) 首先创建调用处理器

public class SubjectHandler<T extends Subject> implements InvocationHandler {
    private T subject;
    public SubjectHandler(T subject) {
        this.subject = subject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在代理真实对象前我们可以添加一些自己的操作
        long start = System.currentTimeMillis();
        System.out.println("task begins");
        // 调用真实主题对象方法
        Object invoke = method.invoke(subject, args);
        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("task ends, duration: \t" + (-start + System.currentTimeMillis()) / 1000 + "s");
        return invoke;
    }
}

2) 创建动态代理

public static void main(String[] args) {
        // 我们要代理的真实对象
        Subject realSubject = new RealSubject();
        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new SubjectHandler<>(realSubject);
        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(), handler);
        // 调用代理类方法
        subject.doTask();
    }

三、JDK的动态代理详细

明面上的jdk动态代理功能主要由两个类提供:

  1. InvocationHandler
  2. Proxy

1)InvocationHandler

jdk对java.lang.reflect.InvocationHandler的说明:

每一个动态代理实例都关联一个调用处理器(InvocationHandler),当调用代理实例的方法时,实际调用被封装,转发给给其关联的调用处理器的invoke方法;

在实现上,调用处理器的invoke方法是通过反射的方式调用真实主题对象的方法,因此调用处理器必须持有真实主题对象的引用,这样,动态代理类间接将请求委托给真实主题对象。从而符合代理模式,真实请求通过代理类委托给真实主题对象处理的逻辑。

invoke(Object proxy, Method method, Object[] args)参数说明:

  1. proxy是动态代理实例;
  2. method是Method实例,对应动态代理类实现的接口的方法;
  3. args是调用参数。

2)Proxy

java.lang.reflect.Proxy提供创建动态代理类以及动态代理类实例的静态方法,同时也是其创建出来的所有动态代理类的父类。

动态代理类实例,有两种创建方式:

  1. 直接调用其newProxyInstance(ClassLoader, interfaces, invocationHandler)方法,获得代理类实例;

  2. 调用其getProxyClass(ClassLoader, interface)方法获取动态代理类对象,然后获取动态代理类的带参构造构造器,最后通过反射构造动态代理类实例:

     Class<?> proxyClass = Proxy.getProxyClass(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces());
     Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
     Subject subject  = (Subject) constructor.newInstance(handler);
    

其中:

  1. ClassLoader是加载动态代理类所需的类加载器;
  2. interfaces是动态代理类要实现的接口列表;
  3. invocationHandler是动态代理类关联的调用处理器。

动态代理类是在运行时实现指定接口列表的类,每一个动态代理类都关联一个调用处理器,通过调用处理器,间接将请求委托给真实主题对象处理。

3)动态代理类

在调用处理器的invoke方法中,第一个参数Object proxy是动态代理类实例,通过这个对象,可以一窥动态代理类。

Class<?> proxyClass = proxy.getClass();
System.out.println(proxyClass);
System.out.println(proxyClass.getSuperclass());
System.out.println(Arrays.toString(proxyClass.getInterfaces()));

输出:

class com.sun.proxy.$Proxy0
class java.lang.reflect.Proxy
[interface design.pattern.proxy.Subject]

四、JDK的动态代理原理

jdk动态代理的核心,就在于动态代理类的创建;

抽丝剥茧,发现在Proxy类的内部类 ProxyClassFactory中的apply方法,使用:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);

创建动态代理类的字节码。

那么,可以使用ProxyGenerator手动创建动态代理类的字节码,然后通过字节码反编译,探究动态代理类:

public static void main(String[] args) {
    byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",
            new Class[]{Subject.class}, Modifier.PUBLIC);
    String path = DynamicClassGenTest.class.getResource("").getPath() + "$Proxy0.class";
    File file = new File(path);
    try (FileOutputStream fos = new FileOutputStream(file)) {
        fos.write(classFile);
        fos.flush();
        System.out.println("代理类class文件写入成功");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

反编译字节码文件$Proxy0.class伪代码:

public class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    // equals, toString, hashCode实现
    public final void doTask() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
           // toString method
            m3 = Class.forName("design.pattern.proxy.Subject").getMethod("doTask");
            // hashCode method
        } 
        // catch block
    }
}
  1. 首先,动态代理类实现要代理的接口Subject,继承类Proxy。
  2. 在static代码块中,使用反射,接口Subject的doTask()方法,并使用反射Method引用之;
  3. Subject#doTask()方法的实现,将this(反映到调用处理器中就是实际动态代理对象),反射类Methods实例method(Subject的doTask()方法),调用参数作为参数,调用父类Proxy的InvocationHandler的invoke方法。

缺陷

根据动态代理类的创建原理,不难看出,动态代理类必须实现要代理的接口;因此jdk动态代理只能对接口进行代理,不能代理类。

另外,jdk动态代理基于JAVA反射,这决定了其性能不会太高。

总结

使用JDK动态代理时,通过类Proxy创建动态代理类,每一个动态代理类关联一个调用处理器,调用处理器将请求委托给真正具备处理能力的实际对象。

根据jdk动态代理类的原理,jdk动态代理只能对接口进行代理,不能代理类。

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

推荐阅读更多精彩内容