动态加载具有不同包名称的java中的类

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

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

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

正如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”
 public class Order extends java.lang.Object {
私人客户;

公共秩序(客户);
  码:
    0:aload_0
    1:invokespecial#10;  //方法java / lang / Object。“”:()V
    4:aload_0
    5:aload_1
    6:putfield#13;  //现场客户:LCustomer;
    9:回归

 public Customer getCustomer();
  码:
    0:aload_0
    1:getfield#13;  //现场客户:LCustomer;
    4:转过来

 public void setCustomer(Customer);
  码:
    0:aload_0
    1:aload_1
    2:putfield#13;  //现场客户:LCustomer;
    5:回归

 }

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

 > javap -private -c命令
编译自“Order.java”
 public class com.mycompany.Order扩展com.mycompany.java.lang.Object {
私人com.mycompany.Customer客户;

 public com.mycompany.Order(com.mycompany.Customer);
  码:
    0:aload_0
    1:invokespecial#30;  //方法“com.mycompany.java/lang/Object"."":()V
    4:aload_0
    5:aload_1
    6:putfield#32;  //现场客户:Lcom.mycompany.Customer;
    9:回归

 public com.mycompany.Customer getCustomer();
  码:
    0:aload_0
    1:getfield#32;  //现场客户:Lcom.mycompany.Customer;
    4:转过来

 public void setCustomer(com.mycompany.Customer);
  码:
    0:aload_0
    1:aload_1
    2:putfield#32;  //现场客户:Lcom.mycompany.Customer;
    5:回归

 }

如您所见,重新映射已更改对com.mycompany.Order所有Order引用以及对com.mycompany.Order所有Customer引用。

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

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

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

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