2009-04-20 13 views
10

So che questo è normalmente piuttosto stupido, ma non spararmi prima di leggere la domanda. Prometto di avere una buona ragione per doverlo fare :)Esiste un modo per modificare il valore di un campo `private static final` in Java al di fuori della classe?

È possibile modificare i normali campi privati ​​in java utilizzando la reflection, tuttavia Java genera un'eccezione di sicurezza quando si tenta di fare lo stesso per i campi final.

Immagino che questo sia applicato in modo rigoroso, ma ho pensato di chiederlo comunque nel caso qualcuno avesse scoperto un trucco per farlo.

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

ho essenzialmente vogliono scimmia-Patch SomeClass in modo che possa eseguire la mia versione di doSomething(). Dal momento che non c'è (a mia conoscenza) un modo per farlo veramente in java, la mia unica soluzione qui è modificare il valore di INSTANCE in modo che restituisca la mia versione della classe con il metodo modificato.

In sostanza, voglio solo chiudere 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 è un singleton).

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

Se non riesco a farlo, l'unica altra soluzione a cui posso pensare è copiare e incollare l'intera classe e modificare il metodo. Questo non è l'ideale in quanto dovrò tenere aggiornata la mia forcella con le modifiche alla libreria. Se qualcuno ha qualcosa di più gestibile, sono aperto ai suggerimenti.

+0

Peccato che non ci siano metodi di estensione in Java –

+0

Hai bisogno di python: P – Rexsung

+3

Mi sono preso la libertà di aggiungere "statico" a getInstance(), poiché probabilmente è così che sembra. –

risposta

9

È possibile. Ho usato questo per monkeypatch threadlocals impertinenti che impedivano lo scarico delle classi in webapps. Hai solo bisogno di usare il reflection per rimuovere il modificatore final, quindi puoi 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 così intorno Field#set, quindi se qualche codice ha eseguito prima che potrebbe non necessariamente lavorare ....

+0

Forse se hai eseguito questa discussione all'inizio dell'applicazione, risolverebbe il problema della cache? –

+0

problema di memorizzazione nella cache non è davvero un problema. Penso che accadrà solo se un altro metodo ha tentato di impostare il campo usando la riflessione. – benmmurphy

+0

Grazie, questo è esattamente quello che stavo cercando (vorrei poterti sorvolare di più, questo è bello e oscuro). –

0

Prefiggerò questa risposta riconoscendo che questa non è in realtà una risposta alla tua domanda su come modificare un campo finale statico privato. Tuttavia, nel codice di esempio specifico sopra menzionato, posso in effetti farlo in modo da poter eseguire l'override di DoSomething(). Che cosa si può fare è quello di approfittare del fatto che getInstance() è un metodo pubblico e sottoclasse:

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 il gioco è fatto partire. Ovviamente, questo funziona solo se tu stai invocando getInstance() e non qualche altra parte della roba immodificabile con cui stai lavorando.

+0

Purtroppo questo non è il caso, getInstance è invocato interamente dal codice interno della libreria. –

+0

Ah, scusami allora :( –

1

Se proprio devi (anche se per il nostro problema ti suggerirei di utilizzare la soluzione di CaptainAwesomePants) potresti dare un'occhiata a JMockIt. Sebbene questo sia inteso per essere utilizzato nei test unitari, se consente di ridefinire i metodi arbitrari. Questo viene fatto modificando il bytecode in fase di runtime.

1

Dovresti essere in grado di cambiarlo con JNI ... non sono sicuro che sia un'opzione per te.

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

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

10.9 La violazione di controllo di accesso Regole

La JNI non applica classe, campo, di accesso e restrizioni metodo di controllo che possono essere espresse a livello di linguaggio di programmazione Java attraverso l' uso di modificatori, come privato e finale. È possibile scrivere il codice nativo per accedere o modificare i campi di un oggetto anche se così facendo al livello di linguaggio di programmazione Java sarebbe portare a un IllegalAccessException. La permissività di JNI era una decisione di progettazione consapevole , dato che il codice nativo può accedere e modificare qualsiasi memoria nella posizione nell'heap comunque.

codice nativo che bypassa -lingua a livello sorgente controlli di accesso può avere effetti indesiderati su l'esecuzione del programma. Ad esempio, è possibile creare un'incoerenza se un metodo nativo modifica un campo finale dopo che un compilatore JIT (just-in-time) ha accesso in linea al campo. Analogamente, i metodi nativi non devono modificare oggetti immutabili come i campi in istanze di java.lang.String o java.lang.Integer. Ciò potrebbe causare la rottura degli invarianti nell'implementazione della piattaforma Java .

+0

Puoi elaborare? Ho pensato che JNI fosse usato solo per chiamare le librerie esterne (cioè C) –

+0

@JamesDavies: Il codice JNI può anche tornare nella JVM e accedere agli oggetti e alle classi Java – mhsmith

5

Qualsiasi quadro AOP si adatta alle vostre esigenze

Sarebbe permetterà di definire una sostituzione di runtime per il metodo getInstance che consente di tornare qualsiasi classe adatta alle proprie esigenze.

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

+0

Buona risposta La chiave non è quella di modificare il campo, ma di intercettare la chiamata per recuperarla. – skaffman

+0

Quindi questo sarebbe essenzialmente utilizzare lo stesso trucco di riscrittura bytecode che JMockit fa? –

+0

Non sono esattamente un esperto ma ci sono almeno due modi per uno usando l'API di strumentazione java 1.5 l'altro usando ASM e la manipolazione diretta del codice byte – Jean

-1

Se non è disponibile alcuna modifica esterna (almeno non ne sono a conoscenza) avrei hackerato la classe stessa. Cambia il codice aggiungendo il controllo di sicurezza che desideri. Come tale è una libreria esterna, non si prenderanno regolarmente gli aggiornamenti, anche se non molti aggiornamenti avvengono comunque. Ogni volta che succede, posso tranquillamente rifarlo perché comunque non è un compito importante.

1

È 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); 
+0

Sfortunatamente il gestore di sicurezza non apprezza questo - anche con setAccessible (true) non ama modificare i valori finali –

+0

Si potrebbe provare a impostare System. setSecurityManager (null) e vedere se è possibile disabilitarlo temporaneamente.;) –

-1

Qui, il tuo problema è una buona dose di dipendenza (aka Inversion of Control). Il tuo obiettivo dovrebbe essere quello di iniettare la tua implementazione di SomeClass anziché monkeypatching.E sì, questo approccio richiede alcune modifiche al design esistente, ma per i giusti motivi (dai il nome al tuo principio di design preferito qui) - in particolare lo stesso oggetto non dovrebbe essere responsabile sia della creazione che dell'utilizzo di altri oggetti.

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

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

    // some more stuff here... 
    } 
} 

Invece, ciò che si dovrebbe fare è prima creare la classe che implementa la stessa interfaccia o si estende SomeClass e poi passare a tale istanza doEverything() così la classe diventa agnostica all'implementazione di SomeClass. In questo caso, il codice che chiama doEverything è responsabile del passaggio dell'implementazione corretta, sia che si tratti dell'effettivo SomeClass o del proprio numero di telefono 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... 
    } 
} 
+0

Grazie, ma come menzionato nella domanda questo non funzionerà perché non sto chiamando SomeClass direttamente. Voglio cambiare l'istanza di SomeClass usata da una libreria esterna, non la chiamo mai direttamente dal mio codice. –

0

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

questa stampe di codice; "qualcosa è cambiato!" puoi facilmente sostituire le tue istanze singleton. I miei 0,02 $ centesimi.

Problemi correlati