2009-04-20 16 views
10

Sé que esto es normalmente bastante estúpido, pero no me disparen antes de leer la pregunta. Prometo que tengo una buena razón para necesitar hacer esto :)¿Hay alguna forma de modificar el valor de un campo `private static final` en Java desde fuera de la clase?

Es posible modificar campos privados regulares en java mediante reflexión, sin embargo, Java arroja una excepción de seguridad cuando se intenta hacer lo mismo para los campos final.

Supongo que esto se aplica estrictamente, pero pensé que lo haría de todos modos en caso de que alguien hubiera descubierto un truco para hacer esto.

Digamos que tengo una biblioteca externa con una clase "SomeClass"

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

    public static SomeClass getInstance(){ 
     return INSTANCE; 
    } 

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

que esencialmente quiere mono-Patch SomeClass para que pueda ejecutar mi propia versión de doSomething(). Como no hay (que yo sepa) alguna forma de hacer eso realmente en Java, mi única solución aquí es alterar el valor de INSTANCE para que devuelva mi versión de la clase con el método modificado.

Básicamente, solo quiero ajustar la llamada con un control de seguridad y luego llamar al método original.

La biblioteca externa siempre usa getInstance() para obtener una instancia de esta clase (es decir, es un singleton).

EDITAR: Solo para aclarar, getInstance() es llamado por la biblioteca externa, no es mi código, así que solo subclases no resolverá el problema.

Si no puedo hacer eso, la única otra solución que puedo pensar es copiar y pegar toda la clase y modificar el método. Esto no es ideal, ya que tendré que mantener actualizado mi fork con los cambios en la biblioteca. Si alguien tiene algo un poco más fácil de mantener, estoy abierto a sugerencias.

+0

Lástima que no hay ningún método de extensión en Java –

+0

Necesita Python: P – Rexsung

+3

Me tomé la libertad de agregar "static" a getInstance(), ya que así es como se ve. –

Respuesta

9

Es posible. Lo he usado para parchar tramas de hilos traviesas que estaban impidiendo la descarga de clases en webapps. Solo necesita usar el reflejo para eliminar el modificador final, luego puede modificar el campo.

Algo como esto hará el truco:

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

Hay algo de almacenamiento en caché, así Field#set alrededor, por lo que si algún código se ejecute antes de que podría no necesariamente funciona ....

+0

Tal vez si ejecutó este hilo al principio de la aplicación, ¿solucionaría el problema del almacenamiento en caché? –

+0

problema de almacenamiento en caché no es realmente un problema. Creo que solo sucederá si otro método ha intentado establecer el campo usando la reflexión. – benmmurphy

+0

Gracias, esto es exactamente lo que estaba buscando (Ojalá pudiera votarte más, esto es agradable y oscuro). –

0

Prefacio a esta respuesta al reconocer que esta no es en realidad una respuesta a su pregunta establecida sobre la modificación de un campo privado estático final. Sin embargo, en el código de ejemplo específico mencionado anteriormente, de hecho puedo hacerlo para que pueda anular doSomething(). Lo que puede hacer es tomar ventaja del hecho de que getInstance() es un método público y la subclase:

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

    public SomeClass getInstance() { 
     return INSTANCE; 
    } 

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

Ahora acaba de invocar MySomeClass.getInstance() en lugar de SomeClass.getInstance() y ya está bueno para ir. Por supuesto, esto solo funciona si usted es el que invoca getInstance() y no alguna otra parte de las cosas no modificables con las que está trabajando.

+0

Desafortunadamente este no es el caso, getInstance es invocado completamente por el código interno de la biblioteca. –

+0

Ah, perdón entonces :( –

1

Si realmente debe (aunque para nuestro problema le sugiero que use la solución de CaptainAwesomePants) puede echar un vistazo a JMockIt. Aunque esto está pensado para usarse en pruebas unitarias si le permite redefinir métodos arbitrarios. Esto se hace modificando el bytecode en el tiempo de ejecución.

1

Debería poder cambiarlo con JNI ... no estoy seguro si esa es una opción para usted.

EDITAR: es posible, pero no es una buena idea.

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

10.9 Reglas La violación de control de acceso

El JNI no hace cumplir la clase, campo, y de acceso método restricciones de control que se pueden expresar a nivel lenguaje de programación Java a través del uso de modificadores como privado y final. Es posible escribir el código nativo para acceder o modificar los campos de un objeto aunque hacerlo en el nivel de lenguaje de programación Java sería dando lugar a una IllegalAccessException. La permisividad de JNI fue una decisión de diseño consciente , dado que el código nativo puede acceder y modificar cualquier ubicación de memoria en el montón de todos modos.

código nativo que no pasa por comprobaciones de acceso-a nivel de idioma de origen puede tener efectos no deseados sobre la ejecución del programa. Por ejemplo, una incoherencia se puede crear si un método nativo modifica un campo final después de que un compilador just-in-time (JIT) tenga accesos en línea al campo. De manera similar, los métodos nativos no deberían modificar los objetos inmutables como los campos en instancias de java.lang.String o java.lang.Integer. Hacerlo puede ocasionar la rotura de invariantes en la implementación de la plataforma Java .

+0

¿Puedes elaborar? Pensé que JNI solo se usaba para llamar bibliotecas externas (es decir, C) –

+0

@JamesDavies: el código JNI también puede volver a la JVM y acceder a clases y objetos Java. – mhsmith

5

Cualquier marco AOP se ajuste a sus necesidades

Sería permitirá definir una anulación de tiempo de ejecución para el método getInstance que le permite regresar cualquier clase adapte a sus necesidades.

Jmockit utiliza internamente el marco ASM para hacer lo mismo.

+0

Buena respuesta La clave no es modificar el campo, sino interceptar la llamada para recuperarlo. – skaffman

+0

¿Entonces esto sería esencialmente usar el mismo truco de reescritura de código byte que hace JMockit? –

+0

No soy exactamente un experto, pero hay al menos dos formas de hazlo. Uno que usa API de instrumentación java 1.5 y el otro usa ASM y manipulación manual de código byte – Jean

-1

Si no hay ningún truco externo disponible (al menos no estoy al tanto) habría pirateado la clase. Cambie el código agregando el control de seguridad que desee. Como tal, es una biblioteca externa, no tomará las actualizaciones regularmente, tampoco muchas actualizaciones sucederán de todos modos. Cada vez que eso sucede puedo felizmente volver a hacerlo, ya que no es una gran tarea de todos modos.

1

Puede intentar lo siguiente. Nota: No es en absoluto hilo de seguridad y esto no funciona para las primitivas constantes conocidas en tiempo de compilación (ya que están entre líneas por el compilador)

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

Desafortunadamente, a Security Manager no le gusta esto, incluso con setAccessible (verdadero) no le gusta modificar los valores finales. –

+0

Puedes intentar configurar el sistema. setSecurityManager (null) y vea si puede deshabilitarlo temporalmente;;) –

-1

Aquí, su problema es la antigua Inyección de Dependencia (también conocida como Inversión de Control). Su objetivo debe ser inyectar su implementación de SomeClass en lugar de monopatching.Y sí, este enfoque requiere algunos cambios en su diseño existente pero por las razones correctas (nombre aquí su principio de diseño favorito), especialmente el mismo objeto no debería ser responsable de crear y usar otros objetos.

Asumo la forma en que está usando SomeClass se ve algo como esto:

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

    // some more stuff here... 
    } 
} 

su lugar, lo que debe hacer primero es crear la clase que implementa la misma interfaz o se extiende SomeClass y luego pasar a esa instancia doEverything() por lo que su clase se vuelve agnóstica a la implementación de SomeClass. En este caso, el código que llama al doEverything es responsable de pasar la implementación correcta, ya sea el SomeClass real o el MySomeClass monopatchado.

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

Gracias, pero como se menciona en la pregunta, esto no funcionará porque no estoy llamando a SomeClass directamente. Quiero cambiar la instancia de SomeClass utilizada por una biblioteca externa; nunca la llamo directamente desde mi código. –

0

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

este código imprime; "cambiar algo!" puede reemplazar fácilmente sus instancias singleton. Mis 0.02 $ centavos.

Cuestiones relacionadas