Domanda

E 'possibile caricare una classe in Java e 'falso' il nome del pacchetto / nome canonico di una classe? Ho provato a fare questo, il modo più ovvio, ma ottengo un messaggio "nome di classe non corrisponde" in un ClassDefNotFoundException.

La ragione per cui sto facendo questo è che sto cercando di caricare un'API che è stato scritto nel pacchetto di default in modo che io possa utilizzarlo direttamente senza l'utilizzo di riflessione. Il codice verrà compilato contro la classe in una struttura di cartelle che rappresenta il pacchetto e un nome del pacchetto di importazione. vale a dire:

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

Il mio codice attuale è la seguente:

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

}
È stato utile?

Soluzione

Come menzionato Pete , questo può essere fatto utilizzando la libreria ASM bytecode. Infatti, che in realtà biblioteca fornito con una classe appositamente per la gestione di questi nomi di classe ri-mappature ( RemappingClassAdapter ). Ecco un esempio di un caricatore di classe utilizzando questa 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;
            }
        }

    }

}

Per illustrare, ho creato due classi, entrambi i quali fanno parte del pacchetto di default:

public class Customer {

}

e

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

}

Questa è la messa in vendita di Order prima qualsiasi rimappatura:

> 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

}

Questa è la messa in vendita di Order dopo la rimappatura (utilizzando com.mycompany come il pacchetto di default):

> 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

}

Come si può vedere, la rimappatura ha cambiato tutti i riferimenti a Order com.mycompany.Order e tutti i riferimenti a Customer com.mycompany.Customer.

Questo caricatore di classe avrebbe dovuto caricare tutte le classi che:

  • appartengono al pacchetto di default, o
  • utilizzare altre classi che appartengono al pacchetto di default.

Altri suggerimenti

Si dovrebbe essere in grado di mettere qualcosa con ASM , anche se sarebbe più facile da fare il pacchetto rinominare una volta al momento della compilazione, piuttosto che al momento del caricamento.

Forse sarebbe più facile spostare l'API dal pacchetto predefinito in un posto più ragionevole? Sembra che non si ha accesso al codice sorgente. Non sono sicuro se il pacchetto è codificato in file di classe, così semplicemente muovendo la classe API potrebbe essere la pena di provare. In caso contrario, Java decompilatori come JAD solito fare un lavoro buono, quindi è possibile modificare il nome del pacchetto nella fonte decompiled e compilarlo di nuovo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top