【问题标题】:How can I create dynamic proxy for final Class?如何为最终类创建动态代理?
【发布时间】:2013-05-08 09:38:32
【问题描述】:

简而言之: 1. 我有一些最终类,我想为它创建动态代理。我该怎么做? 2. MethodHandle可以转成Method吗?

详情 首先,是否存在将 MethodHandle 转换为 Method 的 API?类似于 java.lang.invoke.MethodHandles 的东西

public MethodHandle unreflect(Method m) throws IllegalAccessException;

但是反过来呢?

假设我想创建动态 java.lang.reflect.Method。它被视为

public final
   class Method extends AccessibleObject implements GenericDeclaration,
                                                 Member ;

所以,如果我想使用 JDK 动态代理,我必须使用一些接口(例如成员)。虽然有两个主要缺点。一、方法如

public Class<?>[] getParameterTypes();

比如

public Class<?> getReturnType();

不是任何界面的一部分,但它们被广泛使用。

第二个缺点是它无法提供直接替换。也就是说,我无法将我的动态代理传递给需要 java.lang.reflect.Method 的代码。

另一种方法是使用 CGLIB 或 Javaassist。 AFAIK,CGLIB 不能代理最终课程,是吗? Javaassist 可以代理最终类吗?如何从课程中“删除”最终标识符?失败了,Javvassist 可以以某种方式做到这一点...

【问题讨论】:

    标签: java proxy mocking powermock cglib


    【解决方案1】:

    这取决于您需要什么样的代理。基本上有三种方法可以实现这一点,其中两种在生产代码中是可行的。正如@probrekely 所说,cglib 或 javassist 的问题是它们动态创建子类,这对于最终类是不可能的。您可以通过以下方式避免这种情况:

    • 禁用字节码验证。 Java 运行时验证字节码以确保不加载恶意字节码。例如,当通过网络或互联网接收课程(例如小程序)时,这一点很重要。这样,您可以创建最终类的子类,因为字节码验证器不会阻止您。假设您只运行受信任的代码,则可以禁用此验证。这可以通过运行来完成:

      java -Xverify:none ApplicationName
      

      然而,这是我最不推荐您的解决方案。我不会将这种方法用于生产代码,但它肯定是最容易实现的解决方案。

    • 在加载类之前或之后从加载的类中删除 final 修饰符。这可以通过使用Java agent 来实现。 Java 代理可以在应用程序启动时通过命令行安装,也可以在运行时通过Attach API 安装。使用像 ASM 这样的字节码工具,您可以解析原始字节数组并从所有感兴趣的类中删除 final 修饰符。也可以重新定义已经加载的类。删除 final 修饰符不会与旧的类版本发生冲突,因此这样的重新定义始终是可能的。

    • 按照我描述的方法删除final 修饰符,但重新定义加载的类以实际包含原始类中的所有检测逻辑。这个 aporach 肯定需要最大的努力,但这将使您的仪器对所有其他代码透明。这将是所有解决方案中最干净的解决方案。

    【讨论】:

    • 能否请您更详细地解释最后一个选项?
    • 您可以使用检测 API 重新定义类。比如说,一个类定义了一个方法 foo。您可以重命名方法栏并从您重新定义的新方法 foo 中调用它
    【解决方案2】:

    对不起,你想要的不可能:

    您可以使用 CGLIB 或 Javassist 为具体类创建代理,因为这些库会动态生成您尝试代理的类的子类。 final 类不能被子类化,因此不能以这种方式创建代理。

    PowerMock 确实允许您代理 final 类和方法,但这是因为它在其特殊的 ClassLoader 下运行您的测试,该 ClassLoader 使用 Javassist 在加载时修改您希望代理的类的字节码。 (你不会想在生产中使用这种东西,因为通常修改后的“僵尸”版本的类除了运行特定的模拟单元测试之外不会有什么好处。)

    但是,PowerMock 方法在这里不起作用 - 您想要代理 java.lang.reflect.Method,它位于引导类路径上,因此会在任何 PowerMock/Javassist 类型工具之前加载,因此不可代理。

    【讨论】:

      猜你喜欢
      • 2013-03-21
      • 2012-01-13
      • 1970-01-01
      • 2020-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-06
      • 1970-01-01
      相关资源
      最近更新 更多