سؤال

هل من الممكن تحميل فئة في Java و "مزيفة" اسم الحزمة/الاسم الكنسي للفصل؟ حاولت القيام بذلك ، الطريقة الواضحة ، لكنني أحصل على رسالة "اسم الفصل لا تتطابق" في أ ClassDefNotFoundException.

السبب في أنني أفعل ذلك هو أنني أحاول تحميل واجهة برمجة تطبيقات تم كتابتها في الحزمة الافتراضية حتى أتمكن من استخدامها مباشرة دون استخدام التفكير. سيتم تجميع الكود مقابل الفصل في بنية مجلد تمثل الحزمة واستيراد اسم الحزمة. بمعنى آخر:

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

}
هل كانت مفيدة؟

المحلول

كما ذكر بيت, ، يمكن القيام بذلك باستخدام مكتبة ASM Bytecode. في الواقع ، هذه المكتبة تشحن فعليًا مع فصل خصيصًا للتعامل مع إعادة استخدام اسم الفصل (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 بعد، بعدما remapping (باستخدام 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.

سيتعين على محمل الفئة هذا تحميل جميع الفئات أيضًا:

  • تنتمي إلى الحزمة الافتراضية ، أو
  • استخدم فئات أخرى تنتمي إلى الحزمة الافتراضية.

نصائح أخرى

يجب أن تكون قادرًا على ضرب شيء ما ASM, ، على الرغم من أنه سيكون من الأسهل إجراء تسمية الحزمة مرة واحدة في وقت البناء بدلاً من وقت التحميل.

ربما سيكون من الأسهل نقل واجهة برمجة التطبيقات من الحزمة الافتراضية إلى مكان أكثر منطقية؟ يبدو أنك لا تستطيع الوصول إلى الكود المصدري. لست متأكدًا مما إذا كانت الحزمة مشفرة في ملفات الفصل ، لذا فإن مجرد نقل فئة API قد يستحق المحاولة. بخلاف ذلك ، عادة ما تقوم Java Decompilers مثل JAD بعمل جيد ، بحيث يمكنك تغيير اسم الحزمة في المصدر المقطوع وتجميعه مرة أخرى.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top