2010-05-24 18 views
9

¿Es posible cargar una clase en Java y 'falso' el nombre del paquete/nombre canónico de una clase? Intenté hacer esto, de la manera más obvia, pero aparece el mensaje "class name does not matching" en un ClassDefNotFoundException.Dinámico cargando una clase en Java con un nombre de paquete diferente

La razón por la que estoy haciendo esto es porque estoy tratando de cargar una API escrita en el paquete predeterminado para que pueda usarla directamente sin usar el reflejo. El código se compilará contra la clase en una estructura de carpetas que representa el paquete y una importación de nombre de paquete. es decir:

 
./com/DefaultPackageClass.class 
// ... 
import com.DefaultPackageClass; 
import java.util.Vector; 
// ... 

Mi código actual es la siguiente:

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

} 

Respuesta

12

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 Orderantes 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 Orderdespué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.
0

Tal vez sería más fácil mover la API desde el paquete por defecto en un lugar más razonable? Parece que no tienes acceso al código fuente. No estoy seguro de si el paquete está codificado en archivos de clase, por lo que basta con mover la clase API. De lo contrario, los descompiladores de Java como JAD suelen hacer un buen trabajo, por lo que podría cambiar el nombre del paquete en la fuente descompilada y compilarlo de nuevo.

+0

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. –

+0

¿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

1

Debería ser capaz de noquear algo con ASM, aunque sería más fácil hacer el cambio de nombre del paquete una vez en tiempo de compilación en lugar de en tiempo de carga.

Cuestiones relacionadas