573 Stimmen

Privates statisches Endfeld mit Java-Reflexion ändern

Ich habe eine Klasse mit einer private static final Feld, das ich leider während der Laufzeit ändern muss.

Bei der Verwendung von reflection erhalte ich diesen Fehler: 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);

4 Stimmen

Das ist eine schlechte Idee. Ich würde stattdessen versuchen, den Quellcode zu bekommen und neu zu kompilieren (oder sogar zu dekompilieren/neu zu kompilieren).

0 Stimmen

System.out ist ein öffentliches, statisches, endgültiges Feld, das aber auch geändert werden kann.

21 Stimmen

@irreputable System.out/in/err sind so "speziell", dass sie im Java Memory Model besonders erwähnt werden müssen. Sie sind keine Beispiele, denen man folgen sollte.

7voto

Some Name Punkte 7628

Auch wenn sie final ein Feld kann außerhalb des statischen Initialisierers geändert werden und (zumindest in JVM HotSpot) wird der Bytecode einwandfrei ausgeführt.

Das Problem ist, dass der Java-Compiler dies nicht zulässt, aber das kann leicht umgangen werden mit objectweb.asm . Hier ist eine perfekt gültige Klassendatei eine ungültige Klassendatei aus der Sicht der JVMS-Spezifikation aber es besteht die Bytecode-Verifizierung und wird dann erfolgreich unter JVM HotSpot OpenJDK12 geladen und initialisiert:

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 grob gesagt wie folgt 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 kann aber von der JVM geladen und ausgeführt werden.

JVM HotSpot hat eine besondere Behandlung solcher Klassen in dem Sinne, dass es verhindert, dass solche "Konstanten" an der Konstantenfaltung teilnehmen. Diese Prüfung erfolgt auf 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, ist, dass die final Feld sollte nicht außerhalb der Klasse geändert werden, in der die final Feld deklariert ist.

4 Stimmen

Das ist einfach nur pur EVIL und schön.

4 Stimmen

Ich bin nicht einverstanden mit der "vollkommen gültigen Klassendatei". Die JVMS §6.5 sagt eindeutig: "Andernfalls, wenn das aufgelöste Feld endgültig ist, muss es in der aktuellen Klasse oder Schnittstelle deklariert werden, und die Anweisung muss in der Initialisierungsmethode der Klasse oder des Interfaces vorkommen der aktuellen Klasse oder Schnittstelle. Andernfalls wird ein IllegalAccessError geworfen". Dies ist also nur ein weiterer Fall, in dem die Implementierung in eklatanter Weise gegen die Spezifikation verstößt und Code an mehreren Stellen verteilt ist, um das zu behandeln, was eigentlich hätte abgelehnt werden müssen

0 Stimmen

@Holger Danke für den Hinweis. Ich habe eine Korrektur entsprechend Ihrer Anmerkung vorgenommen, um weitere Leser nicht zu verwirren.

6voto

VanagaS Punkte 2287

Falls ein Sicherheitsmanager vorhanden ist, kann man AccessController.doPrivileged

Nehmen wir das gleiche Beispiel wie in der obigen 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, AccessController.doPrivileged kann vereinfacht werden zu:

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

0 Stimmen

Ja @dan1st, Sie haben Recht! Bitte überprüfen Sie dies für eine Lösung: stackoverflow.com/a/56043252/2546381

5voto

pringi Punkte 3109

Ab Java 12 funktionieren die angegebenen Antworten nicht mehr.

Hier ist ein Beispiel für die Änderung einer private static final Feld seit Java 12 (basierend auf diese Antwort ).

  private Object modifyField(Object newFieldValue, String fieldName, Object classInstance) throws NoSuchFieldException, IllegalAccessException {
    Field field = classInstance.getClass().getDeclaredField(fieldName);
    VarHandle MODIFIERS;

    field.setAccessible(true);

    var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
    MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
    int mods = field.getModifiers();

    if (Modifier.isFinal(mods)) {
      MODIFIERS.set(field, mods & ~Modifier.FINAL);
    }

    Object previousValue = field.get(classInstance);
    field.set(null, newFieldValue);

    return previousValue;
  }

Ver dieses Thema für weitere Einzelheiten.

1 Stimmen

Dies funktioniert nicht mit Java 16+.

0 Stimmen

@JohannesKuhn funktioniert, wenn Sie Folgendes hinzufügen --illegal-access=permit

1 Stimmen

--illegal-access=permit wurde in Java 17 entfernt.

4voto

Brice Punkte 36676

Mit JDK 18 wird dies nicht mehr möglich sein, da die Kernreflexion neu implementiert wurde und über invokedynamic y MethodHandle s als Teil von JEP-416 ( PR ).

Zitat von Mandy Chung - der der Hauptautor dieses unglaublichen Werkes ist - im folgender Kommentar . Hervorhebungen sind von mir.

Wenn das zugrunde liegende Feld endgültig ist, wird ein Field Objekt hat Schreibzugriff wenn und nur wenn

  • setAccessible(true) ist dies gelungen Field Objekt;
  • das Feld ist nicht statisch; und
  • die deklarierende Klasse des Feldes keine verborgene Klasse ist; und
  • die deklarierende Klasse des Feldes ist keine Datensatzklasse.

3voto

d4vidi Punkte 2199

Viele der hier gegebenen Antworten sind nützlich, aber ich habe festgestellt, dass keine von ihnen bei folgenden Problemen hilft Android insbesondere. Ich bin sogar ein ziemlich intensiver Nutzer von Reflect von joor und weder sie noch apache 's FieldUtils - die hier in einigen der Antworten erwähnt wurden, erfüllen den Zweck.

Problem mit Android

Der Hauptgrund dafür ist, dass es auf Android keine modifiers Feld in der Field Klasse, was jeden Vorschlag, der diesen Code beinhaltet (wie in der markierten Antwort), unbrauchbar macht:

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

In der Tat, um zu zitieren aus FieldUtils.removeFinalModifier() :

// Do all JREs implement Field with a private ivar called "modifiers"?
final Field modifiersField = Field.class.getDeclaredField("modifiers");

Die Antwort ist also nein...

Lösung

Ziemlich einfach - statt modifiers lautet der Feldname accessFlags . Das ist der Trick:

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

Nebenbemerkung Nr. 1: Dies kann unabhängig davon funktionieren, ob das Feld in der Klasse statisch ist oder nicht.

Nebenbemerkung #2: Da das Feld selbst privat sein könnte, wird empfohlen, auch den Zugriff auf das Feld selbst zu ermöglichen, indem man field.setAccessible(true) (zusätzlich zu accessFlagsField.setAccessible(true) .

1 Stimmen

Wenn Sie den Originalcode in Android ausführen, erhalten Sie folgende Fehlermeldung: java.lang.NoSuchFieldException: No field modifiers in class Ljava/lang/reflect/Field; (declaration of 'java.lang.reflect.Field' appears in /apex/com.android.runtime/javalib/core-oj.jar) . Die vorgeschlagene Lösung funktioniert in meinem Fall. (Dieser Fehler hat derzeit nur ein Google-Ergebnis, so dass hoffentlich Menschen jetzt diese Seite finden werden)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X