ToB企服应用市场:ToB评测及商务社交产业平台

标题: JdkProxy的进阶知识 [打印本页]

作者: 西河刘卡车医    时间: 2023-4-12 21:24
标题: JdkProxy的进阶知识
如果想增强一个方法的功能,无非就是直接在方法体内直接修改。但这也无非给一些有代码洁癖人士一丝丝不悦!于是乎我们即不想在原来的代码里修改,又不想把原有的代码重新写一次,那么前辈们就发明了代理.
注意:本文以 JdkProxy 为基础展开所有描述!
参与对象

那么一个代理过程参与的对象有以下几项:
目标接口。

至于为什么要用接口,这是JdkProxy的理论知识。文章结束后你也会明白!
  1. public interface Fight {
  2.     /**
  3.      * 射击
  4.      */
  5.     void shot();
  6.     /**
  7.      * 炸弹
  8.      */
  9.     void bomb();
  10. }
复制代码
目标类

就是需要被代理的类!
  1. public class BeautifulCountryTarget implements Fight {
  2.     private static final Logger LOGGER = LoggerFactory.getLogger(BeautifulCountryTarget.class);
  3.     /**
  4.      * 射击
  5.      */
  6.     @Override
  7.     public void shot() {
  8.         
  9.         LOGGER.debug("M4 shot ---> big goose!");
  10.         
  11.     }
  12.     /**
  13.      * 炸弹
  14.      */
  15.     @Override
  16.     public void bomb() {
  17.         
  18.         LOGGER.debug("HIMARS fire ---> big goose!");
  19.     }
  20. }
复制代码
调试和输出结果
  1. public class JdkProxy {
  2.    
  3.    
  4.     private static final Logger LOGGER = LoggerFactory.getLogger(JdkProxy.class);
  5.     /**
  6.      * 通过 Proxy.create 生成的对象是代理对象,基于 接口的代理对象,那么有以下几点是需要注意
  7.      *  
  8.      * 1:生成的 代理对象 是实现了 目标接口
  9.      * 2:生成的 代理对象 与 代理目标 是兄弟关系 (都实现了同一个目标接口)
  10.      *
  11.      */
  12.     public static void main(String[] args) throws InterruptedException {
  13.         
  14.         // jdkProxy
  15.         jdkProxyTest();
  16.         
  17.     }
  18.    
  19.    
  20.     public static void jdkProxyTest() throws InterruptedException {
  21.         //类加载器,负责把生成的class($Proxy???)文件加载到JVM
  22.         ClassLoader loader = JdkProxy.class.getClassLoader();
  23.         //需要代理的目标(对象)
  24.         BeautifulCountryTarget target = new BeautifulCountryTarget();
  25.         //增强目标方法的处理程序
  26.         InvocationHandler handler = (proxy, method, args) -> {
  27.             LOGGER.debug("大哥你在旁边看着喝Coffee!,武器开给,我来打!");
  28.             //方法调用,(目标对象,参数)
  29.             return method.invoke(target, args);
  30.         };
  31.         //创建代理人
  32.         Fight w_k_l_Fight = (Fight)Proxy.newProxyInstance(
  33.                 loader,
  34.                 new Class[]{Fight.class},
  35.                 handler
  36.         );
  37.         
  38.         //打印下类路径,方便使用 arthas 进行反张译
  39.         LOGGER.debug("proxy<w_k_l_Fight> class {}", w_k_l_Fight.getClass());
  40.    
  41.         //代理人调用方法
  42.         w_k_l_Fight.shot();
  43.         w_k_l_Fight.bomb();
  44.         
  45.     }
  46. }
复制代码
打印出
  1. 19:22:41.839 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- proxy<w_k_l_Fight> class class jdk.proxy1.$Proxy0
  2. 19:22:41.842 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
  3. 19:22:41.842 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- M4 shot ---> big goose!
  4. 19:22:41.842 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
  5. 19:22:41.842 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- HIMARS fire ---> big goose!
复制代码
可以看出代理生效了,-_-!
那么通过 Proxy.create函数生成的是一字节码文件(至于怎么生成,太高端没去研究),它被 loader 加载到JVM,通过 debug只看了类名$Proxy0
模拟手写代理类

既然是系统自己生成的,那么我们自己可以自己写一个,不用系统生成的~。由理论知识可以写出以下 代理类
  1. /**
  2. * 模拟 通过 Proxy.create 出来的 $Proxy?? 类,
  3. *
  4. */
  5. public class SimulateProxy extends Proxy implements Fight {
  6.    
  7.    
  8.     protected SimulateProxy(InvocationHandler h) {
  9.         super(h);
  10.     }
  11.    
  12.    
  13.     /**
  14.      * 射击
  15.      */
  16.     @Override
  17.     public void shot() {
  18.         try {
  19.             // 代理的方法
  20.             Method pMethod = Fight.class.getMethod("shot");
  21.             //调用增强处理器
  22.             super.h.invoke(this, pMethod, null);   
  23.         } catch (Throwable e) {
  24.             e.printStackTrace();
  25.         }
  26.     }
  27.     /**
  28.      * 炸弹
  29.      */
  30.     @Override
  31.     public void bomb() {
  32.         try {
  33.             // 代理的方法
  34.             Method pMethod = Fight.class.getMethod("bomb");
  35.             //调用增强处理器
  36.             super.h.invoke(this, pMethod, null);   
  37.         } catch (Throwable e) {
  38.             e.printStackTrace();
  39.         }
  40.     }
  41. }
复制代码
调试输出
  1. public class JdkProxy {
  2.    
  3.    
  4.     private static final Logger LOGGER = LoggerFactory.getLogger(JdkProxy.class);
  5.     /**
  6.      * 通过 Proxy.create 生成的对象是代理对象,基于 接口的代理对象,那么有以下几点是需要注意
  7.      *  
  8.      * 1:生成的 代理对象 是实现了 目标接口
  9.      * 2:生成的 代理对象 与 代理目标 是兄弟关系 (都实现了同一个目标接口)
  10.      *
  11.      */
  12.     public static void main(String[] args) throws InterruptedException {
  13.         
  14.         // 模拟代理类
  15.         simulateProxyTest();
  16.         
  17.     }
  18.    
  19.    
  20.     public static void simulateProxyTest() {
  21.         
  22.         //需要代理的目标(对象)
  23.         BeautifulCountryTarget target = new BeautifulCountryTarget();
  24.         //增强目标方法的函数
  25.         InvocationHandler handler = (proxy, method, args) -> {
  26.             LOGGER.debug("大哥你在旁边看着喝Coffee!,武器开给,我来打!");
  27.             return method.invoke(target, args);
  28.         };
  29.         
  30.         //创建代理人 这里换成自己手写的
  31.         Fight w_k_l_Fight = new SimulateProxy(handler);
  32.         //代理人调用方法
  33.         w_k_l_Fight.shot();
  34.         w_k_l_Fight.bomb();
  35.     }
  36. }
复制代码
同样输出
  1. 19:38:23.950 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
  2. 19:38:23.951 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- M4 shot ---> big goose!
  3. 19:38:23.951 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
  4. 19:38:23.951 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- HIMARS fire ---> big goose!
复制代码
总结

我们先来看下系统生成的$Proxy0是什么样。
现在通过在main方法添加 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");可以把系统生成的代理class文件写到目录里root/jdk/proxy1/$Proxy0.class。我这里直接贴出来,方便和上下文对比。
  1. public final class $Proxy0 extends Proxy implements Fight {
  2.     private static final Method m0;
  3.     private static final Method m1;
  4.     private static final Method m2;
  5.     private static final Method m3;
  6.     private static final Method m4;
  7.     public $Proxy0(InvocationHandler param1) {
  8.         super(var1);
  9.     }
  10.     public final int hashCode() {
  11.         try {
  12.             return (Integer)super.h.invoke(this, m0, (Object[])null);
  13.         } catch (RuntimeException | Error var2) {
  14.             throw var2;
  15.         } catch (Throwable var3) {
  16.             throw new UndeclaredThrowableException(var3);
  17.         }
  18.     }
  19.     public final boolean equals(Object var1) {
  20.         try {
  21.             return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
  22.         } catch (RuntimeException | Error var2) {
  23.             throw var2;
  24.         } catch (Throwable var3) {
  25.             throw new UndeclaredThrowableException(var3);
  26.         }
  27.     }
  28.     public final String toString() {
  29.         try {
  30.             return (String)super.h.invoke(this, m2, (Object[])null);
  31.         } catch (RuntimeException | Error var2) {
  32.             throw var2;
  33.         } catch (Throwable var3) {
  34.             throw new UndeclaredThrowableException(var3);
  35.         }
  36.     }
  37.     public final void shot() {
  38.         try {
  39.             super.h.invoke(this, m3, (Object[])null);
  40.         } catch (RuntimeException | Error var2) {
  41.             throw var2;
  42.         } catch (Throwable var3) {
  43.             throw new UndeclaredThrowableException(var3);
  44.         }
  45.     }
  46.     public final void bomb() {
  47.         try {
  48.             super.h.invoke(this, m4, (Object[])null);
  49.         } catch (RuntimeException | Error var2) {
  50.             throw var2;
  51.         } catch (Throwable var3) {
  52.             throw new UndeclaredThrowableException(var3);
  53.         }
  54.     }
  55.     static {
  56.         try {
  57.             m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  58.             m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  59.             m2 = Class.forName("java.lang.Object").getMethod("toString");
  60.             m3 = Class.forName("com.java.coffeetime.aop.Fight").getMethod("shot");
  61.             m4 = Class.forName("com.java.coffeetime.aop.Fight").getMethod("bomb");
  62.         } catch (NoSuchMethodException var2) {
  63.             throw new NoSuchMethodError(var2.getMessage());
  64.         } catch (ClassNotFoundException var3) {
  65.             throw new NoClassDefFoundError(var3.getMessage());
  66.         }
  67.     }
  68.     private static Lookup proxyClassLookup(Lookup var0) throws IllegalAccessException {
  69.         if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
  70.             return MethodHandles.lookup();
  71.         } else {
  72.             throw new IllegalAccessException(var0.toString());
  73.         }
  74.     }
  75. }
复制代码
那么对比下系统生成的和自己手写的代理类区别 SimulateProxy和 $Proxy0,可以看出系统生成的代理类比我们手写的比较优雅一些!
本文示例代码

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4