【问题标题】:Is it possible to fake missing classes using ClassLoader?是否可以使用 ClassLoader 伪造缺失的类?
【发布时间】:2020-07-28 03:41:00
【问题描述】:

我正在从实现公共 API 接口的 JAR 中加载类。接口本身将保持不变,但与 API 关联的其他类可能会随着时间而改变。显然,一旦 API 发生变化,我们将不再能够支持使用旧版本编写的接口的实现。然而,一些接口方法提供了String 类型的简单元数据,我们可以假设它们永远不会改变,也永远不会依赖可能改变的 API 的其他部分。即使 API 发生变化,我也希望能够提取此元数据。

例如,考虑以下可能加载的实现,其中Foo 是接口,Bar 是 API 中的另一个类。即使Bar 类不再存在,我也想调用name 方法。

class MyFoo implements Foo {
   Bar bar = null;

   @Override public String name() {
      return "MyFoo"
   }
}

据我所知,显而易见的方法是在我的自定义 ClassLoader 中覆盖 loadClass(String name) 并为 Bar 返回一些“假”类。可以假设元数据方法永远不会创建或使用Bar 对象。问题是当被要求加载Bar 时如何生成这个“假”类。我考虑过以下方法:

  1. 只需返回任何旧的现有类。我尝试返回Object.class,但是当我尝试实例化Foo 的实例时,这仍然会导致BarNoClassDefFoundError
  2. 使用 ASM 从头开始​​为新类生成字节码。
  3. 使用 ASM 重命名某种空模板类以匹配 Bar 并加载它。

2. 和 3. 似乎都很复杂,所以我想知道是否有更简单的方法来实现我的目标?

【问题讨论】:

    标签: java jar classloader noclassdeffounderror java-bytecode-asm


    【解决方案1】:

    这是一个类加载器,它将以非常简单的方式为在搜索路径上找不到的每个类创建一个虚拟类:

    public class DummyGeneratorLoader extends URLClassLoader {
    
        public DummyGeneratorLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent);
        }
    
        public DummyGeneratorLoader(URL[] urls) {
            super(urls);
        }
    
        public DummyGeneratorLoader(
            URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
            super(urls, parent, factory);
        }
    
        static final byte[] template = ("Êþº¾\0\0\0002\0\n\1\7\0\1\1\0\20java/lang/Object"
            + "\7\0\3\1\0\6<init>\1\0\3()V\14\0\5\0\6\n\0\4\0\7\1\0\4Code\0\1\0\2\0\4\0"
            + "\0\0\0\0\1\0\1\0\5\0\6\0\1\0\t\0\0\0\21\0\1\0\1\0\0\0\5*·\0\b±\0\0\0\0\0\0")
            .getBytes(StandardCharsets.ISO_8859_1);
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                return super.findClass(name);
            }
            catch(ClassNotFoundException ex) { }
            return new ByteArrayOutputStream(template.length + name.length() + 10) { {
                write(template, 0, 11);
                try { new DataOutputStream(this).writeUTF(name.replace('.', '/')); }
                catch (IOException ex) { throw new AssertionError(); }
                write(template, 11, template.length - 11);
            }
            Class<?> toClass(String name) {
                return defineClass(name, buf, 0, count); } }.toClass(name);
        }
    }
    

    但是,使用代码可能会产生许多虚拟类无法满足的期望或结构限制。毕竟,在调用接口方法之前,必须先创建类的实例,所以它必须通过验证并成功执行其构造函数。

    如果方法确实具有像public String name() { return "MyFoo"; } 这样的假定结构,使用 ASM 可能是更简单的选择,但不是生成任意复杂的虚假环境,而是解析这些方法并预测它们返回的常量值。这种方法只包含两条​​指令,ldc valueareturn。您只需要检查是否是这种情况并从第一条指令中提取值。

    【讨论】:

      猜你喜欢
      • 2018-08-18
      • 2021-06-04
      • 2023-04-02
      • 1970-01-01
      • 2013-02-12
      • 2011-12-15
      • 1970-01-01
      • 2012-11-03
      • 2022-01-08
      相关资源
      最近更新 更多