it-swarm.com.de

Privates statisches Endfeld mithilfe von Java-Reflektion ändern

Ich habe eine Klasse mit einem private static final-Feld, das ich leider zur Laufzeit ändern muss.

Mit Reflektion bekomme ich diese Fehlermeldung: Java.lang.IllegalAccessException: Can not set static final boolean field

Gibt es eine Möglichkeit, den Wert zu ändern?

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
407
fixitagain

Wenn Sie davon ausgehen, dass SecurityManager Sie nicht daran hindert, können Sie setAccessible verwenden, um private umzugehen und den Modifikator zurückzusetzen, um final zu entfernen, und ein private static final-Feld tatsächlich ändern.

Hier ist ein Beispiel:

import Java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

Wenn kein SecurityException ausgelöst wird, gibt der obige Code "Everything is true" aus.

Was hier gemacht wird, ist wie folgt:

  • Die primitiven boolean-Werte true und false in main sind autoboxiert mit dem Referenztyp Boolean "Konstanten" Boolean.TRUE und Boolean.FALSE.
  • Reflection wird verwendet, um den public static final Boolean.FALSE zu ändern, um auf den Boolean zu verweisen, auf den Boolean.TRUE verwiesen wird.
  • Daraufhin bezieht sich jedes Mal, wenn eine false zu Boolean.FALSE autoboxed ist, auf dieselbe Boolean wie die von Boolean.TRUE bezeichnete.
  • Alles was jetzt "false" war, ist "true"

Verwandte Fragen


Vorsichtsmaßnahmen

Immer, wenn Sie so etwas tun, ist äußerste Vorsicht geboten. Es funktioniert möglicherweise nicht, weil SecurityManager vorhanden ist, aber auch wenn dies nicht der Fall ist, funktioniert es je nach Verwendungsmuster möglicherweise nicht.

JLS 17.5.3 Nachträgliche Änderung der endgültigen Felder

In einigen Fällen, beispielsweise bei der Deserialisierung, muss das System die final-Felder eines Objekts nach der Konstruktion ändern. final-Felder können durch Reflektion und andere implementierungsabhängige Mittel geändert werden. Das einzige Muster, in dem dies eine sinnvolle Semantik hat, ist eines, in dem ein Objekt erstellt wird und dann die final-Felder des Objekts aktualisiert werden. Das Objekt sollte nicht für andere Threads sichtbar gemacht werden und die final-Felder sollten erst gelesen werden, wenn alle Aktualisierungen der final-Felder des Objekts abgeschlossen sind. Einfrieren eines final-Feldes tritt sowohl am Ende des Konstruktors auf, in dem das final-Feld gesetzt ist, als auch unmittelbar nach jeder Änderung eines final-Feldes durch Reflexion oder einen anderen speziellen Mechanismus.

Selbst dann gibt es eine Reihe von Komplikationen. Wenn ein final-Feld in der Felddeklaration auf eine Kompilierungszeitkonstante initialisiert wird, können Änderungen am final-Feld nicht beobachtet werden, da die Verwendung dieses final-Felds zur Kompilierzeit durch die Kompilierungszeitkonstante ersetzt wird.

Ein weiteres Problem ist, dass die Spezifikation eine aggressive Optimierung von final-Feldern ermöglicht. Innerhalb eines Threads ist es zulässig, Lesevorgänge eines final-Felds mit den Änderungen eines abschließenden Feldes neu zu ordnen, die nicht im Konstruktor stattfinden.

Siehe auch

  • JLS 15.28 Konstanter Ausdruck
    • Es ist unwahrscheinlich, dass diese Technik mit einem primitiven private static final boolean arbeitet, da sie als Kompilierungszeitkonstante nicht inlinierbar ist und der "neue" Wert daher möglicherweise nicht beobachtet werden kann

Anhang: Zur bitweisen Manipulation

Im Wesentlichen,

field.getModifiers() & ~Modifier.FINAL

schaltet das Bit aus, das Modifier.FINAL von field.getModifiers() entspricht. & ist das bitweise-und und ~ ist das bitweise Komplement.

Siehe auch


Denken Sie an konstante Ausdrücke

Immer noch nicht in der Lage, das zu lösen? Sind Sie auf Depression gefallen, wie ich es getan habe? Sieht Ihr Code so aus?

public class A {
    private final String myVar = "Some Value";
}

Als ich die Kommentare zu dieser Antwort las, insbesondere die von @Pshemo, erinnerte es mich daran, dass Konstante Ausdrücke unterschiedlich gehandhabt werden, so dass es unmöglich ist, sie zu ändern. Daher müssen Sie Ihren Code so ändern, dass er wie folgt aussieht:

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}

wenn du nicht der Besitzer der Klasse bist ... ich fühle dich!

Für weitere Informationen darüber, warum dieses Verhalten lesen Sie dieses ?

775

Wenn der einem static final boolean-Feld zugewiesene Wert zur Kompilierzeit bekannt ist, handelt es sich um eine Konstante. Felder vom Typ primitiv oder String können Konstanten zur Kompilierzeit sein. Eine Konstante wird in jedem Code eingefügt, der auf das Feld verweist. Da das Feld zur Laufzeit nicht wirklich gelesen wird, hat das Ändern des Felds keine Auswirkungen.

Die Java-Sprachspezifikation sagt dies:

Wenn ein Feld eine konstante Variable ist (§4.12.4) und löscht dann das Schlüsselwort Endgültig oder Ändern des Werts wird nicht bricht die Kompatibilität mit bereits vorhandenen Binärdateien, indem sie nicht ausgeführt werden aber sie sehen keinen neuen Wert für die Verwendung des Feldes, sofern sie nicht werden neu kompiliert. Dies gilt auch wenn Die Verwendung selbst ist keine Kompilierungszeit konstanter Ausdruck (§15.28)

Hier ist ein Beispiel:

class Flag {
  static final boolean FLAG = true;
}

class Checker {
  public static void main(String... argv) {
    System.out.println(Flag.FLAG);
  }
}

Wenn Sie Checker dekompilieren, sehen Sie, dass anstelle von Flag.FLAG der Code einfach den Wert 1 (true) auf den Stack schiebt (Anweisung Nr. 3).

0:   getstatic       #2; //Field Java/lang/System.out:Ljava/io/PrintStream;
3:   iconst_1
4:   invokevirtual   #3; //Method Java/io/PrintStream.println:(Z)V
7:   return
47
erickson

Eine kleine Neugier aus der Java-Sprachspezifikation, Kapitel 17, Abschnitt 17.5.4 "Schreibgeschützte Felder":

Normalerweise kann ein Feld, das endgültig und statisch ist, nicht geändert werden. System.in, System.out und System.err sind jedoch statische Endfelder dass es aus alten Gründen erlaubt sein muss, von den Methoden geändert zu werden System.setIn, System.setOut und System.setErr. Wir verweisen auf diese Felder als schreibgeschützt, um sie von normalen .__ zu unterscheiden. letzte Felder.

Quelle: http://docs.Oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4

12

Ich habe es auch in joor library integriert

Benutz einfach 

      Reflect.on(yourObject).set("finalFieldName", finalFieldValue);

Ich habe auch ein Problem mit override behoben, das die vorherigen Lösungen scheinbar zu übersehen scheinen ..__ Verwenden Sie dies jedoch sehr sorgfältig, wenn keine andere gute Lösung vorliegt.

6
iirekm

Neben der Top-Antwort können Sie einen etwas einfachsten Ansatz verwenden. Die Klasse FieldUtils von Apache commons verfügt bereits über eine bestimmte Methode, die das erledigen kann. Bitte schauen Sie sich die FieldUtils.removeFinalModifier-Methode an. Sie sollten die Zielfeldinstanz und das Kennzeichen für die Erzwingung der Erreichbarkeit angeben, wenn Sie mit nicht öffentlichen Feldern spielen. Mehr Infos finden Sie hier .

5
nndru

Bei Vorhandensein eines Sicherheitsmanagers kann AccessController.doPrivileged verwendet werden. 

Nehmen Sie das gleiche Beispiel aus der oben akzeptierten Antwort:

import Java.lang.reflect.*;

public class EverythingIsTrue {
    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");

        // wrapping setAccessible 
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                modifiersField.setAccessible(true);
                return null;
            }
        });

        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }

    public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);
      System.out.format("Everything is %s", false); // "Everything is true"
    }
}

Im Lambda-Ausdruck kann AccessController.doPrivileged folgendermaßen vereinfacht werden:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});
4
VanagaS

Die akzeptierte Antwort funktionierte für mich bis zu ihrer Bereitstellung auf JDK 1.8u91 . Dann wurde mir klar, dass die field.set(null, newValue);-Zeile fehlgeschlagen ist, als ich den Wert vor dem Aufruf der setFinalStatic-Methode über Reflektion gelesen hatte.

Wahrscheinlich hat der Lesevorgang zu einer anderen Einrichtung der Java-Reflection-Interna geführt (nämlich Sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl im Fehlerfall anstelle von Sun.reflect.UnsafeStaticObjectFieldAccessorImpl im Erfolgsfall), aber ich habe es nicht weiter ausgeführt.

Da ich vorübergehend einen neuen Wert basierend auf dem alten Wert festlegen und den alten Wert später zurücksetzen musste, habe ich die Signatur etwas geändert, um die Berechnungsfunktion extern bereitzustellen und auch den alten Wert zurückzugeben:

public static <T> T assignFinalField(Object object, Class<?> clazz, String fieldName, UnaryOperator<T> newValueFunction) {
    Field f = null, ff = null;
    try {
        f = clazz.getDeclaredField(fieldName);
        final int oldM = f.getModifiers();
        final int newM = oldM & ~Modifier.FINAL;
        ff = Field.class.getDeclaredField("modifiers");
        ff.setAccessible(true);
        ff.setInt(f,newM);
        f.setAccessible(true);

        T result = (T)f.get(object);
        T newValue = newValueFunction.apply(result);

        f.set(object,newValue);
        ff.setInt(f,oldM);

        return result;
    } ...

Für den allgemeinen Fall wäre dies jedoch nicht ausreichend.

2

Selbst wenn ein Feld final ist, kann es außerhalb des statischen Initialisierers geändert werden, und (mindestens JVM HotSpot) führt den Bytecode einwandfrei aus.

Das Problem ist, dass der Compiler Java dies nicht zulässt, dies kann jedoch mit objectweb.asm leicht umgangen werden. Hier ist eine vollständig gültige Klassendatei, die die Bytecode-Überprüfung besteht und unter JVM HotSpot OpenJDK12 erfolgreich geladen und initialisiert wurde:

ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "Java/lang/Object", null);
{
    FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
    fv.visitEnd();
}
{
    // public void setFinalField1() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_5);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
{
    // public void setFinalField2() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_2);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
cw.visitEnd();

In Java sieht die Klasse ungefähr so ​​aus:

public class Cl{
    private static final int fld;

    public static void setFinalField1(){
        fld = 5;
    }

    public static void setFinalField2(){
        fld = 2;
    }
}

die nicht mit javac kompiliert werden kann, sondern von JVM geladen und ausgeführt werden kann.

JVM HotSpot behandelt solche Klassen speziell in dem Sinne, dass verhindert wird, dass solche "Konstanten" an der konstanten Faltung teilnehmen. Diese Überprüfung erfolgt in der Bytecode-Umschreibungsphase der Klasseninitialisierung :

// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
   Symbol* field_name = cp->name_ref_at(bc_index);
   Symbol* field_sig = cp->signature_ref_at(bc_index);

   fieldDescriptor fd;
   if (klass->find_field(field_name, field_sig, &fd) != NULL) {
      if (fd.access_flags().is_final()) {
         if (fd.access_flags().is_static()) {
            if (!method->is_static_initializer()) {
               fd.set_has_initialized_final_update(true);
            }
          } else {
            if (!method->is_object_initializer()) {
              fd.set_has_initialized_final_update(true);
            }
          }
        }
      }
    }
}

Die einzige Einschränkung, die JVM HotSpot überprüft, besteht darin, dass das Feld final nicht außerhalb der Klasse geändert werden darf, in der das Feld final deklariert ist.

0
Some Name

Wenn Ihr Feld einfach privat ist, können Sie Folgendes tun:

MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");

und werfen/behandeln Sie NoSuchFieldException

0
Philip Rego