代码之家  ›  专栏  ›  技术社区  ›  C. Ross trotttrotttrott

用不同的包名称动态加载Java中的类

  •  11
  • C. Ross trotttrotttrott  · 技术社区  · 14 年前

    是否可以在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); 
        }
    
    }
    
    3 回复  |  直到 14 年前
        1
  •  15
  •   Community CDub    7 年前

    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
    Compiled from "Order.java"
    public class Order extends java.lang.Object{
    private Customer customer;
    
    public Order(Customer);
      Code:
       0:   aload_0
       1:   invokespecial   #10; //Method java/lang/Object."":()V
       4:   aload_0
       5:   aload_1
       6:   putfield    #13; //Field customer:LCustomer;
       9:   return
    
    public Customer getCustomer();
      Code:
       0:   aload_0
       1:   getfield    #13; //Field customer:LCustomer;
       4:   areturn
    
    public void setCustomer(Customer);
      Code:
       0:   aload_0
       1:   aload_1
       2:   putfield    #13; //Field customer:LCustomer;
       5:   return
    
    }
    

    这是 秩序 之后 重新映射(使用 com.mycompany 作为默认包):

    > javap -private -c Order
    Compiled from "Order.java"
    public class com.mycompany.Order extends com.mycompany.java.lang.Object{
    private com.mycompany.Customer customer;
    
    public com.mycompany.Order(com.mycompany.Customer);
      Code:
       0:   aload_0
       1:   invokespecial   #30; //Method "com.mycompany.java/lang/Object"."":()V
       4:   aload_0
       5:   aload_1
       6:   putfield    #32; //Field customer:Lcom.mycompany.Customer;
       9:   return
    
    public com.mycompany.Customer getCustomer();
      Code:
       0:   aload_0
       1:   getfield    #32; //Field customer:Lcom.mycompany.Customer;
       4:   areturn
    
    public void setCustomer(com.mycompany.Customer);
      Code:
       0:   aload_0
       1:   aload_1
       2:   putfield    #32; //Field customer:Lcom.mycompany.Customer;
       5:   return
    
    }
    

    如你所见,重新映射已经改变了所有 秩序 参考文献 com.mycompany.Order 以及所有 Customer 参考文献 com.mycompany.Customer .

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

    • 属于默认包,或
    • 使用属于默认包的其他类。
        2
  •  1
  •   Pete Kirkham    14 年前

    你应该能用 ASM 尽管在构建时而不是在加载时对包进行一次重命名会更容易。

        3
  •  0
  •   FelixM    14 年前

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