JDK 动态代理

Proxy 和 InvocationHandler

Java 的动态代理需要使用这两个类来实现。这两个类的作用描述如下:

Proxy 的描述

provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

为创建的动态代理的类和实例提供静态方法。并且是所有动态代理类的超类。

InvocationHandler 的描述

Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.

在代理实例上处理方法调用并返回结果。当在与其关联的代理实例上调用方法时,将在 InvocationHandler 上调用此方法。

实现一个动态代理

定义类和接口

// 业务类
public interface Service {
    void add();
}
// 业务类的接口
public class ServiceImpl implements Service {
    @Override
    public void add() {
        System.out.println("It is service impl.");
    }
}

定义代理类

public class ServiceProxy implements InvocationHandler {
        // 被代理的对象
    private Object target;
    public ServiceProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 执行方法调用之前要做的事情
        System.out.println("---- before ----");
        Object res = method.invoke(target, args);
        // 执行方法调用之后要做的事情
        System.out.println("---- after ----");
        return res;
    }

    // 生成代理的对象
    public Object getProxy() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class[] interfaces = target.getClass().getInterfaces();
        // 这里会生成一个代理类,并且返回该代理类的一个实例对象
        return Proxy.newProxyInstance(loader, interfaces, this);
    }
}

使用方式

public class Main {
    public static void main(String[] args) {
            // 被代理的对象
        Service object = new ServiceImpl();
        ServiceProxy serviceProxy = new ServiceProxy(object);
        // 代理的对象
        Service service1 = (Service) serviceProxy.getProxy();
        service1.add();
    }
}

方法调用之后打印的结果如下:

---- before ----
It is service impl.
---- after ----

实现原理

Proxy.newProxyInstance

这个方式是实现代理类的关键。做的事情如下:

  • 首先检查 Proxy.proxyClassCache 是否已有代理类

  • 有:则直接返回;没有:使用内部类 ProxyClassFactory 创建

  • native方法 Proxy.defineClass0 负责字节码加载的实现,并返回对应的Class对象

  • 利用 clazz.newInstance 反射机制生成代理类的对象

ProxyClassFactory 中生成代理类字节码的方法如下:

// proxyName:格式如 "com.sun.proxy.$Proxy0";
// interfaces:代理类需要实现的接口数组;
// accessFlags:代理类的访问标识;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

生成的代理类

我们可以保存生成的字节码到 class 文件,并通过反编译工具得到相应的 java 文件,具体如下:

// 可以看到,代理类的超类为 Proxy ,并且实现了被代理类具有的接口
public final class $proxy0 extends Proxy implements Service {

        // 这里传入了定义的 InvocationHandler 
    public $proxy1(InvocationHandler invocationhandler) {
        super(invocationhandler);
    }

    public final boolean equals(Object obj) {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    // 代理类里面也有一个 add 方法
    // 执行代理对象的方法,就是执行 InvocationHandle 对象的 invoke 方法,传入的参数分别是当前代理对象,当前执行的方法和参数
    public final void add() {
        try {
            // h: 传入的 InvocationHandler 类的实例
            // this: 代理对象
            // 调用的方法
            // 传入的参数(这里没有,所以为 null)
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    static {
        try {
            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("zzzzzz.Service").getMethod("add", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception) {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception) {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

局限性

jdk动态代理是通过反射类 ProxyInvocationHandler 回调接口实现的。过程中要求被代理类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理。这种情况可以使用 cglib 实现。

参考文章

说说Java代理模式

Java高级 ----> Java动态代理的原理

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

推荐阅读更多精彩内容

  • title: Jdk动态代理原理解析 tags:代理 categories:笔记 date: 2017-06-14...
    行径行阅读 19,220评论 3 36
  • 0.前言 本文主要想阐述的问题如下:什么动态代理(AOP)以及如何用JDK的Proxy和InvocationHan...
    SYFHEHE阅读 2,256评论 1 7
  • JDK动态代理详解 java动态代理类 Java动态代理类位于java.lang.reflect包下,一般主要涉及...
    冰火人生阅读 525评论 0 1
  • 动态代理的两种方式JDK动态代理和cglib动态代理在上一篇中动态代理jdk和cglib的区别已经通过实例做了比较...
    激情的狼王阅读 686评论 0 1
  • 从小学传武和各种格斗术,从不排斥哪一种…… 传武可能有很多失传了,或者就是传说…… 这些都无所谓,每天坚持练习,最...
    亮子子阅读 244评论 0 2