【问题标题】:How to hot swap java.lang Class in Java如何在 Java 中热交换 java.lang 类
【发布时间】:2017-08-08 11:47:29
【问题描述】:

我知道这不是一个好的要求。但我现在需要这样做。

我需要替换Class.getResourceAsStream(String)的方法

但是ClassLoader.preDefineClass(String , ProtectionDomain)检查全名的类名是否以java.开头,如果是则抛出异常,不允许加载。

有没有办法绕过运行时安全检查而不是编译时

更新

真正的需求是很多老项目需要迁移到新环境,因为这些项目有的可能已经过时,有的可能找不到源代码。我需要在新环境中模拟旧环境,以便这些古老的项目能够继续发挥作用。

Upadte (GhostCat)

我尝试了你给的方式。

类加载器类

public class ClassLoaderTest extends ClassLoader {
    @Override
    public InputStream getResourceAsStream(String name) {
        System.out.println("getResourceAsStream " + name);
        return new ByteArrayInputStream(new byte[]{1, 3, 5, 7, 9});
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("Load class " + name);
        String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
        InputStream is = getClass().getResourceAsStream(fileName);

        if (is == null) {
            return super.loadClass(name);
        }

        byte[] b = new byte[0];
        try {
            b = new byte[is.available()];
            is.read(b);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return defineClass(name, b, 0, b.length);
    }
}

调用类

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
    ClassLoaderTest classLoaderTest = new ClassLoaderTest();
    Class clazz = Class.forName("com.Client", true, classLoaderTest);
    Object client = clazz.newInstance();
    Method method = clazz.getDeclaredMethod("method");
    method.invoke(client);
}

客户端类

public class Client {
    public void method() {
        System.out.println(this.getClass().getResourceAsStream("aaa"));
        System.out.println(Class.class.getResourceAsStream("bbb"));
        System.out.println("".getClass().getResourceAsStream("ccc"));
    }
}

它确实适用于 this.getClass().getResourceAsStream(String),但 Class.class.getResourceAsStream(String) / "".getClass().getResourceAsStream(String) 。因为我无法使用我的自定义 ClassLoader 加载类 名字以java.开头

【问题讨论】:

  • 出于安全考虑,此功能已禁用。您可以扩展所需的类并在需要时调用自己的类,但无法进行替换。
  • 为什么你认为你需要替换Class.getResourceAsStream()?这就是你应该提出的问题。
  • @Kayaman 是的,这确实是我的问题。因为一个很老的项目已经迁移到新环境了,老项目早就没有维护了,现在正在尝试模拟一个和老项目一致的环境。
  • @RishiGoel 谢谢。这是一个非常古老的项目,但它实际上没用,但它不愿意扔掉它。估计有一部分项目可能一直找不到源代码
  • 什么是ClassLoader.preDefineClass(String, ProtectionDomain)?没有这种方法。

标签: java jvm javassist hotswap


【解决方案1】:

你不能。

此类由bootstrap 类加载器加载。而且你无法改变这个人正在做的事情 - 它是用 native 代码编写的。换句话说:这是 JVM 的核心——你不能篡改它。

这就是 java 安全概念的“整体思想”:用户无法“进入”JVM 平台所保证的事物。

鉴于 OP 的编辑认为这是关于遗留代码的:您似乎想为那些 JVM 一部分但属于您的类的类挂接到 Class.getResourceAsStream(String) strong>自己的代码。

如果是这样,那么“答案”很简单:您必须确保所有 your 类都由 your 类加载器加载。 getResourceAsStreeam() 的 javadoc 明确指出:

查找具有给定名称的资源。搜索与给定类关联的资源的规则由该类的定义类加载器实现。这个方法委托给这个对象的类加载器。

因此:学习如何使用自定义类加载器。以here 开头。

【讨论】:

  • 谢谢。我已经更新了这个问题。主要问题是这些古老的项目很多都是使用Class.getResourceAsStream(String)来获取资源的,但是在新的环境下,这些资源不存在,被重定向到其他地方
  • 然后查看我的更新答案。以防万一您考虑接受我的回答...请随时等到明天 ;-)
  • 再次感谢。我试过了,它适用于this.getClass().getResourceAsStream(String)。但它不适用于Class.class.getResourceAsStream(String) / String.class.getResourceAsStream(String)
【解决方案2】:

对于尝试定义具有以java. 开头的限定名称的类的尝试抛出异常是the contract of defineClass 的一部分,但这无关紧要。任何使用 ClassLoader 上现有类的名称定义类的尝试只能有两种可能的结果之一:

  1. 如果现有类已由同一个ClassLoader 定义,则尝试再次定义它将被拒绝并抛出异常。 defineClass 不会更改现有类,这根本不在其功能范围内。

  2. 如果现有类已由不同的ClassLoader(或引导加载程序)加载,您可以在此ClassLoader 上定义具有相同限定名称的类,但它将是一个完全不同的类,与同名但定义类加载器不同的类无关。

您不能为java.* 类定义具有相同名称和不同加载器的此类的事实只是一个额外的限制,但您的尝试无论如何都行不通。

如果你想在类ClassLoader被加载后替换它,你只能使用代理,例如使用Instrumentation API 的Java 代理或调试器。或者,您可以使用-Xbootclasspath/p:… 命令行选项简单地将您自己的版本添加到引导类路径。

当然,将其替换为特定版本仅适用于衍生该版本的环境。此外,此类应用程序也不允许公开部署。

更通用的解决方案是使用当前版本的 ClassLoader 并即时修改它的 Instrumentation Agent,但是,如果您必须选择开发字节码转换代理,它会更容易并且转换应用程序代码更可靠,只需将Class.getResourceAsStream(String)的所有调用替换为自定义方法的调用,无需接触不需要这种适配的代码。

【讨论】:

    【解决方案3】:

    我在这里写了一个关于如何实现运行时类转换的长答案:how to retransform a class at runtime

    一个更简单的方法是我在https://github.com/nickman/retransformer 的 retransformer 项目,它将转换 java.* 类,包括本地方法。 (我用它来修改 Runtime.availableProcessors )

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-03
      • 1970-01-01
      相关资源
      最近更新 更多