As Pete mentioned, esto se puede hacer utilizando la biblioteca de códigos de bytes ASM. De hecho, esa biblioteca en realidad viene con una clase específica para manejar estas nuevas asignaciones de nombres de clase (RemappingClassAdapter
). Aquí está un ejemplo de un cargador de clases utilizando esta clase:
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;
}
}
}
}
Para ilustrar, he creado dos clases, las cuales pertenecen al volumen predeterminado:
public class Customer {
}
y
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;
}
}
Este es el listado de Order
antes de cualquier re-mapeo:
> 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
}
Esta es la lista de Order
después remapeo (usando com.mycompany
como el paquete por defecto):
> 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
}
Como se puede ver, la reasignación ha cambiado todo Order
referencias a com.mycompany.Order
y todas Customer
referencias com.mycompany.Customer
.
Este cargador de clases tendría que cargar todas las clases que, o bien:
- pertenecen al volumen predeterminado, o
- uso de otras clases que pertenecen al volumen predeterminado.
No, no tengo acceso a la fuente. Mover las clases a un directorio no funciona en tiempo de ejecución, aunque parece compilarse. Prefiero no hackear la clase si es posible. –
¿Estás diciendo que cambiar el paquete de una clase en tiempo de ejecución no es un truco? Para mí, la pregunta es cuál es el peor truco y luego usar el otro ... – FelixM