Question

Est-il possible de charger une classe en Java et « faux » le nom du package / nom canonique d'une classe? J'ai essayé de le faire, la manière évidente, mais je reçois un message « nom de classe ne correspond pas » dans un ClassDefNotFoundException.

La raison pour laquelle je fais c'est que je suis en train de charger une API qui a été écrit dans le paquet par défaut pour que je puisse l'utiliser directement sans utiliser la réflexion. Le code compilera contre la classe dans une structure de dossiers représentant le paquet et une importation de nom du package. à-dire:

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

Mon code actuel est le suivant:

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

}
Était-ce utile?

La solution

Comme mentionné Pete , cela peut être fait en utilisant la bibliothèque de bytecode ASM. En fait, cette bibliothèque en fait des navires avec une classe spécifique pour la gestion de ces noms de classe re-correspondances ( RemappingClassAdapter ). Voici un exemple d'un chargeur de classe en utilisant cette classe:

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

    }

}

Pour illustrer, j'ai créé deux classes, les deux qui appartiennent au package par défaut:

public class Customer {

}

et

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

}

Voici la liste des Order avant une re-mapping:

> 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

}

Voici la liste des Order après remappant (en utilisant com.mycompany comme le package par défaut):

> 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

}

Comme vous pouvez le voir, le remappage a changé toutes les références à Order com.mycompany.Order et toutes les références à Customer com.mycompany.Customer.

Ce chargeur de classe devrait charger toutes les classes qui soit:

  • appartiennent à son package par défaut, ou
  • utiliser d'autres classes qui appartiennent au package par défaut.

Autres conseils

Vous devriez être en mesure de frapper quelque chose avec ASM , mais il serait plus facile de faire le paquet renommer une fois au moment de la construction plutôt que dans le temps de chargement.

Peut-être qu'il serait plus facile de déplacer l'API du package par défaut dans un endroit plus raisonnable? Il semble que vous n'avez pas accès au code source. Je ne sais pas si le paquet est codé dans des fichiers de classe, si simplement le déplacement de la classe API est peut-être la peine d'essayer. Dans le cas contraire, Java décompilateurs comme JAD font généralement un bon travail, vous pouvez changer le nom du package dans la source décompilé et le compiler à nouveau.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top