动态加载具有不同包名称的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反编译器通常做得很好,因此您可以在反编译源中更改包名称并再次编译。