¿Hay alguna manera de modificar el valor de un campo `final` estática privada en Java desde fuera de la clase?

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

  •  12-09-2019
  •  | 
  •  

Pregunta

Sé que esto es normalmente bastante estúpida, pero no me dispares antes de leer la pregunta. Prometo que tengo una buena razón para necesitar hacer esto:)

Es posible modificar campos privados regulares en Java utilizando la reflexión, sin embargo Java lanza una excepción de seguridad cuando se trata de hacer lo mismo para los campos final.

Me asumir esto se aplica estrictamente, pero pensé que le pediría de todos modos sólo en caso de que alguien había descubierto un truco para hacer esto.

Vamos a decir que tengo una biblioteca externa con un "SomeClass" clase

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

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

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

Yo quiero esencialmente mono-Patch SomeClass para que pueda ejecutar mi propia versión de doSomething(). Dado que no hay (que yo sepa) alguna manera de hacer realmente que en Java, mi única solución en este caso es el de alterar el valor del INSTANCE de modo que devuelve mi versión de la clase con el método modificado.

En esencia, sólo quiero envolver la llamada con un control de seguridad y luego llamar al método original.

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

EDIT: Solo para aclarar, getInstance() es llamado por la biblioteca externa, no es mi código, por lo que acaba de subclases no va a resolver el problema.

Si no puedo hacer que la única otra solución que se me ocurre es copiar y pegar clase entera y modificar el método. Esto no es ideal, ya que voy a tener que mantener mi tenedor al día con cambios en la biblioteca. Si alguien tiene algo un poco más fácil de mantener estoy abierto a sugerencias.

¿Fue útil?

Solución

Es posible. He utilizado este a monkeypatch threadlocals traviesas que impedían la descarga de clases en aplicaciones web. Sólo tiene que utilizar la reflexión para eliminar el modificador final, entonces se 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);
}

No es un poco 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 ....

Otros consejos

Cualquier marco AOP se ajuste a sus necesidades

Esto permitiría 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 el marco ASM internamente para hacer lo mismo.

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

Usted debe ser capaz de cambiar con JNI ... no estoy seguro si eso es una opción para usted.

EDIT:. Es posible, pero no es una buena idea

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

  

10.9 Violar las reglas de control de acceso

     

El JNI no hace cumplir la clase, campo,   y restricciones de control de acceso método   que se puede expresar en el Java   nivel de lenguaje de programación a través de la   uso de modificadores como privado y   final. Es posible escribir nativa   código para acceder o modificar campos de una   oponerse a pesar de hacerlo en el   nivel de lenguaje de programación Java haría   conducir a una IllegalAccessException.   la permisividad de JNI era un consciente   decisión de diseño, dado que la nativa   código puede acceder y modificar cualquier memoria   ubicación en el montón de todos modos.

     

código nativo que no pasa por   -comprobaciones de acceso de nivel de idioma de origen   puede tener efectos no deseados en   la ejecución del programa. Por ejemplo, una   inconsistencia se puede crear si un   método nativo modifica un campo final   después de un compilador Just-In-Time (JIT)   ha inline accesos al campo.   Del mismo modo, los métodos nativos no debería   modificar los objetos inmutables como   campos en los casos de   java.lang.String o java.lang.Integer.   Si lo hace, puede conducir a la rotura de   invariantes en la plataforma Java   aplicación.

Si realmente tiene que (aunque para nuestro problema le sugeriría utilizar la solución de CaptainAwesomePants) que podría echar un vistazo a JMockIt . Aunque esto esta destinada a ser utilizada en las pruebas de unidad si le permite redefinir los métodos arbitrarios. Esto se hace mediante la modificación del código de bytes en tiempo de ejecución.

Yo le Prefacio esta respuesta por reconocer que esto no es en realidad una respuesta a su pregunta declarado acerca de cómo modificar un campo static final privado. Sin embargo, en el código de ejemplo específico mencionado anteriormente, puedo, de hecho, hacerlo de modo que se puede reemplazar doSomething (). Lo que puede hacer es tomar ventaja del hecho de que getInstance () es un método y subclase público:

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 sólo funciona si usted es el que invocando getInstance () y no alguna otra parte de la materia no se puede modificar el que está trabajando.

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 "algo cambió!"; se puede reemplazar fácilmente casos únicos. Mis 0,02 $ centavos.

Si no hay truco externa disponible (al menos yo no conozco) me hubiera cortado la propia clase. Cambiar el código mediante la adición de la comprobación de seguridad que desea. Como tal, su una biblioteca externa, no se va a tomar las actualizaciones con regularidad, tampoco muchos de actualización ocurre de todos modos. Cada vez que esto sucede felizmente puedo volver a hacerlo, ya que no es una gran tarea de todos modos.

Aquí, el problema es buena de edad inyección de dependencia (también conocido como Inversión de Control). Su objetivo debe ser para inyectar su implementación de SomeClass en lugar de monkeypatching ella. Y sí, este enfoque requiere algunos cambios en su diseño ya existente, pero por las razones correctas (el nombre de su principio de diseño favorito aquí) - en especial el mismo objeto no debe ser responsable tanto de la creación y el uso de 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...
  }
}

En su lugar, lo que debe hacer primero es crear su clase que implementa la misma interfaz o se extiende SomeClass y luego pasar a esa instancia doEverything() por lo que su clase se convierte en agnóstico a la aplicación de SomeClass. En este caso el código que llama doEverything es responsable de pasar en la ejecución correcta - ya sea real o el SomeClass su 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...
  }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top