溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

有哪些類代理的方式

發(fā)布時間:2021-10-26 13:55:45 來源:億速云 閱讀:158 作者:iii 欄目:編程語言

這篇文章主要介紹“有哪些類代理的方式”,在日常操作中,相信很多人在有哪些類代理的方式問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”有哪些類代理的方式”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

五種類代理的方式

不出意外,你可能只知道兩種類代理的方式。一種是JDK自帶的,另外一種是CGLIB。

我們先定義出一個接口和相應(yīng)的實現(xiàn)類,方便后續(xù)使用代理類在方法中添加輸出信息。

「定義接口」

public interface IUserApi {

    String queryUserInfo();

}
 

「實現(xiàn)接口」

public class UserApi implements IUserApi {

    public String queryUserInfo() {
        return "小傅哥,公眾號:bugstack蟲洞棧 | 沉淀、分享、成長,讓自己和他人都能有所收獲!";
    }

}
 

好!接下來我們就給這個類方法使用代理加入一行額外輸出的信息。

 

0. 先補充一點反射的知識

@Test
public void test_reflect() throws Exception {
    Class<UserApi> clazz = UserApi.class;
    Method queryUserInfo = clazz.getMethod("queryUserInfo");
    Object invoke = queryUserInfo.invoke(clazz.newInstance());
    System.out.println(invoke);
}
 
  • 指數(shù):??
  • 點評:有代理地方幾乎就會有反射,他們是一套互相配合使用的功能類。在反射中可以調(diào)用方法、獲取屬性、拿到注解等相關(guān)內(nèi)容。這些都可以與接下來的類代理組合使用,完成各種框架中的技術(shù)場景。
 

1. JDK代理方式

public class JDKProxy {

    public static <T> T getProxy(Class clazz) throws Exception {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        return (T) Proxy.newProxyInstance(classLoader, new Class[]{clazz}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName() + " 你被代理了,By JDKProxy!");
                return "小傅哥,公眾號:bugstack蟲洞棧 | 沉淀、分享、成長,讓自己和他人都能有所收獲!";
            }
        });
    }

}

@Test
public void test_JDKProxy() throws Exception {
    IUserApi userApi = JDKProxy.getProxy(IUserApi.class);
    String invoke = userApi.queryUserInfo();
    logger.info("測試結(jié)果:{}", invoke);
}

/**
 * 測試結(jié)果:
 * 
 * queryUserInfo 你被代理了,By JDKProxy!
 * 19:55:47.319 [main] INFO  org.itstack.interview.test.ApiTest - 測試結(jié)果:小傅哥,公眾號:bugstack蟲洞棧 | 沉淀、分享、成長,讓自己和他人都能有所收獲!
 *
 * Process finished with exit code 0
 */
 
  • 指數(shù):??
  • 場景:中間件開發(fā)、設(shè)計模式中代理模式和裝飾器模式應(yīng)用
  • 點評:這種JDK自帶的類代理方式是非常常用的一種,也是非常簡單的一種?;緯谝恍┲虚g件代碼里看到例如:數(shù)據(jù)庫路由組件、Redis組件等,同時我們也可以使用這樣的方式應(yīng)用到設(shè)計模式中。
 

2. CGLIB代理方式

public class CglibProxy implements MethodInterceptor {
    public Object newInstall(Object object) {
        return Enhancer.create(object.getClass(), this);
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("我被CglibProxy代理了");
        return methodProxy.invokeSuper(o, objects);
    }
}

@Test
public void test_CglibProxy() throws Exception {
    CglibProxy cglibProxy = new CglibProxy();
    UserApi userApi = (UserApi) cglibProxy.newInstall(new UserApi());
    String invoke = userApi.queryUserInfo();
    logger.info("測試結(jié)果:{}", invoke);
}

/**
 * 測試結(jié)果:
 * 
 * queryUserInfo 你被代理了,By CglibProxy!
 * 19:55:47.319 [main] INFO  org.itstack.interview.test.ApiTest - 測試結(jié)果:小傅哥,公眾號:bugstack蟲洞棧 | 沉淀、分享、成長,讓自己和他人都能有所收獲!
 *
 * Process finished with exit code 0
 */
 
  • 指數(shù):???
  • 場景:Spring、AOP切面、鑒權(quán)服務(wù)、中間件開發(fā)、RPC框架等
  • 點評:CGLIB不同于JDK,它的底層使用ASM字節(jié)碼框架在類中修改指令碼實現(xiàn)代理,所以這種代理方式也就不需要像JDK那樣需要接口才能代理。同時得益于字節(jié)碼框架的使用,所以這種代理方式也會比使用JDK代理的方式快1.5~2.0倍。
 

3. ASM代理方式

public class ASMProxy extends ClassLoader {

    public static <T> T getProxy(Class clazz) throws Exception {

        ClassReader classReader = new ClassReader(clazz.getName());
        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);

        classReader.accept(new ClassVisitor(ASM5, classWriter) {
            @Override
            public MethodVisitor visitMethod(int access, final String name, String descriptor, String signature, String[] exceptions) {

                // 方法過濾
                if (!"queryUserInfo".equals(name))
                    return super.visitMethod(access, name, descriptor, signature, exceptions);

                final MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);

                return new AdviceAdapter(ASM5, methodVisitor, access, name, descriptor) {

                    @Override
                    protected void onMethodEnter() {
                        // 執(zhí)行指令;獲取靜態(tài)屬性
                        methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        // 加載常量 load constant
                        methodVisitor.visitLdcInsn(name + " 你被代理了,By ASM!");
                        // 調(diào)用方法
                        methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                        super.onMethodEnter();
                    }
                };
            }
        }, ClassReader.EXPAND_FRAMES);

        byte[] bytes = classWriter.toByteArray();

        return (T) new ASMProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
    }

}

@Test
public void test_ASMProxy() throws Exception {
    IUserApi userApi = ASMProxy.getProxy(UserApi.class);
    String invoke = userApi.queryUserInfo();
    logger.info("測試結(jié)果:{}", invoke);
}

/**
 * 測試結(jié)果:
 * 
 * queryUserInfo 你被代理了,By ASM!
 * 20:12:26.791 [main] INFO  org.itstack.interview.test.ApiTest - 測試結(jié)果:小傅哥,公眾號:bugstack蟲洞棧 | 沉淀、分享、成長,讓自己和他人都能有所收獲!
 *
 * Process finished with exit code 0
 */
 
  • 指數(shù):?????
  • 場景:全鏈路監(jiān)控、破解工具包、CGLIB、Spring獲取類元數(shù)據(jù)等
  • 點評:這種代理就是使用字節(jié)碼編程的方式進行處理,它的實現(xiàn)方式相對復(fù)雜,而且需要了解Java虛擬機規(guī)范相關(guān)的知識。因為你的每一步代理操作,都是在操作字節(jié)碼指令,例如:     Opcodes.GETSTATIC、     Opcodes.INVOKEVIRTUAL,除了這些還有小200個常用的指令。但這種最接近底層的方式,也是最快的方式。所以在一些使用字節(jié)碼插裝的全鏈路監(jiān)控中,會非常常見。
 

4. Byte-Buddy代理方式

public class ByteBuddyProxy {

    public static <T> T getProxy(Class clazz) throws Exception {

        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                .subclass(clazz)
                .method(ElementMatchers.<MethodDescription>named("queryUserInfo"))
                .intercept(MethodDelegation.to(InvocationHandler.class))
                .make();

        return (T) dynamicType.load(Thread.currentThread().getContextClassLoader()).getLoaded().newInstance();
    }

}

@RuntimeType
public static Object intercept(@Origin Method method, @AllArguments Object[] args, @SuperCall Callable<?> callable) throws Exception {
    System.out.println(method.getName() + " 你被代理了,By Byte-Buddy!");
    return callable.call();
}

@Test
public void test_ByteBuddyProxy() throws Exception {
    IUserApi userApi = ByteBuddyProxy.getProxy(UserApi.class);
    String invoke = userApi.queryUserInfo();
    logger.info("測試結(jié)果:{}", invoke);
}

/**
 * 測試結(jié)果:
 * 
 * queryUserInfo 你被代理了,By Byte-Buddy!
 * 20:19:44.498 [main] INFO  org.itstack.interview.test.ApiTest - 測試結(jié)果:小傅哥,公眾號:bugstack蟲洞棧 | 沉淀、分享、成長,讓自己和他人都能有所收獲!
 *
 * Process finished with exit code 0
 */
 
  • 指數(shù):????
  • 場景:AOP切面、類代理、組件、監(jiān)控、日志
  • 點評:     Byte Buddy 也是一個字節(jié)碼操作的類庫,但     Byte Buddy 的使用方式更加簡單。無需理解字節(jié)碼指令,即可使用簡單的 API 就能很容易操作字節(jié)碼,控制類和方法。比起JDK動態(tài)代理、cglib,Byte Buddy在性能上具有一定的優(yōu)勢。     「另外」,2015年10月,Byte Buddy被 Oracle 授予了 Duke's Choice大獎。該獎項對Byte Buddy的“ Java技術(shù)方面的巨大創(chuàng)新 ”表示贊賞。
 

5. Javassist代理方式

public class JavassistProxy extends ClassLoader {

    public static <T> T getProxy(Class clazz) throws Exception {

        ClassPool pool = ClassPool.getDefault();
        // 獲取類
        CtClass ctClass = pool.get(clazz.getName());
        // 獲取方法
        CtMethod ctMethod = ctClass.getDeclaredMethod("queryUserInfo");
        // 方法前加強
        ctMethod.insertBefore("{System.out.println(\"" + ctMethod.getName() + " 你被代理了,By Javassist\");}");

        byte[] bytes = ctClass.toBytecode();

        return (T) new JavassistProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
    }

}

@Test
public void test_JavassistProxy() throws Exception {
    IUserApi userApi = JavassistProxy.getProxy(UserApi.class)
    String invoke = userApi.queryUserInfo();
    logger.info("測試結(jié)果:{}", invoke);
}

/**
 * 測試結(jié)果:
 * 
 * queryUserInfo 你被代理了,By Javassist
 * 20:23:39.139 [main] INFO  org.itstack.interview.test.ApiTest - 測試結(jié)果:小傅哥,公眾號:bugstack蟲洞棧 | 沉淀、分享、成長,讓自己和他人都能有所收獲!
 *
 * Process finished with exit code 0
 */
 
  • 指數(shù):????
  • 場景:全鏈路監(jiān)控、類代理、AOP
  • 點評:     Javassist 是一個使用非常廣的字節(jié)碼插裝框架,幾乎一大部分非入侵的全鏈路監(jiān)控都是會選擇使用這個框架。因為它不想ASM那樣操作字節(jié)碼導(dǎo)致風(fēng)險,同時它的功能也非常齊全。另外,這個框架即可使用它所提供的方式直接編寫插裝代碼,也可以使用字節(jié)碼指令進行控制生成代碼,所以綜合來看也是一個非常不錯的字節(jié)碼框架。
 

總結(jié)

有哪些類代理的方式  
  • 代理的實際目的就是通過一些技術(shù)手段,替換掉原有的實現(xiàn)類或者給原有的實現(xiàn)類注入新的字節(jié)碼指令。而這些技術(shù)最終都會用到一些框架應(yīng)用、中間件開發(fā)以及類似非入侵的全鏈路監(jiān)控中。
  • 一個技術(shù)棧深度的學(xué)習(xí)能讓你透徹的了解到一些基本的根本原理,通過這樣的學(xué)習(xí)可以解惑掉一些似懂非懂的疑問,也可以通過這樣技術(shù)的拓展讓自己有更好的工作機會和薪資待遇。
  • 這些技術(shù)學(xué)起來并不會很容易,甚至可能還有一些燒腦。但每一段值得深入學(xué)習(xí)的技術(shù)都能幫助你突破一定階段的技術(shù)瓶頸。

到此,關(guān)于“有哪些類代理的方式”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI