java reflection monkeypatching

¿Es posible parche de mono en Java?



reflection monkeypatching (10)

Puedes intentar usar herramientas como PowerMock / Mockito. Si puede simular pruebas, también puede simular la producción.

Sin embargo, estas herramientas no están diseñadas para ser utilizadas de esa manera, por lo que tendrá que preparar el entorno usted mismo y no podrá usar los corredores JUnit como lo hace en las pruebas ...

No quiero discutir los méritos de este enfoque, solo si es posible. Creo que la respuesta es "no". ¡Pero tal vez alguien me sorprenda!

Imagine que tiene una clase de widget central. Tiene un método calculateHeight() , que devuelve una altura. La altura es demasiado grande; esto da como resultado botones (por ejemplo) demasiado grandes. Puede extender DefaultWidget para crear su propio NiceWidget e implementar su propio calculateHeight() para devolver un tamaño más agradable.

Ahora una clase de biblioteca WindowDisplayFactory, instancia el DefaultWidget en un método bastante complejo. Le gustaría usar su NiceWidget. El método de la clase de fábrica se ve así:

public IWidget createView(Component parent) { DefaultWidget widget = new DefaultWidget(CONSTS.BLUE, CONSTS.SIZE_STUPIDLY); // bunch of ifs ... SomeOtherWidget bla = new SomeOtherWidget(widget); SomeResultWidget result = new SomeResultWidget(parent); SomeListener listener = new SomeListener(parent, widget, flags); // more widget creation and voodoo here return result; }

Ese es el trato. El resultado tiene el widget predeterminado en una jerarquía de otros objetos. La pregunta: ¿cómo obtener este método de fábrica para usar mi propio NiceWidget? O al menos obtener mi propio calculateHeight() allí. Idealmente, me gustaría poder parchear el parche DefaultWidget para que su cálculoHeight hizo lo correcto ...

public class MyWindowDisplayFactory { public IWidget createView(Component parent) { DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight); return super.createView(parent); } }

Que es lo que podría hacer en Python, Ruby, etc. setMethod() embargo, he inventado el nombre setMethod() . Las otras opciones abiertas para mí son:

  • Copiar y pegar el código del método createView() en mi propia clase que hereda de la clase de fábrica
  • Vivir con widgets que son demasiado grandes

La clase de fábrica no se puede cambiar: es parte de una plataforma API central. Intenté reflexionar sobre el resultado obtenido para acceder al widget que (finalmente) se agregó, pero se trata de varias capas de widgets y en algún lugar se usa para inicializar otras cosas, lo que causa extraños efectos secundarios.

¿Algunas ideas? Mi solución hasta ahora es el trabajo de copiar y pegar, pero eso es una salida de policía que requiere el seguimiento de los cambios en la clase principal de fábrica cuando se actualiza a versiones más nuevas de la plataforma, y ​​me gustaría escuchar otras opciones.


¿Quizás podría usar la Programación Orientada a Aspectos para atrapar llamadas a esa función y devolver su propia versión?

Spring ofrece algunas funcionalidades AOP, pero hay otras bibliotecas que también lo hacen.


Bueno, sigo tratando de publicar sugerencias, y luego veo que no funcionarán o que ya has mencionado que las has probado.

La mejor solución que puedo pensar es subclase WindowDisplayFactory, luego en el método createView () de la subclase, primero llame a super.createView (), luego modifique el objeto devuelto para descartar completamente el widget y reemplácelo con una instancia de la subclase que hace lo que quieres Pero el widget se usa para inicializar cosas, por lo que tendrías que cambiarlas todas.

Luego pienso en usar la reflexión sobre el objeto devuelto desde createView () y tratando de arreglar las cosas de esa manera, pero de nuevo, eso es peludo porque se inicializaron muchas cosas con el widget. Creo que trataría de usar ese enfoque, si fuera lo suficientemente simple como para justificarlo sobre copiar y pegar.

Voy a estar viendo esto, además de pensar si puedo encontrar otras ideas. La Reflexión de Java es agradable, pero no puede superar la introspección dinámica que he visto disponible en idiomas como Perl y Python.


La forma orientada a objetos de hacer esto sería crear un contenedor implementando IWidget, delegando todas las llamadas al widget real, excepto calculateHeight, algo como:

class MyWidget implements IWidget { private IWidget delegate; public MyWidget(IWidget d) { this.delegate = d; } public int calculateHeight() { // my implementation of calculate height } // for all other methods: { public Object foo(Object bar) { return delegate.foo(bar); } }

Para que esto funcione, debe interceptar todas las creaciones del widget que desea reemplazar, lo que probablemente signifique crear un contenedor similar para WidgetFactory. Y debe poder configurar qué WidgetFactory usar.

También depende de que ningún cliente intente devolver el IWidget a DefaultWidget ...


Solo mi idea conceptual,

Es posible que use AOP, con forma de ingeniería bytecode, para inyectar un aspecto al método calculateHeight.

Entonces, puedes habilitar el parche por ThreadLocal o de otra manera variable.


Solo sugerencias en las que puedo pensar:

  1. Explore la API de la biblioteca para ver si hay alguna forma de anular los valores predeterminados y el tamaño. El tamaño puede ser confuso en el swing (al menos para mí), setMinimum, setMaximum, setdefault, setDefaultOnThursday, .... Es posible que haya una manera. Si puede ponerse en contacto con el / los diseñador (es) de la biblioteca, puede encontrar una respuesta que alivie la necesidad de piratería desagradable.

  2. Tal vez extienda la fábrica solo anulando algunos parámetros de tamaño predeterminados? depende de la fábrica, pero podría ser posible.

Crear una clase con el mismo nombre podría ser la única otra opción, ya que otros han señalado que es feo y es probable que se olvide y rompa cosas cuando actualice la biblioteca de API o implemente en un entorno diferente y olvide por qué tenía el classpath configurado de esa manera.


Una solución fea sería poner su propia implementación de DefaultWidget (con el mismo FQCN) anteriormente en el Classpath que la implementación normal. Es un hack terrible, pero cada otro enfoque que puedo pensar es aún peor.


cglib es una biblioteca de Java que puede hacer algunas cosas similares a la parche de mono: puede manipular bytecode en tiempo de ejecución para cambiar ciertos comportamientos. No estoy seguro de si puede hacer exactamente lo que necesita, pero vale la pena verlo ...