Java:用Javassist實現(xiàn)動態(tài)代理實體類

1. JDK動態(tài)代理

相信大家對JDK的動態(tài)代理非常熟悉了郭卫,這里簡單舉個例子說明一下

首先是定義一個接口霹疫,然后定義一個類并實現(xiàn)這個接口

public interface ICallback{
    void callback();
}

public static class MyCallback implements ICallback {

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

接著用 JDK 的 Proxy 生成代理類舔腾,在每個方法調(diào)用前后都加上一句輸出

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();
}

上面的代碼很簡單,但是他的局限性很大:只能代理接口,而不能代理實體類

那么能不能自己實現(xiàn)一種動態(tài)代理來代理實體類呢,經(jīng)過我的實踐泌参,發(fā)現(xiàn)是可以的脆淹。需要用到 Javassist 這個工具

2. 實體類動態(tài)代理的分析

這篇文章不是專門講解 Javassist,有興趣的可以去了解一下沽一。Javassist官方文檔傳送門盖溺,感覺英文吃力的可以看國內(nèi)大牛翻譯的 Javassist文檔翻譯

這里我們只需要知道添加 javassist 的依賴

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

在實現(xiàn)實體類的動態(tài)代理前,我們先要分析接口和實體類的區(qū)分

  1. 接口的方法默認(rèn)都是 public 的铣缠,所有實現(xiàn)接口的類默認(rèn)都會全部繼承所有接口烘嘱;而實體類的方法有 private、protected蝗蛙、public 和 default 的區(qū)別
  2. 實現(xiàn)接口可以直接使用默認(rèn)無參構(gòu)造函數(shù)蝇庭;而繼承實體類有多個構(gòu)造函數(shù)需要繼承,并且需要制定一個構(gòu)造函數(shù)來實例化代理對象
  3. 接口的方法都不是 final 的捡硅;而實體類的方法可能是 final 的
  4. 接口的方法都不是 static 的哮内;而實體類的方法可能是 static 的

再梳理一下,模仿JDK的動態(tài)代理的設(shè)計思路病曾,實現(xiàn)動態(tài)代理實體類所需要的步驟

  1. 定義 InvocationHandler 類牍蜂,用于代理對象的方法調(diào)用時的回調(diào)
  2. 根據(jù) classloader 和 class 判斷是否有緩存漾根,如果有則直接從緩存獲取泰涂。否則再次生成class并在同一個 classloader 加載的話會出現(xiàn)問題
  3. 判斷被代理對象是否是final的,不是final才進行下一步
  4. 用javassist新建一個類辐怕,包名和被代理類一致逼蒙,采用Proxy_前綴命名
  5. 設(shè)置代理類的修飾符和繼承關(guān)系
  6. 添加成員變量 InvocationHandler,便于后面方法調(diào)用時的回調(diào)
  7. 添加構(gòu)造器寄疏,規(guī)則是在被代理類的所有構(gòu)造器的基礎(chǔ)上是牢,添加 InvocationHandler 作為第一個參數(shù)
  8. 添加方法,規(guī)則是在被代理類的所有方法上陕截,篩選 public驳棱、projected、default 的方法农曲,方法體直接調(diào)用 InvocationHandler 的 invoke 方法
  9. 用 classloader 生成 class社搅,并放入緩存
  10. 根據(jù)被代理對象的構(gòu)造函數(shù),在參數(shù)前面加一個 InvocationHandler 參數(shù)來實例化代理對象

3. 實現(xiàn)實體類動態(tài)代理

首先模仿 JDK 實現(xiàn)一個代理方法的回調(diào)接口

public interface InvocationHandler {

    /**
     * @param proxy 動態(tài)生成的代理對象
     * @param method 調(diào)用的方法
     * @param args 調(diào)用的參數(shù)
     * @return 該方法的返回值
     * @throws Throwable
     */
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

}

然后就是代理器的實現(xiàn)

public class Proxy {

    // 動態(tài)生成代理類的前綴
    private static final String PROXY_CLASSNAME_PREFIX = "$Proxy_";
    // 緩存容器乳规,防止生成同一個Class文件在同一個ClassLoader加載崩潰的問題
    private static final Map<String, Class<?>> proxyClassCache = new HashMap<>();

    /**
     * 緩存已經(jīng)生成的代理類的Class形葬,key值根據(jù) 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);
    }

    /**
     * 返回一個動態(tài)創(chuàng)建的代理類暮的,此類繼承自 targetClass
     *
     * @param classLoader       從哪一個ClassLoader加載Class
     * @param invocationHandler 代理類中每一個方法調(diào)用時的回調(diào)接口
     * @param targetClass       被代理對象
     * @param targetConstructor 被代理對象的某一個構(gòu)造器笙以,用于決定代理對象實例化時采用哪一個構(gòu)造器
     * @param targetParam       被代理對象的某一個構(gòu)造器的參數(shù),用于實例化構(gòu)造器
     * @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));
            // 設(shè)置描述符
            proxyCtClass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
            // 設(shè)置繼承關(guān)系
            proxyCtClass.setSuperclass(targetCtClass);
            // 添加構(gòu)造器
            addConstructor(pool, proxyCtClass, targetCtClass);
            // 添加方法
            addMethod(proxyCtClass, targetCtClass, targetClass);
            // 從指定ClassLoader加載Class
            proxyClass = proxyCtClass.toClass(classLoader, null);
            // 緩存
            saveProxyClassCache(classLoader, targetClass, proxyClass);
            // 輸出到文件保存冻辩,用于debug調(diào)試
//            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;
    }

    /**
     * 生成代理類的類名生成規(guī)則
     */
    private static String generateProxyClassName(Class<?> targetClass) {
        return targetClass.getPackage().getName() + "." + PROXY_CLASSNAME_PREFIX + targetClass.getSimpleName();
    }

    /**
     * 根據(jù)被代理類的構(gòu)造器猖腕,構(gòu)造代理類對象拆祈。代理類的所有構(gòu)造器都是被代理類構(gòu)造器前添加一個invocationHandler 參數(shù)
     *
     * @param proxyClass        代理類
     * @param invocationHandler 代理類所有構(gòu)造器的第一個參數(shù)
     * @param targetConstructor 被代理類的構(gòu)造器
     * @param targetParam       被代理類的構(gòu)造器的參數(shù)
     * @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);
    }

    /**
     * 代理類添加構(gòu)造器,基于被代理類的構(gòu)造器倘感,在所有參數(shù)開頭添加一個 {@link InvocationHandler} 參數(shù)
     *
     * @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);
            // 因為第一個參數(shù)指定為 InvocationHandler缘屹,所以super()的參數(shù)是從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);
            }
            // 添加構(gòu)造器方法
            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         被代理類的方法數(shù)組
     * @param ctMethods       被代理類的方法數(shù)組
     * @param isPublic        是否是共有方法逻炊,是:只包含public方法互亮;否:包含projected和default方法
     * @param methodNameIndex 新建方法的命名下標(biāo)
     * @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;
            }

            // 匹配對應(yīng)的方法
            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);
            // 區(qū)分靜態(tài)與非靜態(tài)余素,主要就是對象是否傳null豹休。注意這里必須用($r)轉(zhuǎn)換類型,否則會發(fā)生類型轉(zhuǎn)換失敗的問題
            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 的下標(biāo)索引并返回桨吊。找不到則返回 -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} 是否相等威根。主要從方法名、返回值類型视乐、參數(shù)類型三個維度判斷
     */
    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的方法類似洛搀,只是需要額外指定被代理對象實例化的構(gòu)造器,內(nèi)部會自動轉(zhuǎn)換為代理對象的構(gòu)造器佑淀,這是因為實體類可能會有多個構(gòu)造器留美。

private static void proxyByJavassist(){
    try {
        Demo demo = new Demo();
        Class clazz = Demo.class;
        // 指定被代理對象的構(gòu)造器,內(nèi)部會自動轉(zhuǎn)換為代理對象的構(gòu)造器
        Constructor constructor = clazz.getConstructor(new Class[]{});
        Object[] constructorParam = new Object[]{};
        // 指定方法回調(diào)的接口
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before:" + method.getName());
                // 記住這兒是調(diào)用的被代理對象的方法伸刃,所以傳參是 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();
        // 測試?yán)^承的public方法
        System.out.println(proxy.toString());
    } catch (Exception e) {
        e.printStackTrace();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捧颅,一起剝皮案震驚了整個濱河市景图,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碉哑,老刑警劉巖挚币,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谭梗,居然都是意外死亡忘晤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門激捏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來设塔,“玉大人,你說我怎么就攤上這事∪蚧祝” “怎么了痕钢?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長序六。 經(jīng)常有香客問我任连,道長,這世上最難降的妖魔是什么例诀? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任随抠,我火速辦了婚禮,結(jié)果婚禮上繁涂,老公的妹妹穿的比我還像新娘拱她。我一直安慰自己,他們只是感情好扔罪,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布秉沼。 她就那樣靜靜地躺著,像睡著了一般矿酵。 火紅的嫁衣襯著肌膚如雪唬复。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天全肮,我揣著相機與錄音敞咧,去河邊找鬼。 笑死倔矾,一個胖子當(dāng)著我的面吹牛妄均,可吹牛的內(nèi)容都是我干的柱锹。 我是一名探鬼主播哪自,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼禁熏!你這毒婦竟也來了壤巷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瞧毙,失蹤者是張志新(化名)和其女友劉穎胧华,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宙彪,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡矩动,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了释漆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悲没。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖男图,靈堂內(nèi)的尸體忽然破棺而出示姿,到底是詐尸還是另有隱情甜橱,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布栈戳,位于F島的核電站岂傲,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏子檀。R本人自食惡果不足惜镊掖,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望褂痰。 院中可真熱鬧堰乔,春花似錦、人聲如沸脐恩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驶冒。三九已至苟翻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骗污,已是汗流浹背崇猫。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留需忿,地道東北人诅炉。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像屋厘,于是被迫代替她去往敵國和親涕烧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容