无所不能的javassist和基于它实现的动态代理

标签: Java, 设计模式

保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/63/ )。

我们已经介绍了基于反射的动态代理和基于cglib(Code Generation Library,即代码生成库) 的动态代理。本文我们将介绍javassist,基于它也可以实现动态代理。

cglib是基于底层的ASM框架来实现Java字节码的修改。而javassist和ASM类似,它是也是一个开源的用来创建、修改Java字节码的类库,能实现类的创建、方法的修改、继承关系的设置等一系列的操作。

相比于ASM,javassist的优势是学习成本低,可以根据java代码生成字节码,而不需要直接操作字节码。

javassist的使用虽然比ASM简单,但也并不是太容易。我们通过一个示例来展示下javassist的使用,以“无中生有”的方式创建一个类,并给类设置属性和方法。整个示例如如下所示。

public static void main(String[] args) throws Exception {
    ClassPool pool = ClassPool.getDefault();
    // 定义一个类
    CtClass userCtClazz = pool.makeClass("com.github.yeecode.mybatisdemo.User");
    // 创建name属性
    CtField nameField = new CtField(pool.get("java.lang.String"), "name", userCtClazz);
    userCtClazz.addField(nameField);
    // 创建name的set方法
    CtMethod setMethod = CtNewMethod.make("public void setName(String name) { this.name = name;}", userCtClazz);
    userCtClazz.addMethod(setMethod);
    // 创建sayHello方法
    CtMethod sayHello = CtNewMethod.make("public String sayHello() { return \"Hello, I am \" + this.name ;}", userCtClazz);
    userCtClazz.addMethod(sayHello);

    Class<?> userClazz = userCtClazz.toClass();
    // 创建一个对象
    Object user = userClazz.newInstance();
    // 为对象设置name值
    Method[] methods = userClazz.getMethods();
    for (Method method: methods){
        if (method.getName().equals("setName")) {
            method.invoke(user,"易哥");
        }
    }
    // 调用对象sayHello方法
    for (Method method: methods){
        if (method.getName().equals("sayHello")) {
            String result = (String) method.invoke(user);
            System.out.println(result);

        }
    }
}

运行上述代码,可以看到控制台打印出下图所示的输出结果。

图 22-2 程序运行结果

在代码中,我们凭空创建了一个User对象,并为其赋予了name属性和setName方法、sayHello方法。然后实例化出该类的对象后,调用了对象的相关方法。这些操作都是直接针对JVM中的字节码展开的。这充分说明了直接操作字节码这种方式的灵活与强大,但因为它涉及较多的底层操作,并不是很容易驾驭。

但无论如何,javassist这个强大的工具可以直接修改字节码。因此我们可以使用它创建被代理类的子类从而实现动态代理,也可以使用它创建被代理类接口的子类从而实现动态代理。当然,它也有一定的局限性:也就无法为final类创建代理,因为final类没有子类。

MyBatis在进行对象的懒加载时就有使用javassist创建目标对象的代理对象的过程。其具体过程本文不再展开,大家可以参考《通用源码阅读指导书——MyBatis源码详解》一书的“22.3 懒加载功能”章节,该章节对MyBatis的懒加载相关源码进行了详细的介绍,读完会很有收获。

通用源码阅读指导书-京东自营

《通用源码阅读指导书》

可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。

作者书籍推荐