2010-05-24 2 views
9

Можно ли загрузить класс в Java и «подделать» имя пакета/каноническое имя класса? Я попытался сделать это, очевидно, но я получаю сообщение «имя класса не соответствует» в ClassDefNotFoundException.Динамическая загрузка класса в java с другим именем пакета

Причина, по которой я делаю это, я пытаюсь загрузить API, который был написан в пакете по умолчанию, поэтому я могу использовать его напрямую, не используя отражение. Код будет скомпилирован против класса в структуре папок, представляющей пакет и импорт имени пакета. то есть:

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

Мой текущий код выглядит следующим образом:

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

} 

ответ

12

As Pete mentioned, это можно сделать с использованием библиотеки байт-кода ASM. Фактически, эта библиотека действительно поставляется с классом, специально предназначенным для обработки повторных сопоставлений имени класса (RemappingClassAdapter). Вот пример загрузчика классов с помощью этого класса:

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

    } 

} 

Чтобы иллюстрировать, я создал два класса, оба из которых принадлежат к пакету по умолчанию:

public class Customer { 

} 

и

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

} 

Это список Orderдо любое переназначение:

 
> 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 

} 

Это перечисление Orderпосле переназначения (с использованием com.mycompany в пакете по умолчанию):

 
> 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 

} 

Как вы можете видеть, переназначение изменил все Order ссылки на com.mycompany.Order и все Customer ссылки com.mycompany.Customer.

Этот загрузчик классов должен загрузить все классы, которые либо:

  • принадлежат к пакету по умолчанию, или
  • использования других классов, которые принадлежат к пакету по умолчанию.
0

Может быть, было бы легче перенести API из пакета по умолчанию в более разумном месте? Похоже, что у вас нет доступа к исходному коду. Я не уверен, что пакет закодирован в файлы классов, поэтому стоит просто попробовать класс API. В противном случае декомпиляторы Java, такие как JAD, обычно выполняют хорошую работу, поэтому вы можете изменить имя пакета в декомпилированном источнике и скомпилировать его снова.

+0

Нет, у меня нет доступа к источнику. Перемещение классов в каталог не работает во время выполнения, хотя, похоже, оно компилируется. Я бы предпочел не взломать класс, если это возможно. –

+0

Вы говорите, что изменение пакета класса во время выполнения - это не взлом? Для меня вопрос только в том, что хуже хук, а затем использовать другой ... – FelixM

1

Вы должны что-то сбить с ASM, хотя было бы проще сделать переименование пакета один раз во время сборки, а не во время загрузки.