Java 是否可以使用类加载器伪造丢失的类?

Java 是否可以使用类加载器伪造丢失的类?,java,jar,classloader,noclassdeffounderror,java-bytecode-asm,Java,Jar,Classloader,Noclassdeffounderror,Java Bytecode Asm,我正在从一个JAR加载类,该JAR实现了一个来自公共API的接口。接口本身将保持不变,但与API相关的其他类可能会随着时间的推移而改变。显然,一旦API发生变化,我们将无法再支持使用旧版本编写的接口的实现。然而,一些接口方法提供了类型为String的简单元数据,我们可以假设这些元数据永远不会改变,也永远不会依赖API中可能改变的其他部分。我希望能够提取这些元数据,即使API发生了变化 例如,考虑以下可能实现的实现,其中 Foo是接口, BAR 是API中的另一个类。我想调用name方法,即使类B

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

例如,考虑以下可能实现的实现,其中<代码> Foo是接口,<代码> BAR <代码>是API中的另一个类。我想调用

name
方法,即使类
Bar
不再存在

类MyFoo实现Foo{
Bar=null;
@重写公共字符串名称(){
返回“MyFoo”
}
}
据我所知,最明显的方法是在我的自定义
ClassLoader
中重写
loadClass(字符串名称)
,并为
Bar
返回一些“假”类。可以假设元数据方法从不创建或使用
对象。问题是当要求加载
Bar
时,如何生成这个“假”类。我考虑过以下方法:

  • 只需返回任何旧的现有类。我已尝试返回
    Object.class
    ,但当我尝试实例化
    Foo
    的实例时,仍然会导致
    NoClassDefFoundError
    Bar
  • 使用ASM从头开始为新类生成字节码
  • 使用ASM重命名某种类型的空模板类以匹配
    Bar
    ,然后加载该类

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

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

    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);
        }
    }
    
    公共类DummyGeneratorLoader扩展URLClassLoader{
    公共DummyGeneratorLoader(URL[]URL,类加载器父级){
    超级(URL,父级);
    }
    公共DummyGeneratorLoader(URL[]URL){
    超级链接(URL);
    }
    公共发电机装载机(
    URL[]URL,类加载器父级,URLStreamHandlerFactory){
    超级(URL、父级、工厂);
    }
    静态最终字节[]模板=(“Êþ¾\0\0\0002\0\n\1\7\0\1\1\0\20java/lang/Object”
    +“\7\0\3\1\0\6\1\0\3()V\14\0\5\0\6\n\0\4\0\7\1\0\4代码\0\1\0\2\0\4\0”
    +“\0\0\0\0\1\0\1\0\0\5\0\6\0\1\0\t\0\0\0\0\21\0\1\0\0\0\0\0\5*·\0\b±\0\0\0\0\0”
    .getBytes(标准字符集.ISO_8859_1);
    @凌驾
    受保护类findClass(字符串名称)引发ClassNotFoundException{
    试一试{
    返回super.findClass(名称);
    }
    catch(ClassNotFoundException ex){}
    返回新的ByteArrayOutputStream(template.length+name.length()+10){{
    写入(模板,0,11);
    请尝试{newdataoutputstream(this.writeUTF(name.replace('.','/'));}
    catch(IOException ex){throw new AssertionError();}
    书写(模板,11,模板长度-11);
    }
    类到类(字符串名称){
    返回defineClass(name,buf,0,count);}}}.toClass(name);
    }
    }
    
    然而,使用代码可能会带来许多虚拟类无法实现的期望或结构约束。毕竟,在调用接口方法之前,必须创建类的实例,因此它必须通过验证并成功执行其构造函数

    如果这些方法确实具有假定的结构,比如
    publicstringname(){return“MyFoo”}
    ,那么使用ASM可能是更简单的选择,但不是生成任意复杂的伪环境,而是解析这些方法并预测它们将返回的常量值。这种方法只包含两条指令,
    ldc value
    areturn
    。您只需要检查情况是否如此,并从第一条指令中提取值