Dynamic loading a class in java with a different package name

后端 未结 3 1492
悲哀的现实
悲哀的现实 2020-12-30 07:04

Is it possible to load a class in Java and \'fake\' the package name/canonical name of a class? I tried doing this, the obvious way, but I get a \"class name doesn\'t match

相关标签:
3条回答
  • 2020-12-30 07:26

    Maybe it would be easier to move the API from the default package into a more reasonable spot? It sounds like you don't have access to the source code. I am not sure if the package is encoded into class files, so simply moving the API class might be worth a try. Otherwise, Java decompilers like JAD usually do a good job, so you could change the package name in the decompiled source and compile it again.

    0 讨论(0)
  • As Pete mentioned, this can be done using the ASM bytecode library. In fact, that library actually ships with a class specifically for handling these class name re-mappings (RemappingClassAdapter). Here is an example of a class loader using this class:

    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;
                }
            }
    
        }
    
    }
    

    To illustrate, I created two classes, both of which belong to the default package:

    public class Customer {
    
    }
    

    and

    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;
        }
    
    }
    

    This is the listing of Order before any re-mapping:

    > 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
    
    }
    

    This is the listing of Order after remapping (using com.mycompany as the default package):

    > 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
    
    }
    

    As you can see, the remapping has changed all Order references to com.mycompany.Order and all Customer references to com.mycompany.Customer.

    This class loader would have to load all classes that either:

    • belong to the default package, or
    • use other classes that belong to the default package.
    0 讨论(0)
  • 2020-12-30 07:31

    You should be able to knock something up with ASM, though it would be easier to do the package rename once at build time rather than at load time.

    0 讨论(0)
提交回复
热议问题