Est-il possible de modifier la valeur d'un champ `final` statique privé en Java à l'extérieur de la classe?

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

  •  12-09-2019
  •  | 
  •  

Question

Je sais que ce qui est normalement assez stupide, mais ne me tire pas avant de lire la question. Je promets que j'ai une bonne raison pour avoir besoin de faire ceci:)

Il est possible de modifier des champs privés réguliers en Java en utilisant la réflexion, mais Java lance une exception de sécurité lorsque vous essayez de faire la même chose pour les champs final.

Je suppose que cela est strictement appliquée, mais pensé que je demanderais de toute façon juste au cas où quelqu'un avait trouvé un hack pour le faire.

Disons que j'ai une bibliothèque externe avec une classe « SomeClass »

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

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

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

Je veux essentiellement Monkey-Patch SomeClass pour que je puisse exécuter ma propre version de doSomething(). Comme il n'y a pas (à ma connaissance) de toute façon de faire vraiment en java, ma seule solution ici est de modifier la valeur de INSTANCE il retourne ma version de la classe avec la méthode modifiée.

Essentiellement, je veux juste envelopper l'appel avec un contrôle de sécurité puis appeler la méthode originale.

La bibliothèque externe utilise toujours getInstance() pour obtenir une instance de cette classe (à savoir qu'il est un singleton).

EDIT: Juste pour clarifier, getInstance() est appelé par la bibliothèque externe, mon code, si juste ne résoudra pas le sous-classement de la question.

Si je ne peux pas faire que la seule autre solution que je peux penser est à coller copie classe entière et modifier la méthode. Ce n'est pas idéal que je vais devoir garder ma fourchette à jour avec les changements à la bibliothèque. Si quelqu'un a quelque chose d'un peu plus maintenable je suis ouvert aux suggestions.

Était-ce utile?

La solution

Il est possible. Je l'ai utilisé pour ce vilain threadlocals qui monkey-patch empêchaient le déchargement de classe webapps. Vous avez juste besoin d'utiliser la réflexion pour supprimer le modificateur de final, vous pouvez modifier le champ.

Quelque chose comme ça fera l'affaire:

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

Il y a une mise en cache aussi bien autour Field#set, donc si un code a déjà été exécuté, il ne fonctionne pas nécessairement ....

Autres conseils

Tout cadre AOP serait adapté à vos besoins

Il vous permet de définir une dérogation d'exécution pour la méthode getInstance vous permettant de retourner tout ce qui classe convient à vos besoins.

Jmockit utilise le cadre de l'ASM en interne pour faire la même chose.

Vous pouvez essayer ce qui suit. Note: Il est pas du tout thread-safe et cela ne fonctionne pas pour les primitives constantes connues au moment de la compilation (comme ils sont inline par le compilateur)

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

Vous devriez être en mesure de le changer avec JNI ... pas sûr si cela est une option pour vous.

EDIT:. Il est possible, mais pas une bonne idée

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

  

10.9 des règles de contrôle d'accès Violer

     

La JNI n'applique pas classe, sur le terrain,   et les restrictions de contrôle d'accès de la méthode   qui peut être exprimé à Java   niveau de langage de programmation par la   utilisation des modificateurs tels que privé et   final. Il est possible d'écrire native   code pour accéder ou modifier les champs d'un   objet même si ce faisant à la   niveau Java langage de programmation serait   conduire à une IllegalAccessException.   La permissivité de JNI était une conscience   décision de conception, étant donné que natif   code peut accéder et modifier une mémoire   emplacement dans le tas de toute façon.

     

Le code natif qui court-circuite   accès au niveau de langue source de contrôles   peuvent avoir des effets indésirables sur   l'exécution du programme. un exemple,   incohérence peut être créé si   méthode native modifie un champ final   après un juste-à-temps (JIT)   a inline accède au champ.   De même, les méthodes natives ne devraient pas   modifier des objets tels que immuables   champs dans les cas de   java.lang.String ou java.lang.Integer.   Cela pourrait conduire à la rupture de   dans la plate-forme invariants Java   la mise en œuvre.

Si vous avez vraiment devez (bien que pour notre problème, je vous suggère d'utiliser la solution de CaptainAwesomePants), vous pouvez jeter un oeil à JMockIt . Bien que cela soit intented à utiliser dans les tests unitaires si vous permet de redéfinir les méthodes arbitraires. Cela se fait en modifiant le bytecode lors de l'exécution.

Je commencerai cette réponse en reconnaissant que ce n'est pas en fait une réponse à votre question énoncée sur la modification d'un champ static final privé. Cependant, dans le code exemple spécifique mentionné ci-dessus, je peux en fait, il faire en sorte que vous pouvez remplacer doSomething (). Ce que vous pouvez faire est de tirer profit du fait que getInstance () est une méthode publique et sous-classe:

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

   public SomeClass getInstance() {
        return INSTANCE;
   }

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

Maintenant, il suffit invoquer MySomeClass.getInstance () au lieu de SomeClass.getInstance () et vous êtes bon pour aller. Bien sûr, cela ne fonctionne que si vous êtes celui qui invoque getInstance () et non une autre partie de la substance non modifiable que vous travaillez avec.

Mockito est très simple:

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

ce code imprime "quelque chose a changé!"; vous pouvez facilement remplacer vos instances singleton. Mes 0,02 $ cents.

En cas j'aurais piraté la classe elle-même pas bidouille externe disponible (au moins je ne suis pas au courant). Modifiez le code en ajoutant le contrôle de sécurité que vous voulez. En tant que tel sa bibliothèque externe, vous ne prenez pas les mises à jour régulièrement, aussi pas beaucoup mise à jour arrive de toute façon. Chaque fois que cela arrive, je peux heureusement Refaire comme il est pas une grande tâche de toute façon.

Ici, votre problème est bon vieux Dependency Injection (alias Inversion of Control). Votre objectif devrait être d'injecter votre mise en œuvre de SomeClass au lieu de monkeypatching il. Et oui, cette approche nécessite des changements à votre conception existante, mais pour les bonnes raisons (le nom de votre principe de conception préféré) - en particulier le même objet ne doit pas être responsable de la création et l'utilisation d'autres objets.

Je suppose que la façon dont vous utilisez SomeClass ressemble un peu à ceci:

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

    // some more stuff here...
  }
}

Au lieu de cela, ce que vous devez faire est de créer d'abord votre classe qui implémente la même interface ou étend SomeClass et passer ensuite à cette instance doEverything() si votre classe devient agnostique à la mise en œuvre de SomeClass. Dans ce cas, le code qui appelle doEverything est responsable de la transmission dans la mise en œuvre correcte - que ce soit soit la SomeClass réelle ou votre monkeypatched 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...
  }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top