reflection - Changer le champ final statique privé en utilisant la réflexion Java




static private (9)

J'ai une classe avec un private static final que, malheureusement, j'ai besoin de changer au moment de l'exécution.

En utilisant la réflexion, j'obtiens cette erreur: java.lang.IllegalAccessException: Can not set static final boolean field

Est-il possible de changer la valeur?

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

Answers

Si la valeur affectée à un static final boolean est connue à la compilation, il s'agit d'une constante. Les champs de type primitive ou String peuvent être des constantes de compilation. Une constante sera insérée dans tout code référençant le champ. Puisque le champ n'est pas lu au moment de l'exécution, le modifier n'aura aucun effet.

La spécification du langage Java dit ceci:

Si un champ est une variable constante (§4.12.4), alors la suppression du mot-clé final ou la modification de sa valeur ne rompt pas la compatibilité avec les binaires préexistants en les empêchant de s'exécuter, mais ne voit aucune nouvelle valeur pour l'utilisation du champ à moins qu'ils ne soient recompilés. Ceci est vrai même si l'utilisation elle-même n'est pas une expression de constante de compilation (§15.28)

Voici un exemple:

class Flag {
  static final boolean FLAG = true;
}

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

Si vous décompilez Checker , vous verrez qu'au lieu de référencer Flag.FLAG , le code pousse simplement une valeur de 1 ( true ) sur la pile (instruction # 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

La réponse acceptée a fonctionné pour moi jusqu'à déploiement sur JDK 1.8u91. Puis j'ai réalisé qu'il a échoué à field.set(null, newValue); ligne quand j'avais lu la valeur par réflexion avant d'appeler la méthode setFinalStatic .

Probablement la lecture a causé une configuration différente des internes de réflexion de Java (à savoir sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl dans le cas d'échec au lieu de sun.reflect.UnsafeStaticObjectFieldAccessorImpl dans le cas de succès) mais je n'ai pas élaboré plus loin.

Comme j'avais besoin de définir temporairement une nouvelle valeur en fonction de l'ancienne valeur et plus tard de rétablir l'ancienne valeur, j'ai légèrement modifié la signature pour fournir une fonction de calcul externe et retourner l'ancienne valeur:

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

Cependant, pour le cas général, cela ne serait pas suffisant.


Je l'ai également intégré à la bibliothèque joor

Juste utiliser

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

J'ai également corrigé un problème avec override que les solutions précédentes semblent manquer. Cependant, utilisez cela très attentivement, seulement quand il n'y a pas d'autre bonne solution.


Juste vu cette question sur l'une des questions d'entrevue, si possible de changer la variable finale avec réflexion ou en cours d'exécution. Je suis vraiment intéressé, alors ce que je suis devenu:

 /**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class SomeClass {

    private final String str;

    SomeClass(){
        this.str = "This is the string that never changes!";
    }

    public String getStr() {
        return str;
    }

    @Override
    public String toString() {
        return "Class name: " + getClass() + " Value: " + getStr();
    }
}

Une classe simple avec la variable String finale. Donc, dans la classe principale, import java.lang.reflect.Field;

/**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class Main {


    public static void main(String[] args) throws Exception{

        SomeClass someClass = new SomeClass();
        System.out.println(someClass);

        Field field = someClass.getClass().getDeclaredField("str");
        field.setAccessible(true);

        field.set(someClass, "There you are");

        System.out.println(someClass);
    }
}

La sortie sera la suivante:

Class name: class SomeClass Value: This is the string that never changes!
Class name: class SomeClass Value: There you are

Process finished with exit code 0

Selon la documentation https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html


En supposant qu'aucun SecurityManager ne vous empêche de faire cela, vous pouvez utiliser setAccessible pour contourner private et réinitialiser le modificateur pour se débarrasser de final , et en fait modifier un private static final .

Voici un exemple:

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"
   }
}

En supposant qu'aucune SecurityException n'est levée, le code ci-dessus affiche "Everything is true" .

Ce qui est réellement fait ici est comme suit:

  • Les valeurs boolean primitives true et false dans main sont auto-classées en type de référence "constantes" Boolean.TRUE et Boolean.FALSE
  • La réflexion est utilisée pour modifier le public static final Boolean.FALSE pour se référer au Boolean référé par Boolean.TRUE
  • Par conséquent, par la suite chaque fois qu'un false est autooboxed à Boolean.FALSE , il se réfère au même Boolean que celui Boolean.TRUE par Boolean.TRUE
  • Tout ce qui était "false" maintenant est "true"

Questions connexes

Avertissements

Des précautions extrêmes devraient être prises chaque fois que vous faites quelque chose comme ça. Cela peut ne pas fonctionner parce qu'un SecurityManager peut être présent, mais même si ce n'est pas le cas, selon le modèle d'utilisation, cela peut ou non fonctionner.

JLS 17.5.3 Modification ultérieure des champs finaux

Dans certains cas, comme la désérialisation, le système devra changer les champs final d'un objet après la construction. final champs final peuvent être modifiés par réflexion et d'autres moyens dépendant de la mise en œuvre. Le seul motif dans lequel ceci a une sémantique raisonnable est celui dans lequel un objet est construit et ensuite les champs final de l'objet sont mis à jour. L'objet ne doit pas être rendu visible aux autres threads, et les final champs ne doivent pas être lus, jusqu'à ce que toutes les mises à jour des champs final de l'objet soient terminées. Les gels d'un champ final se produisent à la fois à la fin du constructeur dans lequel le champ final est établi, et immédiatement après chaque modification d'un champ final par réflexion ou par un autre mécanisme spécial.

Même alors, il y a un certain nombre de complications. Si un champ final est initialisé à une constante de compilation dans la déclaration de champ, les modifications apportées au champ final peuvent ne pas être observées, puisque les utilisations de ce champ final sont remplacées au moment de la compilation par la constante de compilation.

Un autre problème est que la spécification permet une optimisation agressive des champs final . Au sein d'un thread, il est permis de réorganiser les lectures d'un champ final avec les modifications d'un champ final qui n'ont pas lieu dans le constructeur.

Voir également

  • JLS 15.28 Expression constante
    • Il est peu probable que cette technique fonctionne avec un private static final boolean primitif, car il est inlinable en tant que constante de compilation et donc la "nouvelle" valeur peut ne pas être observable

Annexe: Sur la manipulation des bits

Essentiellement,

field.getModifiers() & ~Modifier.FINAL

Désactive le bit correspondant à Modifier.FINAL partir de field.getModifiers() . & est le bitwise-et, et ~ est le complément au niveau du bit.

Voir également


En cas de présence d'un gestionnaire de sécurité, on peut utiliser AccessController.doPrivileged

Prenant le même exemple de la réponse acceptée ci-dessus:

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"
    }
}

Dans l'expression lambda, AccessController.doPrivileged , peut être simplifié pour:

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

Avec la réponse la mieux classée, vous pouvez utiliser une approche un peu plus simple. FieldUtils Apache FieldUtils classe FieldUtils possède déjà une méthode particulière capable de faire les choses. S'il vous plaît, jetez un oeil à la méthode FieldUtils.removeFinalModifier . Vous devez spécifier l'occurrence de champ cible et l'indicateur de forçage d'accessibilité (si vous jouez avec des champs non publics). Plus d'infos vous pouvez trouver here .


Tout le point d'un champ final est qu'il ne peut pas être réaffecté une fois qu'il a été défini. La JVM utilise cette garantie pour maintenir la cohérence à divers endroits (par exemple, des classes internes référençant des variables externes). Donc non. Etre capable de le faire casserait la JVM!

La solution n'est pas de le déclarer final en premier lieu.


Différence entre @Component, @Service et @Repository

La différence majeure entre ces stéréotypes est qu'ils sont utilisés pour une classification différente.

Dans une application à plusieurs niveaux, nous aurons différentes couches comme la présentation, le service, l'entreprise, l'accès aux données, etc. Lorsqu'une classe doit être annotée pour la détection automatique par Spring, nous devrions utiliser le stéréotype respectif comme ci-dessous.

@Component - générique et peut être utilisé à travers l'application.
@Service - annote les classes au niveau de la couche de service.
@Repository - annote des classes à la couche de persistance, qui agira comme référentiel de base de données.

Si techniquement ils vont être identiques alors pourquoi avons-nous besoin de les utiliser au niveau des couches différentes. Pourquoi ne pas utiliser la même chose à tous les niveaux. Par exemple, si nous utilisons @Service dans toutes les couches, tous les beans seront instanciés et aucun problème. Il y a une différence mineure, par exemple considérons @Repository .

Le postprocesseur recherche automatiquement tous les traducteurs d'exception (implémentations de l'interface PersistenceExceptionTranslator) et conseille tous les beans marqués avec l'annotation @Repository afin que les traducteurs @Repository puissent intercepter et appliquer la traduction appropriée sur les exceptions @Repository .

Similaire à ce qui précède, à l'avenir, Spring peut choisir d'ajouter de la valeur pour @Service , @Controller et @Repository fonction de leurs conventions de superposition. Pour cette caractéristique supplémentaire, il vaut mieux respecter la convention et les utiliser en ligne avec les couches.

Outre ce qui précède, en ce qui concerne la détection automatique, l'injection de dépendances pour BeanDefinition @Component , @Service , @Repository et @Controller sont identiques.





java reflection static private final