2010-05-24 13 views
9

Ist es möglich, eine Klasse in Java zu laden und den Paketnamen/kanonischen Namen einer Klasse zu "fälschen"? Ich habe versucht, dies zu tun, der offensichtliche Weg, aber ich bekomme eine "Klassenname stimmt nicht überein" Nachricht in einem ClassDefNotFoundException.Dynamisches Laden einer Klasse in Java mit einem anderen Paketnamen

Der Grund, warum ich das tue, versuche ich eine API zu laden, die in das Standardpaket geschrieben wurde, damit ich es direkt ohne Reflektion verwenden kann. Der Code wird mit der Klasse in einer Ordnerstruktur kompiliert, die das Paket und einen Paketnamenimport darstellt. dh:

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

Mein aktueller Code ist wie folgt:

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

} 

Antwort

12

As Pete mentioned kann dies mit der ASM-Bytecode-Bibliothek durchgeführt werden. Tatsächlich wird diese Bibliothek tatsächlich mit einer Klasse ausgeliefert, die speziell für die Verarbeitung dieser Neuzuordnungen von Klassennamen vorgesehen ist (RemappingClassAdapter). Hier ist ein Beispiel für eine Klassenlader mit dieser Klasse:

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

    } 

} 

Zur Veranschaulichung, habe ich zwei Klassen, die beide auf dem Standardpaket gehören:

public class Customer { 

} 

und

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

} 

Dies ist die Auflistung von Ordervor jede Neuzuordnung:

Diese
 
> 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 

} 

ist die Auflistung von Ordernach Remapping (mit com.mycompany als Standard-Paket):

 
> 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 

} 

Wie Sie sehen können, hat sich die Neuzuordnung geändert alle Order Verweise auf com.mycompany.Order und alle Customer Verweise auf com.mycompany.Customer.

Diese Klasse loader müsste alle Klassen laden, die entweder:

  • zum Standardpaket gehören, oder
  • Verwendung anderer Klassen, die zum Standardpaket gehören.
0

Vielleicht wäre es einfacher, die API aus dem Standard-Paket in einen angemesseneren Ort zu bewegen? Es klingt, als hätten Sie keinen Zugriff auf den Quellcode. Ich bin mir nicht sicher, ob das Paket in Klassendateien kodiert ist, daher ist es einfach einen Versuch wert, einfach die API-Klasse zu verschieben. Ansonsten machen Java-Decompiler wie JAD in der Regel gute Arbeit, so dass Sie den Paketnamen in der dekompilierten Quelle ändern und neu kompilieren können.

+0

Nein, ich habe keinen Zugriff auf die Quelle. Das Verschieben der Klassen in ein Verzeichnis funktioniert zur Laufzeit nicht, obwohl es anscheinend kompiliert wird. Ich würde lieber nicht die Klasse hacken, wenn möglich. –

+0

Willst du sagen, dass das Ändern des Pakets einer Klasse zur Laufzeit kein Hack ist? Für mich ist die Frage nur, welcher ist der schlechtere Hack und dann den anderen ... – FelixM

1

Sie sollten in der Lage sein, etwas mit ASM zu knacken, obwohl es einfacher wäre, das Paket umzubenennen einmal zur Erstellungszeit anstatt zur Ladezeit.