هل هناك أي طريقة لتعديل قيمة حقل "النهائي الثابت الخاص) في جافا من خارج الفصل؟

StackOverflow https://stackoverflow.com/questions/767008

  •  12-09-2019
  •  | 
  •  

سؤال

أعلم أن هذا هو عادة غبيا إلى حد ما، لكن لا تطلق النار علي قبل قراءة السؤال. أعدك أن لدي سبب وجيه للحاجة إلى القيام بذلك :)

من الممكن تعديل الحقول الخاصة العادية في Java باستخدام التفكير، ومع ذلك، يلقي Java استثناء أمان عند محاولة القيام بذلك final مجالات.

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

دعنا نقول فقط أن لدي مكتبة خارجية مع فئة "SomeClass"

public class SomeClass 
{
  private static final SomeClass INSTANCE = new SomeClass()

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

  public Object doSomething(){
    // Do some stuff here 
  }
} 

أريد أساسا أن قرد التصحيح SomeClass حتى أتمكن من تنفيذ نسختي الخاصة من doSomething(). وبعد نظرا لعدم وجود (معرفتي) بأي طريقة للقيام بذلك حقا في جافا، حلاي الوحيد هنا هو تغيير قيمة INSTANCE لذلك تقوم بإرجاع الإصدار الخاص بي من الفئة مع الطريقة المعدلة.

أساسا أريد فقط أن ألتف المكالمة مع فحص أمان ثم استدعاء الطريقة الأصلية.

تستخدم المكتبة الخارجية دائما getInstance() للحصول على مثيل من هذه الفئة (أي إنه Singleton).

تحرير: فقط لتوضيح، getInstance() يطلق عليه المكتبة الخارجية، وليس الرمز الخاص بي، لذلك لن يحل الفئة الفرعية فقط المشكلة.

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

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

المحلول

إنه ممكن. لقد استخدمت هذا إلى ReskeyPatch Naughty Rureclocals التي كانت تمنع تفريغ الطبقة في Webapps. تحتاج فقط إلى استخدام التفكير لإزالة final معدل، ثم يمكنك تعديل الحقل.

شيء مثل هذا سوف تفعل الخدعة:

private void killThreadLocal(String klazzName, String fieldName) {
    Field field = Class.forName(klazzName).getDeclaredField(fieldName);
    field.setAccessible(true);  
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    int modifiers = modifiersField.getInt(field);
    modifiers &= ~Modifier.FINAL;
    modifiersField.setInt(field, modifiers);
    field.set(null, null);
}

هناك بعض التخزين المؤقت أيضا حولها Field#set, ، لذلك إذا تم تشغيل بعض الكود قبل أن لا يعمل بالضرورة ....

نصائح أخرى

أي إطار AOP ستناسب احتياجاتك

سيتيح لك تحديد تجاوز وقت التشغيل لطريقة GetInstance مما يتيح لك إرجاع أي فئة يناسب حاجتك.

يستخدم JMockit إطار ASM داخليا للقيام بنفس الشيء.

يمكنك تجربة ما يلي. ملاحظة: ليس في جميع نقاط الخيط آمنة وهذا لا يعمل من أجل البدائيات المستمرة المعروفة في وقت الترجمة (كما تنبذها من قبل المحول البرمجي)

Field field = SomeClass.class.getDeclareField("INSTANCE");
field.setAccessible(true); // what security. ;)
field.set(null, newValue);

يجب أن تكون قادرا على تغييره مع JNI ... لست متأكدا مما إذا كان هذا خيارا لك.

تحرير: من الممكن، ولكن ليست فكرة جيدة.

http://java.sun.com/docs/books/jni/html/pitfalls.html.

10.9 انتهاك قواعد التحكم في الوصول

لا يقوم JNI بتنفيذ قيود التحكم في الوصول إلى الفئة والحقل والطريقة التي يمكن التعبير عنها عند مستوى لغة برمجة Java من خلال استخدام المعدلات مثل الخدمة الخاصة والنهائية. من الممكن كتابة التعليمات البرمجية الأصلية للوصول إلى أو تعديل حقول كائن على الرغم من أن القيام بذلك على مستوى لغة برمجة Java سيؤدي إلى تغيير غير شرعي. كان مخففة JNI قرار تصميم واع، بالنظر إلى أن القانون الأصلي يمكنه الوصول إلى أي موقع لذاكرة الذاكرة وتعديله في كومة الكومة على أي حال.

قد يكون الرمز الأصلي الذي يتجاوز شيكات الوصول إلى مستوى اللغة للمصدر تأثيرات غير مرغوب فيها على تنفيذ البرنامج. على سبيل المثال، قد يتم إنشاء تناسق إذا تعدلت طريقة أصلية حقل نهائي بعد الانفصال برامج التحويل البرمجي (JIT) في الوصول إلى الحقل. وبالمثل، يجب ألا تعدل الطرق الأصلية كائنات ثابتة مثل الحقول في مثيلات java.lang.string أو java.lang.inter. القيام بذلك قد يؤدي إلى كسر الثبات في تنفيذ منصة جافا.

إذا كنت حقا يجب أن (على الرغم من مشكلتنا، أقترح عليك استخدام محلول captainawesomespants)، فيمكنك إلقاء نظرة على JMockit.. وبعد على الرغم من أن هذا ممكن أن يستخدم في اختبارات الوحدة إذا سمح لك بإعادة تعريف الأساليب التعسفية. يتم ذلك عن طريق تعديل bytecode في وقت التشغيل.

سأحسم هذه الإجابة عن طريق الاعتراف بأن هذا ليس في الواقع إجابة لسؤالك المذكور حول تعديل حقل نهائي ثابت خاص. ومع ذلك، في رمز المثال المحدد المذكور أعلاه، يمكنني في الواقع جعله حتى تتمكن من تجاوز Dosomething (). ما يمكنك القيام به هو الاستفادة من حقيقة أن GetInstance () هي طريقة عامة و Subclass:

public class MySomeClass extends SomeClass
{
   private static final INSTANCE = new MySomeClass();

   public SomeClass getInstance() {
        return INSTANCE;
   }

   public Object doSomething() {
      //Override behavior here!
   }
}

الآن فقط استدعاء mysomeclass.getinstance () بدلا من someclass.getinstance () وأنت جيد للذهاب. بالطبع، هذا يعمل فقط إذا كنت يحصل على GetInstance () وليس جزءا آخر من الأشياء غير المرغوبة التي تعمل بها.

مع mockito. هو بسيط جدا:

import static org.mockito.Mockito.*;

public class SomeClass {

    private static final SomeClass INSTANCE = new SomeClass();

    public static SomeClass getInstance() {
        return INSTANCE;
    }

    public Object doSomething() {
        return "done!";
    }

    public static void main(String[] args) {
        SomeClass someClass = mock(SomeClass.getInstance().getClass());
        when(someClass.doSomething()).thenReturn("something changed!");
        System.out.println(someClass.doSomething());
    }
}

يطبع هذا الرمز "شيء تغير!"؛ يمكنك بسهولة استبدال مثيلات Singleton الخاصة بك. بلدي 0.02 دولار سنتا.

إذا لم يكن هناك اختراق خارجي متاح (على الأقل أنا لست على مدارما) كنت قد اخترق الفصل نفسه. تغيير التعليمات البرمجية عن طريق إضافة التحقق من الأمان الذي تريده. مثل هذه المكتبة الخارجية، لن تأخذ التحديثات بانتظام، كما يحدث العديد من التحديثات على أي حال. كلما حدث ذلك، يمكنني إعادة فعل ذلك بسعادة لأنها ليست مهمة كبيرة على أي حال.

هنا، مشكلتك هي حقن الاعتماد الجيد (عكس تحكم AKA). يجب أن يكون هدفك هو ضخ تنفيذك SomeClass بدلا من unkepatching ذلك. ونعم، يتطلب هذا النهج بعض التغييرات على التصميم الحالي الخاص بك ولكن للأسباب الصحيحة (اسم مبدأ التصميم المفضل لديك هنا) - لا سيما نفس الكائن لا ينبغي أن يكون مسؤولا عن كل من إنشاء الكائنات الأخرى واستخدامها.

أفترض الطريقة التي تستخدمها SomeClass يبدو إلى حد ما مثل هذا:

public class OtherClass {
  public void doEverything() {
    SomeClass sc = SomeClass.geInstance();
    Object o = sc.doSomething();

    // some more stuff here...
  }
}

بدلا من ذلك، ما يجب عليك فعله هو أولا إنشاء فصلك الذي ينفذ نفس الواجهة أو يمتد SomeClass ثم تمرذه الحالة ل doEverything() لذلك يصبح صفك غير ملائم لتنفيذ SomeClass. وبعد في هذه الحالة الرمز الذي يستدعي doEverything هو المسؤول عن المرور في التنفيذ الصحيح - سواء كان الفعلي SomeClass أو الخاص بك من الرد MySomeClass.

public class MySomeClass() extends SomeClass {
  public Object doSomething() {
    // your monkeypatched implementation goes here
  }
}

public class OtherClass {
  public void doEveryting(SomeClass sc) {
    Object o = sc.doSomething();

    // some more stuff here...
  }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top