【问题标题】:Serializing a proxy class to file将代理类序列化为文件
【发布时间】:2022-02-21 12:04:15
【问题描述】:

我有一个在运行时在 JVM 中生成的代理(生成为 JDKProxy 或 CGLIB)。我想知道是否有办法将这个类的内容(看起来像 com.sun.proxy$Proxy123.class)写入文件,以便我可以使用 jd-eclipse 之类的反编译器来查看代码的种类生成。由于该类存在于 JVM 中,我想知道是否有一种方法可以让 ClassLoader 为实际类提供 InputStream/URL,然后可以使用该类将内容写入磁盘 - 以及磁盘上的这个文件可以使用 jd-eclipse 或 javap 读取。我知道这不是一个生产用例,但我很想看看这个动态生成的类的内容。

谢谢!

【问题讨论】:

    标签: java proxy-classes cglib


    【解决方案1】:

    您可以使用Instrumentationregister 一个可重新转换的ClassFileTransformer 并请求Proxy 类的re-transformation。然后,在转换器的transform method 中,您可以使用构成类的字节数组。将数组保存到类文件后,您可以简单地返回未修改的数组,让 JVM 不受影响地继续。

    但我不确定您对这些课程有何期望。它们被直接实施并且没有提供任何惊喜。以下是在 Oracle 的 jdk1.7.0_40 生成的 java.lang.Runnable Proxy 上执行上述步骤后 javap 的输出示例:

    public final class com.sun.proxy.$Proxy0 extends java.lang.reflect.Proxy implements java.lang.Runnable {
      private static java.lang.reflect.Method m1;
    
      private static java.lang.reflect.Method m3;
    
      private static java.lang.reflect.Method m0;
    
      private static java.lang.reflect.Method m2;
    
      public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler);
        Code:
           0: aload_0       
           1: aload_1       
           2: invokespecial #8                  // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
           5: return        
    
      public final int hashCode();
        Code:
           0: aload_0       
           1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
           4: aload_0       
           5: getstatic     #55                 // Field m0:Ljava/lang/reflect/Method;
           8: aconst_null   
           9: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
          14: checkcast     #57                 // class java/lang/Integer
          17: invokevirtual #60                 // Method java/lang/Integer.intValue:()I
          20: ireturn       
          21: athrow        
          22: astore_1      
          23: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
          26: dup           
          27: aload_1       
          28: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
          31: athrow        
        Exception table:
           from    to  target type
               0    21    21   Class java/lang/Error
               0    21    21   Class java/lang/RuntimeException
               0    21    22   Class java/lang/Throwable
    
      public final boolean equals(java.lang.Object);
        Code:
           0: aload_0       
           1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
           4: aload_0       
           5: getstatic     #20                 // Field m1:Ljava/lang/reflect/Method;
           8: iconst_1      
           9: anewarray     #22                 // class java/lang/Object
          12: dup           
          13: iconst_0      
          14: aload_1       
          15: aastore       
          16: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
          21: checkcast     #30                 // class java/lang/Boolean
          24: invokevirtual #34                 // Method java/lang/Boolean.booleanValue:()Z
          27: ireturn       
          28: athrow        
          29: astore_2      
          30: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
          33: dup           
          34: aload_2       
          35: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
          38: athrow        
        Exception table:
           from    to  target type
               0    28    28   Class java/lang/Error
               0    28    28   Class java/lang/RuntimeException
               0    28    29   Class java/lang/Throwable
    
      public final java.lang.String toString();
        Code:
           0: aload_0       
           1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
           4: aload_0       
           5: getstatic     #65                 // Field m2:Ljava/lang/reflect/Method;
           8: aconst_null   
           9: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
          14: checkcast     #67                 // class java/lang/String
          17: areturn       
          18: athrow        
          19: astore_1      
          20: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
          23: dup           
          24: aload_1       
          25: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
          28: athrow        
        Exception table:
           from    to  target type
               0    18    18   Class java/lang/Error
               0    18    18   Class java/lang/RuntimeException
               0    18    19   Class java/lang/Throwable
    
      static {};
        Code:
           0: ldc           #70                 // String java.lang.Object
           2: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
           5: ldc           #77                 // String equals
           7: iconst_1      
           8: anewarray     #72                 // class java/lang/Class
          11: dup           
          12: iconst_0      
          13: ldc           #70                 // String java.lang.Object
          15: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
          18: aastore       
          19: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
          22: putstatic     #20                 // Field m1:Ljava/lang/reflect/Method;
          25: ldc           #83                 // String java.lang.Runnable
          27: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
          30: ldc           #84                 // String run
          32: iconst_0      
          33: anewarray     #72                 // class java/lang/Class
          36: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
          39: putstatic     #50                 // Field m3:Ljava/lang/reflect/Method;
          42: ldc           #70                 // String java.lang.Object
          44: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
          47: ldc           #85                 // String hashCode
          49: iconst_0      
          50: anewarray     #72                 // class java/lang/Class
          53: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
          56: putstatic     #55                 // Field m0:Ljava/lang/reflect/Method;
          59: ldc           #70                 // String java.lang.Object
          61: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
          64: ldc           #86                 // String toString
          66: iconst_0      
          67: anewarray     #72                 // class java/lang/Class
          70: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
          73: putstatic     #65                 // Field m2:Ljava/lang/reflect/Method;
          76: return        
          77: astore_1      
          78: new           #90                 // class java/lang/NoSuchMethodError
          81: dup           
          82: aload_1       
          83: invokevirtual #93                 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
          86: invokespecial #96                 // Method java/lang/NoSuchMethodError."<init>":(Ljava/lang/String;)V
          89: athrow        
          90: astore_1      
          91: new           #100                // class java/lang/NoClassDefFoundError
          94: dup           
          95: aload_1       
          96: invokevirtual #93                 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
          99: invokespecial #101                // Method java/lang/NoClassDefFoundError."<init>":(Ljava/lang/String;)V
         102: athrow        
        Exception table:
           from    to  target type
               0    77    77   Class java/lang/NoSuchMethodException
               0    77    90   Class java/lang/ClassNotFoundException
    
      public final void run();
        Code:
           0: aload_0       
           1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
           4: aload_0       
           5: getstatic     #50                 // Field m3:Ljava/lang/reflect/Method;
           8: aconst_null   
           9: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
          14: pop           
          15: return        
          16: athrow        
          17: astore_1      
          18: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
          21: dup           
          22: aload_1       
          23: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
          26: athrow        
        Exception table:
           from    to  target type
               0    16    16   Class java/lang/Error
               0    16    16   Class java/lang/RuntimeException
               0    16    17   Class java/lang/Throwable
    }
    

    【讨论】:

      【解决方案2】:

      Cglib 在幕后使用 ASM,并且可以获取由 ASM 创建的表示 cglib 生成的类的字节数组。 cglib Enhancer 类有一个方法 generateClass(ClassVisitor),您可以在其中传递任何 ASM ClassVisitor,对于您的情况,这将是 ClassWriter 的一个实例。只需在类生成后调用 ClassWriter#toByteArray() 并将从此方法接收到的字节保存在以生成的类命名的文件中,并以 .class 为后缀。

      问题:Cglib 代理是通过传递一个到几个实现Callback 接口的实例来实现的。一旦创建并加载了代理类,这些实例就会被 cglib 注入到代理类的 static 字段中。但是,如果您在不使用 cglib 的情况下加载相同的类,则必须在加载类和第一次使用其中一个实例之间手动完成 Callbacks 的注入。否则,您的代理将不起作用,但由于缺少Callback,因此在任何拦截方法的调用上都会抛出NullPointerExceptions。还有更坏的消息:cglib 为Callbacks 创建了一个随机字段名称,这样注入相当困难。

      对于 Java Proxys 也是如此,其中 MethodInterceptor 必须注入创建的代理中。然而,获取代表代理的字节数组就更难了。基本上,Proxy 实现不会为您提供任何访问权限,因此您必须调用ProxyGenerator#generateProxyClass(这是您不应该使用的内部 sun 包类)来创建字节数组。然而,InvocationHandler 的注入相当简单,如 javadoc 中所述:

      Foo f = (Foo) proxyClass.
          getConstructor(new Class[] { InvocationHandler.class }).
          newInstance(new Object[] { handler });
      

      最后,对于我上面描述的所有问题,我不建议您按照您的计划做任何事情。但是,如果您仍然想这样做,请确保仅通过某些封装工厂访问手动加载的代理类,该封装工厂强制代理在使用之前始终正确初始化。否则你将一直处理NullPointerExceptions 和奇怪的功能。一般来说,宁可在其他 VM 上重新创建代理,而不是尝试使它们正确可序列化。

      您不能要求 ClassLoader 将字节数组吐出到它已加载的类,但如果您可以/想要使用检测 API,您可能还想寻求 Holger 的解决方案。

      【讨论】:

        【解决方案3】:

        JDK 代理

        在 JDK11 将此添加到您的 aop 代码之前

        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        

        或者,添加虚拟机选项:-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

        在ProxyGenerator.generateProxyClass()中,有一段代码决定是否将类文件输出到你的位置。

        Cglib 代理

        目前,我只能在调试时输出文件。但是我觉得做一次就可以理解他们是如何完成拦截的了。 在 DefaultGeneratorStrategy.generate(ClassGenerator cg) 处添加断点。同时程序运行到这一行:

        return this.transform(cw.toByteArray());
        

        你可以用FileOutputStream来写cw.toByteArray()。它是cglib代理类,你可以用IDEA、jd gui、bytecode-viewer等工具反编译。

        【讨论】:

          猜你喜欢
          • 2021-12-18
          • 1970-01-01
          • 1970-01-01
          • 2016-11-08
          • 2022-01-02
          • 2013-03-24
          • 2018-11-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多