Gibt es eine Möglichkeit, den Wert eines `private static final` Feld in Java von außerhalb der Klasse zu ändern?

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

  •  12-09-2019
  •  | 
  •  

Frage

Ich weiß, dass dies in der Regel ziemlich dumm ist, aber nicht schießen mich nicht vor die Frage zu lesen. Ich verspreche, ich habe einen guten Grund für die Notwendigkeit, dies zu tun:)

Es ist möglich, regelmäßige private Felder in Java unter Verwendung von Reflexion zu ändern, aber Java wirft eine Sicherheitsausnahme bei dem Versuch, das gleiche für final Felder zu tun.

Ich würde davon ausgehen, das streng durchgesetzt wird, aber ich dachte, fragen würde ohnehin nur für den Fall hatte jemand einen Hack herausgefunden, dies zu tun.

Sagen wir einfach, ich habe eine externe Bibliothek mit einer Klasse „SomeClass

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

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

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

Ich möchte im Wesentlichen Monkey-Patch-Someclass, so dass ich meine eigene Version von doSomething() ausführen kann. Da es nicht (mein Wissen) ist eine Möglichkeit, wirklich zu tun, dass in Java, hier meine einzige Lösung ist, den Wert von INSTANCE zu verändern, so dass es meine Version der Klasse mit der modifizierten Methode gibt.

Im Grunde möchte ich nur das Gespräch mit einer Sicherheitsprüfung wickeln und dann die ursprüngliche Methode aufrufen.

Die externe Bibliothek verwendet immer getInstance() eine Instanz dieser Klasse zu erhalten (das heißt, es ist ein Singleton).

EDIT: Nur um zu klären, getInstance() durch die externe Bibliothek aufgerufen wird, nicht mein Code, so dass nur Subklassen werden das Problem nicht lösen.

Wenn ich nicht tun kann, dass die einzige andere Lösung, die ich mir vorstellen kann ist ganze Klasse kopieren und einfügen und die Methode ändern. Das ist nicht ideal, da ich meine Gabel halten müssen werden mit Änderungen an der Bibliothek auf dem Laufenden. Wenn jemand etwas mehr wartbar hat bin ich offen für Vorschläge.

War es hilfreich?

Lösung

Es ist möglich. Ich habe diese freche threadlocals monkeypatch verwendet, die Klasse Entladen in Webapps verhinderten. Sie müssen nur Reflexion verwenden, um den final Modifikator zu entfernen, dann können Sie das Feld ändern.

So etwas tut den Trick:

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

Es gibt einige Caching als auch um Field#set, also wenn einige Code ausführen, bevor es vielleicht nicht unbedingt arbeiten ....

Andere Tipps

Jeder AOP Rahmen passen würde Ihre Bedürfnisse

Es würde erlauben Ihnen eine Laufzeitkorrektur für die getInstance Methode zu definieren, so dass Sie, was Klasse paßt zu Ihrem Bedarf zurückzukehren.

JMockit verwendet den ASM Rahmen intern das gleiche zu tun.

Sie können folgende versuchen. Hinweis: Es ist durchaus nicht Thread sicher ist und dies nicht funktioniert für konstante Primitiven zum Zeitpunkt der Kompilierung bekannt (wie sie vom Compiler inlined werden)

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

Es soll möglich sein, es mit JNI zu ändern ... nicht sicher, ob das eine Option für Sie ist.

EDIT:. Es ist möglich, aber nicht eine gute Idee,

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

  

10,9 Verletzen Zugriffssteuerungsregeln

     

Die JNI erzwingt keine Klasse, Feld,   und Verfahren Zugriffskontrolle Einschränkungen   das kann auf der Java ausgedrückt werden   Programmiersprache Ebene durch die   Verwendung von Modifikatoren wie private und   Finale. Es ist möglich, native zu schreiben   Code zuzugreifen oder zu modifizieren Felder ein   Objekt, auch wenn dies auf dem Tun   Java-Programmiersprache Ebene würde   führen zu einem Illegal.   JNI der Permissivität war eine bewusste   Design-Entscheidung, da die einheimischen   Code kann eine beliebigen Speicher zugreifen und diese ändern   Lage auf der Halde sowieso.

     

Native Code, umgeht   source-language-Ebene Zugriffsüberprüfungen   kann unerwünschte Auswirkungen auf   Programmausführung. Zum Beispiel kann ein   Inkonsistenz kann, wenn eine erstellt werden   native Methode modifiziert ein Endfeld   nach einem Just-in-Time-Compiler (JIT)   hat inlined Zugriffe auf das Feld.   In ähnlicher Weise sollten native Methoden nicht   unveränderliche Objekte ändern wie   Felder in Instanzen   java.lang.String oder java.lang.Integer.   Andernfalls kann es führen zu Bruch   Invarianten in der Java-Plattform   Umsetzung.

Wenn Sie wirklich müssen (obwohl für unser Problem, das ich würde vorschlagen, Sie die Lösung von CaptainAwesomePants verwenden) konnte man einen Blick auf JMockit . Obwohl dieses intented in Unit-Tests verwendet werden, wenn ermöglicht es Ihnen, beliebige Methoden neu zu definieren. Dies geschieht durch den Bytecode zur Laufzeit zu ändern.

Ich werde diese Antwort Vorwort durch die Anerkennung, dass dies nicht wirklich eine Antwort auf Ihre angegebene Frage über ein privates static final Feld zu ändern. Jedoch in dem speziellen Beispiel Code oben erwähnt, ich in der Tat kann es machen, so dass Sie doSomething außer Kraft setzen können (). Was Sie tun können, ist sich die Tatsache zunutze zu tragen, dass getInstance () ist eine öffentliche Methode und Unterklasse:

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

   public SomeClass getInstance() {
        return INSTANCE;
   }

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

Nun rufen nur MySomeClass.getInstance () anstelle von SomeClass.getInstance () und du bist gut zu gehen. Natürlich funktioniert dies nur, wenn Sie die eine Berufung auf getInstance () und nicht einen anderen Teil der unmodifiable Sachen sind mit dem Sie arbeiten.

Mockito ist sehr einfach:

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

Mit diesem Code druckt "etwas verändert!"; Sie können Ihre Singleton Instanzen leicht ersetzen. My $ 0,02 Cent.

Wenn kein externer Hack verfügbar ist (zumindest ich bin mir nicht bewusst) Ich würde selbst die Klasse gehackt haben. Ändern Sie den Code, indem Sie die Sicherheitskontrolle Sie wollen. Als solche seine externe Bibliothek, werden Sie nicht die Updates nehmen werden ohnehin regelmäßig, auch nicht viele Aktualisierung geschieht. Jedes Mal, wenn das passiert, kann ich glücklich wieder tut es, wie es ohnehin keine große Aufgabe.

Hier ist Ihr Problem guten alten Dependency Injection (auch bekannt als Inversion of Control). Ihr Ziel sollte die Implementierung von SomeClass zu injizieren, anstatt es zu monkeypatching. Und ja, erfordert dieser Ansatz einige Änderungen an Ihrem bestehendes Design, sondern aus den richtigen Gründen (Ihre Lieblinge Bauprinzip hier nennen) - insbesondere das gleiche Objekt soll nicht dafür verantwortlich sein, sowohl das Erstellen und andere Objekte verwenden.

Ich gehe davon aus, wie Sie SomeClass verwenden sieht etwas wie folgt aus:

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

    // some more stuff here...
  }
}

Statt dessen, was Sie tun sollten, ist erste Klasse erzeugen, die die gleiche Schnittstelle implementiert oder SomeClass erstreckt und dann diese Instanz passieren doEverything() so wird Ihre Klasse Agnostiker zu Implementierung von SomeClass. In diesem Fall, dass der Code doEverything nennt, ist verantwortlich für die korrekte Umsetzung vorbei - ob den tatsächlichen SomeClass oder Ihre monkeypatched MySomeClass sein.

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...
  }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top