Java:用Javassist实现动态代理实体类

1. JDK动态代理

相信大家对JDK的动态代理非常熟悉了,这里简单举个例子说明一下

首先是定义一个接口,然后定义一个类并实现这个接口

public interface ICallback{
    void callback();
}

public static class MyCallback implements ICallback {

    @Override
    public void callback() {
        System.out.println("MyCallback");
    }
}

接着用 JDK 的 Proxy 生成代理类,在每个方法调用前后都加上一句输出

private static void proxyByJDK(){
    ICallback callback = new MyCallback();
    Object proxy = java.lang.reflect.Proxy.newProxyInstance(callback.getClass().getClassLoader(), callback.getClass().getInterfaces(), new java.lang.reflect.InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before:" + method.getName());
            Object result = method.invoke(callback, args);
            System.out.println("after:" + method.getName());
            return result;
        }
    });
    ((ICallback)proxy).callback();
}

上面的代码很简单,但是他的局限性很大:只能代理接口,而不能代理实体类

那么能不能自己实现一种动态代理来代理实体类呢,经过我的实践,发现是可以的。需要用到 Javassist 这个工具

2. 实体类动态代理的分析

这篇文章不是专门讲解 Javassist,有兴趣的可以去了解一下。Javassist官方文档传送门,感觉英文吃力的可以看国内大牛翻译的 Javassist文档翻译

这里我们只需要知道添加 javassist 的依赖

implementation 'org.javassist:javassist:3.20.0-GA'

在实现实体类的动态代理前,我们先要分析接口和实体类的区分

  1. 接口的方法默认都是 public 的,所有实现接口的类默认都会全部继承所有接口;而实体类的方法有 private、protected、public 和 default 的区别
  2. 实现接口可以直接使用默认无参构造函数;而继承实体类有多个构造函数需要继承,并且需要制定一个构造函数来实例化代理对象
  3. 接口的方法都不是 final 的;而实体类的方法可能是 final 的
  4. 接口的方法都不是 static 的;而实体类的方法可能是 static 的

再梳理一下,模仿JDK的动态代理的设计思路,实现动态代理实体类所需要的步骤

  1. 定义 InvocationHandler 类,用于代理对象的方法调用时的回调
  2. 根据 classloader 和 class 判断是否有缓存,如果有则直接从缓存获取。否则再次生成class并在同一个 classloader 加载的话会出现问题
  3. 判断被代理对象是否是final的,不是final才进行下一步
  4. 用javassist新建一个类,包名和被代理类一致,采用Proxy_前缀命名
  5. 设置代理类的修饰符和继承关系
  6. 添加成员变量 InvocationHandler,便于后面方法调用时的回调
  7. 添加构造器,规则是在被代理类的所有构造器的基础上,添加 InvocationHandler 作为第一个参数
  8. 添加方法,规则是在被代理类的所有方法上,筛选 public、projected、default 的方法,方法体直接调用 InvocationHandler 的 invoke 方法
  9. 用 classloader 生成 class,并放入缓存
  10. 根据被代理对象的构造函数,在参数前面加一个 InvocationHandler 参数来实例化代理对象

3. 实现实体类动态代理

首先模仿 JDK 实现一个代理方法的回调接口

public interface InvocationHandler {

    /**
     * @param proxy 动态生成的代理对象
     * @param method 调用的方法
     * @param args 调用的参数
     * @return 该方法的返回值
     * @throws Throwable
     */
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

}

然后就是代理器的实现

public class Proxy {

    // 动态生成代理类的前缀
    private static final String PROXY_CLASSNAME_PREFIX = "$Proxy_";
    // 缓存容器,防止生成同一个Class文件在同一个ClassLoader加载崩溃的问题
    private static final Map<String, Class<?>> proxyClassCache = new HashMap<>();

    /**
     * 缓存已经生成的代理类的Class,key值根据 classLoader 和 targetClass 共同决定
     */
    private static void saveProxyClassCache(ClassLoader classLoader, Class<?> targetClass, Class<?> proxyClass) {
        String key = classLoader.toString() + "_" + targetClass.getName();
        proxyClassCache.put(key, proxyClass);
    }

    /**
     * 从缓存中取得代理类的Class,如果没有则返回 null
     */
    private static Class<?> getProxyClassCache(ClassLoader classLoader, Class<?> targetClass) {
        String key = classLoader.toString() + "_" + targetClass.getName();
        return proxyClassCache.get(key);
    }

    /**
     * 返回一个动态创建的代理类,此类继承自 targetClass
     *
     * @param classLoader       从哪一个ClassLoader加载Class
     * @param invocationHandler 代理类中每一个方法调用时的回调接口
     * @param targetClass       被代理对象
     * @param targetConstructor 被代理对象的某一个构造器,用于决定代理对象实例化时采用哪一个构造器
     * @param targetParam       被代理对象的某一个构造器的参数,用于实例化构造器
     * @return
     */
    public static Object newProxyInstance(ClassLoader classLoader,
                                          InvocationHandler invocationHandler,
                                          Class<?> targetClass,
                                          Constructor<?> targetConstructor,
                                          Object... targetParam) {
        if (classLoader == null || targetClass == null || invocationHandler == null) {
            throw new IllegalArgumentException("argument is null");
        }
        try {
            // 查看是否有缓存
            Class<?> proxyClass = getProxyClassCache(classLoader, targetClass);
            if (proxyClass != null) {
                // 实例化代理对象
                return newInstance(proxyClass, invocationHandler, targetConstructor, targetParam);
            }

            ClassPool pool = new ClassPool(true);
            pool.importPackage(InvocationHandler.class.getName());
            pool.importPackage(Method.class.getName());
            // 被代理类
            CtClass targetCtClass = pool.get(targetClass.getName());
            // 检查被代理类是否是 final的
            if ((targetCtClass.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
                throw new IllegalArgumentException("class is final");
            }
            // 新建代理类
            CtClass proxyCtClass = pool.makeClass(generateProxyClassName(targetClass));
            // 设置描述符
            proxyCtClass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
            // 设置继承关系
            proxyCtClass.setSuperclass(targetCtClass);
            // 添加构造器
            addConstructor(pool, proxyCtClass, targetCtClass);
            // 添加方法
            addMethod(proxyCtClass, targetCtClass, targetClass);
            // 从指定ClassLoader加载Class
            proxyClass = proxyCtClass.toClass(classLoader, null);
            // 缓存
            saveProxyClassCache(classLoader, targetClass, proxyClass);
            // 输出到文件保存,用于debug调试
//            File outputFile = new File("/Users/jm/Downloads/Demo");
//            proxyCtClass.writeFile(outputFile.getAbsolutePath());
            proxyCtClass.defrost();
            // 实例化代理对象
            return newInstance(proxyClass, invocationHandler, targetConstructor, targetParam);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 生成代理类的类名生成规则
     */
    private static String generateProxyClassName(Class<?> targetClass) {
        return targetClass.getPackage().getName() + "." + PROXY_CLASSNAME_PREFIX + targetClass.getSimpleName();
    }

    /**
     * 根据被代理类的构造器,构造代理类对象。代理类的所有构造器都是被代理类构造器前添加一个invocationHandler 参数
     *
     * @param proxyClass        代理类
     * @param invocationHandler 代理类所有构造器的第一个参数
     * @param targetConstructor 被代理类的构造器
     * @param targetParam       被代理类的构造器的参数
     * @return
     * @throws Exception
     */
    private static Object newInstance(Class<?> proxyClass,
                                      InvocationHandler invocationHandler,
                                      Constructor<?> targetConstructor,
                                      Object... targetParam) throws Exception {
        Class[] parameterTypes = new Class[targetConstructor.getParameterTypes().length + 1];
        merge(parameterTypes, InvocationHandler.class, targetConstructor.getParameterTypes());
        Constructor<?> constructor = proxyClass.getConstructor(parameterTypes);
        Object[] paramter = new Object[targetParam.length + 1];
        merge(paramter, invocationHandler, targetParam);
        return constructor.newInstance(paramter);
    }

    /**
     * 代理类添加构造器,基于被代理类的构造器,在所有参数开头添加一个 {@link InvocationHandler} 参数
     *
     * @param pool
     * @param proxyClass
     * @param targetClass
     * @throws Exception
     */
    private static void addConstructor(ClassPool pool, CtClass proxyClass, CtClass targetClass) throws Exception {
        // 添加 invocationHandler 字段
        CtField field = CtField.make("private InvocationHandler invocationHandler = null;", proxyClass);
        proxyClass.addField(field);
        CtConstructor[] constructors = targetClass.getConstructors();
        for (CtConstructor constructor : constructors) {
            CtClass[] parameterTypes = new CtClass[constructor.getParameterTypes().length + 1];
            merge(parameterTypes, pool.get(InvocationHandler.class.getName()), constructor.getParameterTypes());
            CtConstructor newConstructor = new CtConstructor(parameterTypes, proxyClass);
            // 因为第一个参数指定为 InvocationHandler,所以super()的参数是从2开始的
            StringBuilder sb = new StringBuilder();
            for (int i = 1; i <= constructor.getParameterTypes().length; i++) {
                sb.append("$").append(i + 1).append(",");
            }
            if (sb.length() > 0) {
                sb.setLength(sb.length() - 1);
            }
            // 添加构造器方法
            String code = String.format("{super(%s);this.invocationHandler = $1;}", sb.toString());
            newConstructor.setBody(code);
            proxyClass.addConstructor(newConstructor);
        }
    }

    /**
     * 先添加 Public 的方法,然后添加 Project 和 default 的方法
     *
     * @param proxyCtClass  代理类
     * @param targetCtClass 被代理类
     * @param targetClass   被代理类
     * @throws Exception
     */
    private static void addMethod(CtClass proxyCtClass, CtClass targetCtClass, Class<?> targetClass) throws Exception {
        int methodNameIndex = 0;
        methodNameIndex = addMethod(proxyCtClass, targetCtClass, targetClass.getMethods(), targetCtClass.getMethods(), true, methodNameIndex);
        addMethod(proxyCtClass, targetCtClass, targetClass.getDeclaredMethods(), targetCtClass.getDeclaredMethods(), false, methodNameIndex);
    }

    /**
     * 代理类添加方法,基于被代理类的共有方法。因为{@link CtClass#getMethods()} 和 {@link Class#getMethods()}返回的列表顺序不一样,所以需要做一次匹配
     *
     * @param proxyCtClass    代理类
     * @param targetCtClass   被代理类
     * @param methods         被代理类的方法数组
     * @param ctMethods       被代理类的方法数组
     * @param isPublic        是否是共有方法,是:只包含public方法;否:包含projected和default方法
     * @param methodNameIndex 新建方法的命名下标
     * @return
     * @throws Exception
     */
    private static int addMethod(CtClass proxyCtClass, CtClass targetCtClass, Method[] methods, CtMethod[] ctMethods, boolean isPublic, int methodNameIndex) throws Exception {
        for (int i = 0; i < ctMethods.length; i++) {
            CtMethod ctMethod = ctMethods[i];
            // final和static修饰的方法不能被继承
            if ((ctMethod.getModifiers() & Modifier.FINAL) == Modifier.FINAL
                    || (ctMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
                continue;
            }
            // 满足指定的修饰符
            int modifyFlag = -1;
            if (isPublic) {
                // public 方法
                if ((ctMethod.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC) {
                    modifyFlag = Modifier.PUBLIC;
                }
            } else {
                // protected 方法
                if ((ctMethod.getModifiers() & Modifier.PROTECTED) == Modifier.PROTECTED) {
                    modifyFlag = Modifier.PROTECTED;
                } else if ((ctMethod.getModifiers() & Modifier.PUBLIC) == 0
                        && (ctMethod.getModifiers() & Modifier.PROTECTED) == 0
                        && (ctMethod.getModifiers() & Modifier.PRIVATE) == 0) {
                    modifyFlag = 0;
                }
            }
            if (modifyFlag == -1) {
                continue;
            }

            // 匹配对应的方法
            int methodIndex = findSomeMethod(methods, ctMethod);
            if (methodIndex == -1) {
                continue;
            }
            // 将这个方法作为字段保存,便于新增的方法能够访问原来的方法
            String code = null;
            if (isPublic) {
                code = String.format("private static Method method%d = Class.forName(\"%s\").getMethods()[%d];",
                        methodNameIndex, targetCtClass.getName(), methodIndex);
            } else {
                code = String.format("private static Method method%d = Class.forName(\"%s\").getDeclaredMethods()[%d];",
                        methodNameIndex, targetCtClass.getName(), methodIndex);
            }
            CtField field = CtField.make(code, proxyCtClass);
            proxyCtClass.addField(field);

            CtMethod newCtMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(), ctMethod.getParameterTypes(), proxyCtClass);
            // 区分静态与非静态,主要就是对象是否传null。注意这里必须用($r)转换类型,否则会发生类型转换失败的问题
            if ((ctMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
                code = String.format("return ($r)invocationHandler.invoke(null, method%d, $args);", methodNameIndex);
            } else {
                code = String.format("return ($r)invocationHandler.invoke(this, method%d, $args);", methodNameIndex);
            }
            newCtMethod.setBody(code);
            newCtMethod.setModifiers(modifyFlag);
            proxyCtClass.addMethod(newCtMethod);
            methodNameIndex++;
        }
        return methodNameIndex;
    }

    /**
     * 从 methods 找到等于 ctMethod 的下标索引并返回。找不到则返回 -1
     */
    private static int findSomeMethod(Method[] methods, CtMethod ctMethod) {
        for (int i = 0; i < methods.length; i++) {
            if (equalsMethod(methods[i], ctMethod)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 判断{@link Method} 和 {@link CtMethod} 是否相等。主要从方法名、返回值类型、参数类型三个维度判断
     */
    private static boolean equalsMethod(Method method, CtMethod ctMethod) {
        if (method == null && ctMethod == null) {
            return true;
        }
        if (method == null || ctMethod == null) {
            return false;
        }
        try {
            if (method.getName().equals(ctMethod.getName())
                    && method.getReturnType().getName().equals(ctMethod.getReturnType().getName())) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                CtClass[] parameterTypesCt = ctMethod.getParameterTypes();
                if (parameterTypes.length != parameterTypesCt.length) {
                    return false;
                }
                boolean equals = true;
                for (int i = 0; i < parameterTypes.length; i++) {
                    if (!parameterTypes[i].getName().equals(parameterTypesCt[i].getName())) {
                        equals = false;
                        break;
                    }
                }
                return equals;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 将 item 和 array 合并到 merge
     */
    private static <T> T[] merge(T[] merge, T item, T[] array) {
        List<T> list = new ArrayList<>();
        list.add(item);
        Collections.addAll(list, array);
        list.toArray(merge);
        return merge;
    }
}

4. 使用方式

使用方法跟JDK的方法类似,只是需要额外指定被代理对象实例化的构造器,内部会自动转换为代理对象的构造器,这是因为实体类可能会有多个构造器。

private static void proxyByJavassist(){
    try {
        Demo demo = new Demo();
        Class clazz = Demo.class;
        // 指定被代理对象的构造器,内部会自动转换为代理对象的构造器
        Constructor constructor = clazz.getConstructor(new Class[]{});
        Object[] constructorParam = new Object[]{};
        // 指定方法回调的接口
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before:" + method.getName());
                // 记住这儿是调用的被代理对象的方法,所以传参是 demo 而不是 proxy
                method.setAccessible(true);
                Object result = method.invoke(demo, args);
                System.out.println("after:" + method.getName());
                return result;
            }
        };
        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), invocationHandler, clazz, constructor, constructorParam);
        // 分别测试 public、protected、default的方法
        ((Demo)proxy).publicDemo();
        ((Demo)proxy).protectDemo();
        ((Demo)proxy).defaultDemo();
        // 测试继承的public方法
        System.out.println(proxy.toString());
    } catch (Exception e) {
        e.printStackTrace();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容