13

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

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

./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); 
    }

}
4

3 回答 3

17

正如 Pete 提到的,这可以使用 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

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

  • 属于默认包,或
  • 使用属于默认包的其他类。
于 2010-05-25T00:00:17.630 回答
1

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

于 2010-05-24T15:38:11.523 回答
0

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

于 2010-05-24T14:45:56.107 回答