Pregunta

¿Es posible cargar una clase en Java y 'falso' el nombre del paquete / nombre canónico de una clase? He intentado hacer esto, la manera obvia, pero me da un "nombre de la clase no coincide" en un mensaje de ClassDefNotFoundException.

La razón por la que estoy haciendo esto es que estoy tratando de cargar una API que fue escrito en el paquete por defecto para que pueda utilizarlo directamente sin necesidad de utilizar la reflexión. El código compilará contra la clase en una estructura de carpetas que representa el paquete y el nombre del paquete de importación. 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); 
    }

}
¿Fue útil?

Solución

Como se mencionó Pete , esto se puede hacer uso de la biblioteca ASM código de bytes. De hecho, que en realidad biblioteca se distribuye con una clase específica para el manejo de estos nombres de clase re-asignaciones ( 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 esto, he creado dos clases, los cuales pertenecen al paquete por defecto:

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

}

Esta es la lista 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 reasignación (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 todas las referencias a Order com.mycompany.Order y todas las referencias a Customer com.mycompany.Customer.

Este cargador de clases tendría que cargar todas las clases que:

  • pertenecen al paquete por defecto, o
  • utilizar otras clases que pertenecen al volumen predeterminado.

Otros consejos

Usted debe ser capaz de golpear algo con ASM , aunque sería más fácil hacer el paquete cambiar el nombre de una vez en tiempo de compilación en lugar de en tiempo de carga.

Tal vez sería más fácil mover la API desde el paquete por defecto en un lugar más razonable? Parece que usted no tiene acceso al código fuente. No estoy seguro de si el paquete se codifica en archivos de clase, por lo que simplemente moviendo la clase API podría valer la pena probar. De lo contrario, Java descompiladores como JAD generalmente hacer un trabajo bueno, por lo que podría cambiar el nombre del paquete en la fuente decompilados y compilar de nuevo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top