C'è un modo per modificare il valore di un campo `privato statica final` in Java dall'esterno della classe?
-
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.
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...
}
}