`

Javassist实现JDK动态代理

    博客分类:
  • Java
阅读更多

提到JDK动态代理,相信很多人并不陌生。然而,对于动态代理的实现原理,以及如何编码实现动态代理功能,可能知道的人就比较少了。接下一来,我们就一起来看看JDK动态代理的基本原理,以及如何通过Javassist进行模拟实现。

JDK动态代理

示例

以下是一个基于JDK动态代理的hello world示例,在很多地方都可以看到类似的版本。

public class DynamicProxyTest {

    interface IHello {
        void sayHello();
    }

    static class Hello implements IHello {
        @Override
        public void sayHello() {
            System.out.println("hello world");
        }
    }

    static class DynamicProxy implements InvocationHandler {

        Object originalObj;

        Object bind(Object originalObj) {
            this.originalObj = originalObj;
            return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("pre method");
            Object result = method.invoke(originalObj, args);
            System.out.println("post method");
            return result;
        }
    }

    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");
        IHello hello = (IHello) new DynamicProxy().bind(new Hello());
        hello.sayHello();
    }
}

生成代理类源码

通过设置参数
sun.misc.ProxyGenerator.saveGeneratedFiles为true,在执行main函数之后,我们将得到一份$Proxy0.class文件,它就是Hello的代理类。

经过反编译,得到$Proxy0的源码(省略了无关内容)如下:

final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }

    public final void sayHello() {
        try {
            this.h.invoke(this, m3, null);
            return;
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals",
                    new Class[] { Class.forName("java.lang.Object") });
            m3 = Class.forName("DynamicProxyTest$IHello").getMethod("sayHello"new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);
        } catch (NoSuchMethodException localNoSuchMethodException) {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}

主要实现原理

  • 动态生成一个代理类,实现IHello接口;
  • 代理类继承Proxy类,提供一个实例变量InvocationHandler h用于保存代理逻辑(此外,Proxy还提供了相关代理类处理逻辑);
  • 代理类声明一系列Method类变量,用于保存接口相关的反射方法;
  • 代理类实现相关接口方法,核心逻辑是调用InvocationHandler的invoke方法,并传入3个参数:当前代理类对象、接口反射方法对象、实际方法参数。

Javassist实现JDK动态代理

前面简单分析了JDK动态代理的基本原理,其中,最核心的逻辑在于如何生成动态代理类,也就是
java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)方法的实现。

接下来我们将通过Javassist一步步实现newProxyInstance方法。

1. 定义接口

接口基本与Proxy.newProxyInstance相同。为简单说明,我们这里只定义了一个接口类型参数Class而不是数组。

public static Object newProxyInstance(ClassLoader loader, Class<?> interfaceClass, InvocationHandler h) {
    ...
}

2. 创建动态代理类

Javassist可以通过简单的Java API来操作源代码,这样就可以在不了解Java字节码相关知识的情况下,动态生成类或修改类的行为。

创建名称为NewProxyClass的代理类。

ClassPool pool = ClassPool.getDefault();
CtClass proxyCc = pool.makeClass("NewProxyClass");

3. 添加实例变量InvocationHandler

添加类型为InvocationHandler的实例变量h。

CtClass handlerCc = pool.get(InvocationHandler.class.getName());
/* 生成代码:private InvocationHandler h; */
CtField handlerField = new CtField(handlerCc, "h", proxyCc);
handlerField.setModifiers(AccessFlag.PRIVATE);
proxyCc.addField(handlerField);

4. 添加构造函数

创建构造函数,参数类型为InvocationHandler。

// 生成构造函数:public NewProxyClass(InvocationHandler h) { this.h = h; }
CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);
ctConstructor.setBody("$0.h = $1;");
proxyCc.addConstructor(ctConstructor);

其中,$0代表this, $1代表构造函数的第1个参数。

5. 实现IHello接口声明

// 生成接口实现声明:public class NewProxyClass implements IHello
CtClass interfaceCc = pool.get(interfaceClass.getName());
proxyCc.addInterface(interfaceCc);

6. 实现IHello相关接口方法

6.1 遍历接口方法

CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
for (int i = 0; i < ctMethods.length; i++) {
    // 核心逻辑在下方
}

6.2 代理方法实现

由于代理类调用invoke方法需要传入接口的反射方法对象(Method),因此,我们需要为每个方法添加一个可复用的Method类变量。

6.2.1 反射方法对象声明及初始化

/* 构造方法参数,如:new Class[] { String.class, Boolean.TYPE, Object.class } */
String classParamsStr = "new Class[0]";
if (ctMethods[i].getParameterTypes().length > 0) {
    for (CtClass clazz : ctMethods[i].getParameterTypes()) {
        classParamsStr = ((classParamsStr == "new Class[0]") ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";
    }
    classParamsStr = "new Class[] {" + classParamsStr + "}";
}
// 字段生成模板
String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
// 根据模板生成方法及参数构造方法字段生成语句
String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);
// 为代理类添加反射方法字段
CtField methodField = CtField.make(methodFieldBody, proxyCc);
proxyCc.addField(methodField);

通过以上逻辑,将生成类似代码如下:

private static Method m0 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello"new Class[0]);
private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2"new Class[] { Integer.TYPE });
private static Method m2 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello3"new Class[] { String.class, Boolean.TYPE, Object.class });

6.2.2 接口方法体实现

// invoke调用逻辑. 其中$args是实际方法传入的参数数组
String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";

// 如果方法有返回类型,则需要转换为相应类型后返回
if (CtPrimitiveType.voidType != ctMethods[i].getReturnType()) {
    // 对8个基本类型进行转型
    // 例如:((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
    if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {
        CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();
        methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ") " + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";
    }
    // 对于非基本类型直接转型即可
    else {
        methodBody = "return (" + ctMethods[i].getReturnType().getName() + ") " + methodBody;
    }
}
methodBody += ";";

/* 为代理类添加方法 */
CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),
        ctMethods[i].getParameterTypes(), proxyCc);
newMethod.setBody(methodBody);
proxyCc.addMethod(newMethod);

通过以上逻辑,将生成类似代码如下:

public void sayHello() {
    this.h.invoke(this, m0, new Object[0]);
}

public String sayHello2(int paramInt) {
    return (String)this.h.invoke(this, m1, new Object[] { new Integer(paramInt) });
}

public int sayHello3(String paramString, boolean paramBoolean, Object paramObject) {
    return ((Integer)this.h.invoke(this, m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
}

7. 生成代理类字节码

以下语句,将生成代理类字节码:D:/tmp/NewProxyClass.class

proxyCc.writeFile("D:/tmp"); // 该步骤可选

8. 生成代理对象

最后,通过调用第3步创建的构造函数,传入InvocationHandler对象,生成并返回代理类。

Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);
return proxy;

完整代码

public static Object newProxyInstance(ClassLoader loader, Class<?> interfaceClass, InvocationHandler h) throws Throwable {
    ClassPool pool = ClassPool.getDefault();

    // 1.创建代理类:public class NewProxyClass
    CtClass proxyCc = pool.makeClass("NewProxyClass");

    /* 2.给代理类添加字段:private InvocationHandler h; */
    CtClass handlerCc = pool.get(InvocationHandler.class.getName());
    CtField handlerField = new CtField(handlerCc, "h", proxyCc);
    handlerField.setModifiers(AccessFlag.PRIVATE);
    proxyCc.addField(handlerField);

    /* 3.添加构造函数:public NewProxyClass(InvocationHandler h) { this.h = h; } */
    CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);
    ctConstructor.setBody("$0.h = $1;"); // $0代表this, $1代表构造函数的第1个参数
    proxyCc.addConstructor(ctConstructor);

    /* 4.为代理类添加相应接口方法及实现 */
    CtClass interfaceCc = pool.get(interfaceClass.getName());

    // 4.1 为代理类添加接口:public class NewProxyClass implements IHello
    proxyCc.addInterface(interfaceCc);

    // 4.2 为代理类添加相应方法及实现
    CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
    for (int i = 0; i < ctMethods.length; i++) {
        String methodFieldName = "m" + i; // 新的方法名

        // 4.2.1 为代理类添加反射方法字段
        // 如:private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2", new Class[] { Integer.TYPE });
        /* 构造反射字段声明及赋值语句 */
        String classParamsStr = "new Class[0]";
        if (ctMethods[i].getParameterTypes().length > 0) {
            for (CtClass clazz : ctMethods[i].getParameterTypes()) {
                classParamsStr = ((classParamsStr == "new Class[0]") ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";
            }
            classParamsStr = "new Class[] {" + classParamsStr + "}";
        }
        String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
        String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);
        // 为代理类添加反射方法字段
        CtField methodField = CtField.make(methodFieldBody, proxyCc);
        proxyCc.addField(methodField);

        System.out.println("methodFieldBody: " + methodFieldBody);

        /* 4.2.2 为方法添加方法体 */
        /* 构造方法体 */
        String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";
        // 如果有返回类型,则需要转换为相应类型后返回
        if (CtPrimitiveType.voidType != ctMethods[i].getReturnType()) {
            // 对8个基本类型进行转型
            // 例如:((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
            if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {
                CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();
                methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ") " + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";
            } else { // 对于非基本类型直接转型即可
                methodBody = "return (" + ctMethods[i].getReturnType().getName() + ") " + methodBody;
            }
        }
        methodBody += ";";
        /* 为代理类添加方法 */
        CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),
                ctMethods[i].getParameterTypes(), proxyCc);
        newMethod.setBody(methodBody);
        proxyCc.addMethod(newMethod);

        System.out.println("Invoke method: " + methodBody);
    }

    proxyCc.writeFile("D:/tmp");

    // 5.生成代理实例. 将入参InvocationHandler h设置到代理类的InvocationHandler h变量
    @SuppressWarnings("unchecked")
    Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);

    return proxy;
}

Javassist官网:http://www.javassist.org/

 

转载请注明来源:http://zhanjia.iteye.com/blog/2428564

 

个人公众号

二进制之路

 

分享到:
评论

相关推荐

    Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

    在Java中,我们可以使用JDK自带的动态代理或者第三方库如CGLIB、Javassist、ASM来实现。 **JDK动态代理**: JDK的动态代理主要依赖于`java.lang.reflect.Proxy`和`java.lang.reflect.InvocationHandler`两个类。...

    动态代理-jdk、cglib、javassist.zip

    本压缩包包含关于三种主要的动态代理实现方式:JDK动态代理、CGLIB以及javassist的相关资料。 首先,JDK动态代理是Java标准库提供的一种动态代理机制,它依赖于java.lang.reflect包中的Proxy和InvocationHandler...

    java jdk 动态代理演示demo2

    Java JDK 动态代理是一种强大的特性,它允许我们在运行时创建代理类,这些代理类可以扩展或修饰已存在的接口实现。动态代理在处理跨层调用、事务管理、日志记录、性能监控等方面非常有用,尤其在AOP(面向切面编程)...

    Javassist18版20版22版的jar包

    - AOP(面向切面编程)框架:如Spring AOP,Javassist可以用来生成代理类以实现切面逻辑。 - 框架开发:许多自定义框架会使用Javassist来实现动态代码生成,比如ORM框架Hibernate。 - 性能监控:可以通过修改字节...

    Java 动态代理.md

    接下来,我们将分别介绍 JDK 动态代理、CGLIB 动态代理、Javassist 代理以及 ASM 代理。 ### JDK 动态代理 JDK 动态代理主要依赖于 `java.lang.reflect.Proxy` 类和 `java.lang.reflect.InvocationHandler` 接口。...

    第6讲 动态代理是基于什么原理1

    2. **字节码操作库动态代理**:如ASM、CGLIB和Javassist等,它们通过直接操作字节码生成代理类,通常比JDK动态代理效率更高,但可能会有更复杂的API。例如,CGLIB是通过继承目标类来创建代理,而ASM和Javassist则...

    javassist-3.7.ga.jar下载

    6. 与JDK动态代理的兼容性:Javaassist可以与JDK的动态代理结合使用,为已经存在的接口提供动态实现。 `javassist-3.7.ga.jar`的下载意味着用户可能需要这个特定版本来兼容他们的项目或者满足特定的环境需求。在...

    javassist-3.20.0-GA.zip

    这个"javassist-3.20.0-GA.zip"压缩包提供了对JDK8支持的Javaassist版本,适合那些不准备立即升级到JDK9及更高版本的项目。通过熟练掌握Javaassist,你可以实现许多原本需要重新编译源代码才能完成的功能,大大提高...

    javassist-3.15.0-GA

    - 动态代理:创建符合特定接口的类实例,实现代理模式。 5. **与其他工具对比**: - **ASM**:比Javaassist更底层,提供更直接的字节码操作,但使用起来较为复杂。 - **ByteBuddy**:相对较新的字节码操作库,...

    35_dubbo支持哪些负载均衡、高可用以及动态代理的策略?.zip

    - JDK动态代理:Dubbo基于JDK的Proxy类实现服务调用的动态代理,使得客户端可以透明地调用服务。 - CGLIB动态代理:当服务接口没有实现接口时,Dubbo会使用CGLIB库创建子类动态代理。 - Javassist动态代理:另一...

    使用代理打印出详细的入参出参

    结合上述知识点,可以得出,通过JDK代理或javassist库,开发者能够实现方法调用的追踪功能,这对于调试和监控大型应用非常关键。在实际开发中,这样的技术通常会应用于AOP场景,如安全性检查、性能监控、事务管理等...

    javassist-3.18.0-GA

    Javaassist 提供了一种简单的API,可以用来修改字节码,这在处理AOP(面向切面编程)、动态代理、代码生成、性能监控等场景非常有用。它将字节码表示为类的CtClass对象,通过这些对象可以方便地进行添加、删除或...

    动态代理

    在Java中,有两种主要的动态代理实现方式:一是使用`java.lang.reflect.Proxy`类,二是使用`javassist`或`ASM`这样的字节码库。`Proxy`类是Java标准库提供的原生支持,而字节码库则允许更底层的代码操作。 1. **...

    javassist-3.18.0-ga

    这对于实现AOP(面向切面编程)或动态代理等高级功能非常有帮助。 2. **类转换**:通过`ClassPool`类,Javaassist可以加载任意的Java类,并对其进行转换。开发者可以通过`CtClass`对象来表示一个类,然后使用该对象...

    求职宝典-Java 基础面试题

    Java中的JDK动态代理基于接口实现,需要代理类实现特定接口。CGLIB动态代理则是通过字节码技术,为类生成子类并覆盖其方法。Javassist和ASM则是更底层的字节码库,可以直接编辑和生成类的字节码,提供更灵活的代理...

    java6string源码-dynamic-proxy:利用ASM、CGLIB、ByteBuddy、Javassist和JDKDynamicP

    接下来,让我们看一些实现动态代理的例子。 1.2 创建调用者 首先,让我们定义一个接口。 public interface EchoService { String echo(String message); } 通过使用运行时代码生成技术,您可以在不定义Class的情况下...

    强势推送cglib-full-2.0及cglib-full-2.0.2

    - **ORM(对象关系映射)**:Hibernate等ORM框架在无法使用JDK动态代理(即目标类没有实现接口)时,也会使用CGLib来创建代理对象,以便于在保存或加载对象时进行额外操作。 - **性能优化**:在某些情况下,通过...

    javassist-rel_3_20_0_ga-22-gccddf79

    7. 动态代理:可以利用Javaassist快速创建动态代理类,实现特定接口。 8. 支持JDK的新特性:Javaassist随着时间的推移不断更新,以支持新的Java语言特性,如Lambda表达式、模块系统等。 总的来说,Javaassist是一...

    collection-agent:通过javaagent和javassist技术实现对java的ArrayList和HashMap的增强,避免在虚拟器一次load大量数据时导致OOM

    通过javaagent和javassist技术实现对java的ArrayList和HashMap的增强,在操作集合元素时判断集合元素个数, 当集合元素个数大于设置的上限时,抛出异常,终止此次操作,从而避免在集合元素过大导致OOM. #使用方式: ...

    服务治理中间件 dubbo原理解析

    这在Dubbo实现动态代理时显得尤为重要,因为Dubbo通过Javassist生成的服务代理类比普通的动态代理更加高效。 ### 代理 在RPC(Remote Procedure Call)框架中,代理模式是一个核心概念。Dubbo的代理模式主要是通过...

Global site tag (gtag.js) - Google Analytics