【问题标题】:Dynamic loading a class in java with a different package name在java中动态加载一个具有不同包名的类
【发布时间】:2011-02-23 06:30:01
【问题描述】:

是否可以在 Java 中加载一个类并“伪造”一个类的包名/规范名?我尝试这样做,显而易见的方式,但我在ClassDefNotFoundException 中收到“类名不匹配”消息。

我这样做的原因是我试图加载一个在默认包中编写的 API,以便我可以直接使用它而不使用反射。代码将针对代表包和包名导入的文件夹结构中的类进行编译。即:

./com/DefaultPackageClass.class
// ...
import com.DefaultPackageClass;
import java.util.Vector;
// ...

我目前的代码如下:

public Class loadClass(String name) throws ClassNotFoundException {
    if(!CLASS_NAME.equals(name))
            return super.loadClass(name);

    try {
        URL myUrl = new URL(fileUrl);
        URLConnection connection = myUrl.openConnection();
        InputStream input = connection.getInputStream();
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int data = input.read();

        while(data != -1){
            buffer.write(data);
            data = input.read();
        }

        input.close();

        byte[] classData = buffer.toByteArray();

        return defineClass(CLASS_NAME,
                classData, 0, classData.length);

    } catch (MalformedURLException e) {
        throw new UndeclaredThrowableException(e);
    } catch (IOException e) {
        throw new UndeclaredThrowableException(e); 
    }

}

【问题讨论】:

    标签: java class dynamic-class-loaders


    【解决方案1】:

    As Pete mentioned,这可以使用 ASM 字节码库来完成。事实上,该库实际上附带了一个专门用于处理这些类名重新映射的类 (RemappingClassAdapter)。下面是一个使用此类的类加载器示例:

    public class MagicClassLoader extends ClassLoader {
    
        private final String defaultPackageName;
    
        public MagicClassLoader(String defaultPackageName) {
            super();
            this.defaultPackageName = defaultPackageName;
        }
    
        public MagicClassLoader(String defaultPackageName, ClassLoader parent) {
            super(parent);
            this.defaultPackageName = defaultPackageName;
        }
    
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            byte[] bytecode = ...; // I will leave this part up to you
            byte[] remappedBytecode;
    
            try {
                remappedBytecode = rewriteDefaultPackageClassNames(bytecode);
            } catch (IOException e) {
                throw new RuntimeException("Could not rewrite class " + name);
            }
    
            return defineClass(name, remappedBytecode, 0, remappedBytecode.length);
        }
    
        public byte[] rewriteDefaultPackageClassNames(byte[] bytecode) throws IOException {
            ClassReader classReader = new ClassReader(bytecode);
            ClassWriter classWriter = new ClassWriter(classReader, 0);
    
            Remapper remapper = new DefaultPackageClassNameRemapper();
            classReader.accept(
                    new RemappingClassAdapter(classWriter, remapper),
                    0
                );
    
            return classWriter.toByteArray();
        }
    
        class DefaultPackageClassNameRemapper extends Remapper {
    
            @Override
            public String map(String typeName) {
                boolean hasPackageName = typeName.indexOf('.') != -1;
                if (hasPackageName) {
                    return typeName;
                } else {
                    return defaultPackageName + "." + typeName;
                }
            }
    
        }
    
    }
    

    为了说明,我创建了两个类,它们都属于默认包:

    public class Customer {
    
    }
    

    public class Order {
    
        private Customer customer;
    
        public Order(Customer customer) {
            this.customer = customer;
        }
    
        public Customer getCustomer() {
            return customer;
        }
    
        public void setCustomer(Customer customer) {
            this.customer = customer;
        }
    
    }
    

    这是Order 任何重新映射之前的列表:

    > javap -private -c 命令 编译自“Order.java” 公共类订单扩展 java.lang.Object{ 私人客户客户; 公共秩序(客户); 代码: 0:aload_0 1:调用特殊#10; //方法 java/lang/Object."":()V 4:aload_0 5:aload_1 6:放置场#13; //字段客户:LCustomer; 9:返回 公共客户 getCustomer(); 代码: 0:aload_0 1:获取字段#13; //字段客户:LCustomer; 4:返回 公共无效 setCustomer(客户); 代码: 0:aload_0 1:aload_1 2:putfield #13; //字段客户:LCustomer; 5:返回 }

    这是Order重新映射后的列表(使用com.mycompany作为默认包):

    > javap -private -c 命令 编译自“Order.java” 公共类 com.mycompany.Order 扩展 com.mycompany.java.lang.Object{ 私人 com.mycompany.Customer 客户; 公共 com.mycompany.Order(com.mycompany.Customer); 代码: 0:aload_0 1:调用特殊#30; //方法 "com.mycompany.java/lang/Object"."":()V 4:aload_0 5:aload_1 6:放置字段#32; //字段客户:Lcom.mycompany.Customer; 9:返回 公共 com.mycompany.Customer getCustomer(); 代码: 0:aload_0 1:获取字段#32; //字段客户:Lcom.mycompany.Customer; 4:返回 公共无效 setCustomer(com.mycompany.Customer); 代码: 0:aload_0 1:aload_1 2:putfield #32; //字段客户:Lcom.mycompany.Customer; 5:返回 }

    如您所见,重新映射已将所有Order 引用更改为com.mycompany.Order,并将所有Customer 引用更改为com.mycompany.Customer

    这个类加载器必须加载所有类:

    • 属于默认包,或
    • 使用属于默认包的其他类。

    【讨论】:

      【解决方案2】:

      您应该能够使用ASM 来解决问题,尽管在构建时重命名一次包比在加载时更容易。

      【讨论】:

        【解决方案3】:

        也许将 API 从默认包移到更合理的位置会更容易?听起来您无权访问源代码。我不确定包是否被编码到类文件中,所以简单地移动 API 类可能值得一试。否则,像 JAD 这样的 Java 反编译器通常会做得很好,因此您可以在反编译的源中更改包名并重新编译。

        【讨论】:

        • 不,我无权访问源代码。将类移动到目录在运行时不起作用,尽管它似乎在编译时起作用。如果可能的话,我宁愿不破解课程。
        • 您是说在运行时更改类的包不是hack?对我来说,问题是哪个是更糟糕的黑客,然后使用另一个......
        猜你喜欢
        • 2011-07-31
        • 2011-06-27
        • 2010-12-14
        • 2014-01-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-25
        • 1970-01-01
        相关资源
        最近更新 更多