您好,登錄后才能下訂單哦!
這篇文章主要講解了“類代理的方式有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“類代理的方式有哪些”吧!
我們先定義出一個接口和相應的實現類,方便后續使用代理類在方法中添加輸出信息。
「定義接口」
public interface IUserApi { String queryUserInfo(); }
「實現接口」
public class UserApi implements IUserApi { public String queryUserInfo() { return "沉淀、分享、成長,讓自己和他人都能有所收獲!"; } }
好!接下來我們就給這個類方法使用代理加入一行額外輸出的信息。
@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); }
點評:有代理地方幾乎就會有反射,他們是一套互相配合使用的功能類。在反射中可以調用方法、獲取屬性、拿到注解等相關內容。這些都可以與接下來的類代理組合使用,完成各種框架中的技術場景。
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 "沉淀、分享、成長,讓自己和他人都能有所收獲!"; } }); } } @Test public void test_JDKProxy() throws Exception { IUserApi userApi = JDKProxy.getProxy(IUserApi.class); String invoke = userApi.queryUserInfo(); logger.info("測試結果:{}", invoke); } /** * 測試結果: * * queryUserInfo 你被代理了,By JDKProxy! * 19:55:47.319 [main] INFO org.itstack.interview.test.ApiTest - 測試結果: 沉淀、分享、成長,讓自己和他人都能有所收獲! * * Process finished with exit code 0 */
指數:??
場景:中間件開發、設計模式中代理模式和裝飾器模式應用
點評:這種JDK自帶的類代理方式是非常常用的一種,也是非常簡單的一種。基本會在一些中間件代碼里看到例如:數據庫路由組件、Redis組件等,同時我們也可以使用這樣的方式應用到設計模式中。
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("測試結果:{}", invoke); } /** * 測試結果: * * queryUserInfo 你被代理了,By CglibProxy! * 19:55:47.319 [main] INFO org.itstack.interview.test.ApiTest - 測試結果: 沉淀、分享、成長,讓自己和他人都能有所收獲! * * Process finished with exit code 0 */
場景:Spring、AOP切面、鑒權服務、中間件開發、RPC框架等
點評:CGLIB不同于JDK,它的底層使用ASM字節碼框架在類中修改指令碼實現代理,所以這種代理方式也就不需要像JDK那樣需要接口才能代理。同時得益于字節碼框架的使用,所以這種代理方式也會比使用JDK代理的方式快1.5~2.0倍。
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() { // 執行指令;獲取靜態屬性 methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); // 加載常量 load constant methodVisitor.visitLdcInsn(name + " 你被代理了,By ASM!"); // 調用方法 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("測試結果:{}", invoke); } /** * 測試結果: * * queryUserInfo 你被代理了,By ASM! * 20:12:26.791 [main] INFO org.itstack.interview.test.ApiTest - 測試結果: 沉淀、分享、成長,讓自己和他人都能有所收獲! * * Process finished with exit code 0 */
場景:全鏈路監控、破解工具包、CGLIB、Spring獲取類元數據等
點評:這種代理就是使用字節碼編程的方式進行處理,它的實現方式相對復雜,而且需要了解Java虛擬機規范相關的知識。因為你的每一步代理操作,都是在操作字節碼指令,例如:Opcodes.GETSTATIC、Opcodes.INVOKEVIRTUAL,除了這些還有小200個常用的指令。但這種最接近底層的方式,也是最快的方式。所以在一些使用字節碼插裝的全鏈路監控中,會非常常見。
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("測試結果:{}", invoke); } /** * 測試結果: * * queryUserInfo 你被代理了,By Byte-Buddy! * 20:19:44.498 [main] INFO org.itstack.interview.test.ApiTest - 測試結果: 沉淀、分享、成長,讓自己和他人都能有所收獲! * * Process finished with exit code 0 */
場景:AOP切面、類代理、組件、監控、日志
點評:Byte Buddy 也是一個字節碼操作的類庫,但 Byte Buddy 的使用方式更加簡單。無需理解字節碼指令,即可使用簡單的 API 就能很容易操作字節碼,控制類和方法。比起JDK動態代理、cglib,Byte Buddy在性能上具有一定的優勢。「另外」,2015年10月,Byte Buddy被 Oracle 授予了 Duke's Choice大獎。該獎項對Byte Buddy的“ Java技術方面的巨大創新 ”表示贊賞。
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("測試結果:{}", invoke); } /** * 測試結果: * * queryUserInfo 你被代理了,By Javassist * 20:23:39.139 [main] INFO org.itstack.interview.test.ApiTest - 測試結果: 沉淀、分享、成長,讓自己和他人都能有所收獲! * * Process finished with exit code 0 */
場景:全鏈路監控、類代理、AOP
點評:Javassist 是一個使用非常廣的字節碼插裝框架,幾乎一大部分非入侵的全鏈路監控都是會選擇使用這個框架。因為它不想ASM那樣操作字節碼導致風險,同時它的功能也非常齊全。另外,這個框架即可使用它所提供的方式直接編寫插裝代碼,也可以使用字節碼指令進行控制生成代碼,所以綜合來看也是一個非常不錯的字節碼框架。
感謝各位的閱讀,以上就是“類代理的方式有哪些”的內容了,經過本文的學習后,相信大家對類代理的方式有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。