C'è un modo per modificare il valore di un campo `privato statica final` in Java dall'esterno della classe?

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

  •  12-09-2019
  •  | 
  •  

Domanda

So che questo è normalmente piuttosto stupido, ma non mi sparare prima di leggere la domanda. Prometto che ho una buona ragione per la necessità di fare questo:)

E 'possibile modificare i campi private regolari in Java utilizzando la riflessione, tuttavia Java genera un'eccezione di protezione quando si cerca di fare lo stesso per i campi final.

Mi piacerebbe Presumo che ciò viene applicata, ma pensato che avrei chiesto comunque solo nel caso in cui qualcuno aveva capito un hack per fare questo.

Diciamo solo dire che ho una libreria esterna con una 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 
  }
} 

I essenzialmente voglio scimmia-Patch SomeClass in modo che possa eseguire la mia versione di doSomething(). Dal momento che non c'è (a mia conoscenza) un modo per fare davvero così in Java, la mia unica soluzione è quella di modificare il valore di INSTANCE quindi restituisce la mia versione della classe con il metodo modificato.

In sostanza voglio solo avvolgere la chiamata con un controllo di sicurezza e quindi chiamare il metodo originale.

La libreria esterna utilizza sempre getInstance() per ottenere un'istanza di questa classe (vale a dire si tratta di un singolo).

EDIT: Giusto per chiarire, getInstance() viene chiamato dalla libreria esterna, non il mio codice, in modo solo sottoclasse non risolverà il problema.

Se non posso fare che l'unica altra soluzione mi viene in mente è quello di copiare e incollare tutta la classe e modificare il metodo. Questo non è l'ideale come dovrò mantenere la mia forchetta aggiornati con le modifiche alla libreria. Se qualcuno ha qualcosa di un po 'più gestibile Sono aperto a suggerimenti.

È stato utile?

Soluzione

E 'possibile. Ho usato questo per monkeypatch threadlocals cattivi che impedivano lo scarico di classe in webapps. Hai solo bisogno di usare riflessione per rimuovere il modificatore final, quindi è possibile modificare il campo.

Qualcosa di simile farà il trucco:

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

C'è un po 'di caching e intorno Field#set, quindi se qualche codice ha eseguito prima che potrebbe non necessariamente lavorare ....

Altri suggerimenti

Ogni quadro AOP si adatta alle vostre esigenze

E 'permetterebbe di definire una sostituzione di runtime per il metodo getInstance che consente di tornare qualsiasi classe adatta alle proprie esigenze.

JMockit utilizza il framework ASM internamente per fare la stessa cosa.

È possibile provare quanto segue. Nota: Non è affatto thread-safe e non funziona per primitive costanti noti al momento della compilazione (come sono inline dal compilatore)

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

Si dovrebbe essere in grado di cambiarlo con JNI ... non so se questo è un'opzione per voi.

EDIT:. È possibile, ma non è una buona idea

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

  

10.9 La violazione di accesso regole di controllo

     

La JNI non applica classe, campo,   e le restrizioni di controllo di accesso metodo   che può essere espresso in Java   livello di linguaggio di programmazione attraverso il   uso di modificatori, come privato e   finale. E 'possibile scrivere nativo   codice di accesso o modificare i campi di un   oggetto anche se così facendo al   livello di linguaggio di programmazione Java sarebbe   portare ad un IllegalAccessException.   permissivismo di JNI è stata una consapevole   decisione di progettazione, visto che nativo   codice può accedere e modificare qualsiasi memoria   posizione nel mucchio in ogni caso.

     

codice nativo che bypassa   i controlli di accesso in lingua a livello di sorgente   possono avere effetti indesiderati sulla   esecuzione del programma. Ad esempio, un   incoerenza può essere creato se un   metodo nativo modifica un campo finale   dopo un (JIT) just-in-time   ha inline accessi al campo.   Allo stesso modo, metodi nativi non dovrebbe   modificare oggetti immutabili come   campi in casi di   java.lang.String o java.lang.Integer.   Ciò potrebbe portare alla rottura   invarianti nella piattaforma Java   implementazione.

Se davvero si deve (anche se per il nostro problema Io suggerirei di utilizzare la soluzione di CaptainAwesomePants) si potrebbe avere uno sguardo a JMockit . Anche se questo è intented per essere utilizzato in unit test se desiderate ridefinire metodi arbitrari. Questo viene fatto modificando il bytecode in fase di esecuzione.

I vi prefazione questa risposta riconoscendo che questo non è in realtà una risposta alla tua domanda dichiarato sulla modifica di un campo finale private static. Tuttavia, nel codice di esempio specifico di cui sopra, posso infatti fare in modo che è possibile ignorare doSomething (). Che cosa si può fare è quello di approfittare del fatto che getInstance () è un metodo e sottoclasse pubblico:

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

   public SomeClass getInstance() {
        return INSTANCE;
   }

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

Ora basta richiamare MySomeClass.getInstance () al posto di SomeClass.getInstance () e si sta bene ad andare. Naturalmente, questo funziona solo se sei quello che invoca getInstance () e non qualche altra parte del roba immodificabile si sta lavorando.

Mockito è molto semplice:

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

Questo stampa di codici a "qualcosa è cambiato!"; si può facilmente sostituire le istanze di Singleton. Le mie 0.02 $ centesimi.

Se non c'è trucco esterna disponibile (almeno io non sono a conoscenza di) avrei violato la classe stessa. Modificare il codice aggiungendo il controllo di sicurezza che si desidera. Come tale la sua una libreria esterna, non sarà prendendo gli aggiornamenti con regolarità, anche non molti aggiornamento accade comunque. Ogni volta che ciò accade Posso tranquillamente ri-farlo in quanto non è un grande compito in ogni caso.

Qui, il problema è buon vecchio Dependency Injection (aka Inversion of Control). Il vostro obiettivo dovrebbe essere quello di iniettare l'implementazione di SomeClass invece di monkeypatching esso. E sì, questo approccio richiede alcune modifiche al progetto esistente, ma per le ragioni giuste (un nome al principio di progettazione preferito qui) - in particolare lo stesso oggetto non dovrebbe essere responsabile sia creazione e l'utilizzo di altri oggetti.

Suppongo che il modo in cui si sta utilizzando SomeClass sembra un po 'in questo modo:

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

    // some more stuff here...
  }
}

Al contrario, ciò che si dovrebbe fare è prima creare la classe che implementa la stessa interfaccia o si estende SomeClass e quindi passare tale istanza al doEverything() così la vostra classe diventa agnostica alla realizzazione di SomeClass. In questo caso il codice che chiama doEverything è responsabile per il passaggio nella corretta applicazione - se sia il SomeClass reale o la vostra MySomeClass monkeypatched.

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...
  }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top