Java动态代理机制

1 动态代理原理

Java程序在运行时通过ClassLoader将字节码信息加载进内存,那么在运行期系统中,遵循Java编译系统组织class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样就完成了在代码中,动态创建一个新类的能力了。

在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。

目前通过Java实现动态代理的方式有如下几种:

  1. JDK Proxy,Java原生动态代理实现方案,通过ProxyGenerator更改字节码生成代理。

  2. ASM,一个开源的字节码生成框架,直接操作字节码(需要了解JVM字节码规范)。

  3. Javasist,直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

  4. CgLib,一个强大的,高性能,高质量的代码生成类库,它可以在运行期扩展Java类与实现Java接口(是对ASM的一层封装)。

2 JDK Proxy

JDK通过接口实现动态代理,基本步骤如下:

  1. 定义被代理接口。

  2. 创建接口的实现类。

  3. 创建InvocationHandler实例handler,用来处理代理的所有方法调用。

  4. 调用ProxynewProxyInstance实例化一个proxy对象。

2.1 定义接口
public interface IPay {
    void pay(int money);
}
2.2 创建接口的实现类
public class WeChatPay implements IPay{
    public void pay(int money) {
        System.out.println("wechat pay:"+money);
    }
}
2.3 创建 InvocationHandler 的实现类
public class PayInvocationHandler implements InvocationHandler {
    IPay pay;
    public PayInvocationHandler(IPay pay){
        this.pay = pay;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before proxy");
        Object res = method.invoke(pay,args);
        System.out.println("after prxoy");
        return res;
    }
}

说明:

InvocationHandler只有一个invoke方法,它会回调接口中的所有方法。

2.4 创建代理类
public static void main(String[] args) {
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        IPay proxyPay = (IPay) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{IPay.class},new PayInvocationHandler(new WeChatPay()));
        proxyPay.pay(10);
    }

注意:

  • "sun.misc.ProxyGenerator.saveGeneratedFile属性设置为true可以使代理对象保存到当前项目下。

  • 生成的代理类的类名默认为:com.sun.proxy.$ProxyN ;

2.5 代理类源码

下面为IPay接口的代理类源码:

package com.sun.proxy;

import com.mchen.dynamic.IPay;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

    public $Proxy0(InvocationHandler var1) throws  {
        //传入InvocationHandler对象
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void pay(int var1) throws  {
        try {
            //回调InvocationHandler的方法
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.mchen.dynamic.IPay").getMethod("pay", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
2.6 Proxy的优缺点

优点:

  • 原生JDK实现,不需要任何依赖。

缺点:

  • 只能基于接口实现。

3 CGLIB代理

CGLIB(Code Generation Library),是一个强大的,高性能,高质量的基于ASM的字节码生成库,允许我们在运行时对字节码文件进行修改和动态生成。

CGLIB的创建步骤如下:

  1. 创建需要代理的实现类

  2. 实现MethodInterceptor接口,用来处理 对代理类上所有方法的请求。

  3. 通过Enhancercreate方法生成代理对象。

3.1 创建被代理对象
public class WeChatPay implements IPay{
    public void pay(int money) {
        System.out.println("wechat pay:"+money);
    }
}
3.2 实现MethodInterceptor接口
public class PayMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before cglib");
        Object res = proxy.invokeSuper(obj,args);
        System.out.println("after cglib");
        return res;
    }

说明:

这个接口和JDK动态代理InvocationHandler的功能和角色是一样的。

3.3 创建代理对象
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir"));
        WeChatPay pay = new WeChatPay();
        //cglib 中加强器,用来创建动态代理
        Enhancer enhancer = new Enhancer();
        //设置要创建动态代理的类
        enhancer.setSuperclass(pay.getClass());
        // 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截
        enhancer.setCallback(new PayMethodInterceptor());
        WeChatPay proxy =(WeChatPay)enhancer.create();
        proxy.pay(15);

    }

注意:

  • CGLIB只能代理对象的非final和非static方法。

  • 添加系统属性cglib.debugLocation,可指定CGLIB代理类生成的路径。

3.4 代理类源码

public class WeChatPay$$EnhancerByCGLIB$$39ebddcc extends WeChatPay implements Factory {
    //......省略
    private MethodInterceptor CGLIB$CALLBACK_0;  // Enchaner传入的methodInterceptor
    // ....省略

    public final void pay(int var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        //如果callback为null,先绑定回调函数。
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            //若callback 不为空,则调用methodInterceptor 的intercept()方法
            var10000.intercept(this, CGLIB$pay$0$Method, new Object[]{new Integer(var1)}, CGLIB$pay$0$Proxy);
        } else {
            //如果没有设置callback回调函数,则默认执行父类的方法
            super.pay(var1);
        }
    }
}

注意:

  • CGLIB代理类的名称为:父类父类$$EnhancerByCGLIB$$XXXXX

  • CGLIB代理类会继承被代理的类,实现Factory接口。

3.5 CGLIB优缺点

优点:

  1. 能实现类代理,功能更强大。

缺点:

  1. 需要引入第三方依赖。

4 Javasist代理

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是JBoss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

通过Javasist生成类:

   public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        //创建Programmer类
        CtClass cc= pool.makeClass("com.samples.Programmer");
        //定义code方法
        CtMethod method = CtNewMethod.make("public void code(){}", cc);
        //插入方法代码
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
        cc.addMethod(method);
        //保存生成的字节码
        cc.writeFile(System.getProperty("user.dir"));

    }
4.1 Javasist生成代理

通过Javasist进行源码级别的操作创建WeChatPay的代理类:

public static void mkproxy()throws Exception{
        ClassPool pool = ClassPool.getDefault();

        CtClass cc = pool.makeClass("com.mchen.dynamic.WeChatPayProxy");

        //设置接口
        CtClass interface1 = pool.get("com.mchen.dynamic.IPay");
        cc.setInterfaces(new CtClass[]{interface1});

        //设置Field
        CtField field = CtField.make("private com.mchen.dynamic.IPay pay;", cc);
        cc.addField(field);

        CtClass payClass = pool.get("com.mchen.dynamic.IPay");
        CtClass[] arrays = new CtClass[]{payClass};
        CtConstructor ctc = CtNewConstructor.make(arrays,null,CtNewConstructor.PASS_NONE,null,null, cc);
        //设置构造函数内部信息
        ctc.setBody("{this.pay=$1;}");
        cc.addConstructor(ctc);

        //创建前置方法
        CtMethod beforePay = CtMethod.make("private void beforePay(){}",cc);
        beforePay.setBody("System.out.println(\"javasist before pay\");");
        cc.addMethod(beforePay);

        //创建后置方法
        CtMethod afterPay = CtMethod.make("private void afterPay(){}",cc);
        afterPay.setBody("System.out.println(\"javasist after pay\");");
        cc.addMethod(afterPay);

        //创建 方法
        CtMethod showInfo = CtMethod.make("public void pay(int money) {}", cc);
        showInfo.setBody("{this.beforePay();" +
                "this.pay.pay($1);" +
                "this.afterPay();}");
        cc.addMethod(showInfo);

        //获取动态生成的class
        Class c = cc.toClass();
        //获取构造器
        Constructor constructor= c.getConstructor(IPay.class);
        //通过构造器实例化
        IPay o = (IPay)constructor.newInstance(new WeChatPay());
        o.pay(20);

        cc.writeFile(System.getProperty("user.dir"));

    }

步骤说明:

  1. 构造代理类对象。

  2. 添加代理类实现接口。

  3. 创建代理类构造方法,传入被代理对象。

  4. 创建前置和后置方法。

  5. 重写接口方法。

  6. 通过构造函数创建代理对象。

  7. 输出代理类的源码。

javasist提供了创建代理类更简便的工具类ProxyFactory,使用方法和 JDK Proxy 类似。

4.2 javasist代理的源代码
public class WeChatPayProxy implements IPay {
    private IPay pay;

    public WeChatPayProxy(IPay var1) {
        this.pay = var1;
    }

    private void beforePay() {
        System.out.println("javasist before pay");
    }

    private void afterPay() {
        System.out.println("javasist after pay");
    }

    public void pay(int var1) {
        this.beforePay();
        this.pay.pay(var1);
        this.afterPay();
    }
}

5 ASM代理

ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。


public static void generate()throws Exception{
        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
                "com/mchen/asm/Comparable", null, "java/lang/Object",
                new String[] { "java/lang/Cloneable" });
        cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I",
                null, new Integer(-1)).visitEnd();
        cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I",
                null, new Integer(0)).visitEnd();
        cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I",
                null, new Integer(1)).visitEnd();
        cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo",
                "(Ljava/lang/Object;)I", null, null).visitEnd();
        cw.visitEnd();
        byte[] b = cw.toByteArray();

        Class clazz = new MyClassLoader().defineClass("com.mchen.asm.Comparable",b);

        System.out.println(clazz);
    }

通过ASM生成如下类

public interface Comparable extends Cloneable{
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}
5.1 定义接口
package com.mchen.asm;

public interface Singable {
    void sing();
}

5.2 实现接口
package com.mchen.asm;

public class Singer implements Singable {
    public void sing() {
        System.out.println("I am singing...");
    }
}
5.3 创建代理
    // 生成一个class的字节数组
    public static byte[] generateProxy() throws Exception {

        ClassWriter classWriter = new ClassWriter(0);
        FieldVisitor fieldVisitor;

        MethodVisitor methodVisitor;

       // 定义class版本1.8,访问权限,类名,继承类,实现接口等信息
        classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/mchen/asm/SingerProxy", null, "java/lang/Object", new String[]{"com/mchen/asm/Singable"});

        classWriter.visitSource("SingerAgent.java", null);

        {
        // 定义私有属性
            fieldVisitor = classWriter.visitField(ACC_PRIVATE, "delegate", "Lcom/mchen/asm/Singable;", null, null);
            fieldVisitor.visitEnd();
        }
        {
        // 定义构造器
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Lcom/mchen/asm/Singable;)V", null, null);
            methodVisitor.visitParameter("delegate", 0);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(10, label0);
            //调用Object的构造方法
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(11, label1);
            //设置被代理对象
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitVarInsn(ALOAD, 1);
            methodVisitor.visitFieldInsn(PUTFIELD, "com/mchen/asm/SingerProxy", "delegate", "Lcom/mchen/asm/Singable;");
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLineNumber(12, label2);
            methodVisitor.visitInsn(RETURN);
            Label label3 = new Label();
            methodVisitor.visitLabel(label3);
            //设置本地变量
            methodVisitor.visitLocalVariable("this", "Lcom/mchen/asm/SingerProxy;", null, label0, label3, 0);
            methodVisitor.visitLocalVariable("delegate", "Lcom/mchen/asm/Singable;", null, label0, label3, 1);
            methodVisitor.visitMaxs(2, 2);
            methodVisitor.visitEnd();
        }
        {
        // 定义方法sing
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "sing", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(16, label0);
            //设置前置方法
            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitLdcInsn("ASM before sing");
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(17, label1);
            //调用被代理对象方法
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitFieldInsn(GETFIELD, "com/mchen/asm/SingerProxy", "delegate", "Lcom/mchen/asm/Singable;");
            methodVisitor.visitMethodInsn(INVOKEINTERFACE, "com/mchen/asm/Singable", "sing", "()V", true);
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLineNumber(18, label2);
            //设置后置方法
            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitLdcInsn("ASM after sing");
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            Label label3 = new Label();
            methodVisitor.visitLabel(label3);
            methodVisitor.visitLineNumber(19, label3);
            methodVisitor.visitInsn(RETURN);
            Label label4 = new Label();
            methodVisitor.visitLabel(label4);
            methodVisitor.visitLocalVariable("this", "Lcom/mchen/asm/SingerProxy;", null, label0, label4, 0);
            methodVisitor.visitMaxs(2, 1);
            methodVisitor.visitEnd();
        }
        classWriter.visitEnd();

        return classWriter.toByteArray();
    }

注意:

ASM创建代理对象需要了解JVM字节码,并且编码工作量较大。

5.4 ASM代理源码
package com.mchen.asm;

public class SingerProxy implements Singable {
    private Singable delegate;

    public SingerProxy(Singable delegate) {
        this.delegate = delegate;
    }

    public void sing() {
        System.out.println("ASM before sing");
        this.delegate.sing();
        System.out.println("ASM after sing");
    }
}

Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)_我的程序人生(亦山札记)-CSDN博客_javassist

ASM实现动态代理 - strongmore - 博客园

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

推荐阅读更多精彩内容