Introducción a cglib

1. Información general

En este artículo, veremos la biblioteca cglib (biblioteca de generación de código). Es una biblioteca de instrumentación de bytes que se utiliza en muchos marcos de Java, como Hibernate o Spring . La instrumentación de bytecode permite manipular o crear clases después de la fase de compilación de un programa.

2. Dependencia de Maven

Para usar cglib en su proyecto, simplemente agregue una dependencia de Maven (la última versión se puede encontrar aquí):

 cglib cglib 3.2.4 

3. Cglib

Las clases en Java se cargan dinámicamente en tiempo de ejecución. Cglib está utilizando esta característica del lenguaje Java para hacer posible agregar nuevas clases a un programa Java que ya se está ejecutando.

Hibernate usa cglib para la generación de proxies dinámicos. Por ejemplo, no devolverá el objeto completo almacenado en una base de datos, pero devolverá una versión instrumentada de la clase almacenada que carga lentamente los valores de la base de datos bajo demanda.

Los marcos de burla populares, como Mockito, usan cglib para los métodos de burla. El simulacro es una clase instrumentada donde los métodos se reemplazan por implementaciones vacías.

Examinaremos las construcciones más útiles de cglib.

4. Implementación de proxy con cglib

Digamos que tenemos una clase PersonService que tiene dos métodos:

public class PersonService { public String sayHello(String name) { return "Hello " + name; } public Integer lengthOfName(String name) { return name.length(); } }

Observe que el primer método devuelve String y el segundo Integer.

4.1. Devolviendo el mismo valor

Queremos crear una clase de proxy simple que interceptará una llamada a un método sayHello () . El Enhancer clase nos permite crear un proxy extendiendo dinámicamente un PersonService clase utilizando un setSuperclass () método de la Enhancer clase:

Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PersonService.class); enhancer.setCallback((FixedValue) () -> "Hello Tom!"); PersonService proxy = (PersonService) enhancer.create(); String res = proxy.sayHello(null); assertEquals("Hello Tom!", res);

El FixedValue es una interfaz de devolución de llamada que simplemente devuelve el valor del método de proxy. La ejecución del método sayHello () en un proxy devolvió un valor especificado en un método de proxy.

4.2. Devolución de valor según la firma del método

La primera versión de nuestro proxy tiene algunos inconvenientes porque no podemos decidir qué método debe interceptar un proxy y qué método debe invocarse desde una superclase. Podemos usar una interfaz MethodInterceptor para interceptar todas las llamadas al proxy y decidir si queremos hacer una llamada específica o ejecutar un método de una superclase:

Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PersonService.class); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) { return "Hello Tom!"; } else { return proxy.invokeSuper(obj, args); } }); PersonService proxy = (PersonService) enhancer.create(); assertEquals("Hello Tom!", proxy.sayHello(null)); int lengthOfName = proxy.lengthOfName("Mary"); assertEquals(4, lengthOfName);

En este ejemplo, estamos interceptando todas las llamadas cuando la firma del método no es de la clase Object , lo que significa que, es decir, los métodos toString () o hashCode () no serán interceptados. Además de eso, estamos interceptando solo métodos de un PersonService que devuelve un String . La llamada a un método lengthOfName () no será interceptada porque su tipo de retorno es un Integer.

5. Bean Creator

Otra construcción útil de cglib es una clase BeanGenerator . Nos permite crear beans dinámicamente y agregar campos junto con los métodos setter y getter. Puede ser utilizado por herramientas de generación de código para generar objetos POJO simples:

BeanGenerator beanGenerator = new BeanGenerator(); beanGenerator.addProperty("name", String.class); Object myBean = beanGenerator.create(); Method setter = myBean.getClass().getMethod("setName", String.class); setter.invoke(myBean, "some string value set by a cglib"); Method getter = myBean.getClass().getMethod("getName"); assertEquals("some string value set by a cglib", getter.invoke(myBean));

6. Creando Mixin

Un mixin es una construcción que permite combinar varios objetos en uno. Podemos incluir un comportamiento de un par de clases y exponer ese comportamiento como una sola clase o interfaz. Los cglib Mixins permiten la combinación de varios objetos en un solo objeto. Sin embargo, para hacerlo, todos los objetos que se incluyen dentro de un mixin deben estar respaldados por interfaces.

Digamos que queremos crear una combinación de dos interfaces. Necesitamos definir ambas interfaces y sus implementaciones:

public interface Interface1 { String first(); } public interface Interface2 { String second(); } public class Class1 implements Interface1 { @Override public String first() { return "first behaviour"; } } public class Class2 implements Interface2 { @Override public String second() { return "second behaviour"; } } 

Para componer implementaciones de Interface1 e Interface2 , necesitamos crear una interfaz que amplíe ambas:

public interface MixinInterface extends Interface1, Interface2 { }

Al usar un método create () de la clase Mixin , podemos incluir comportamientos de Class1 y Class2 en una MixinInterface:

Mixin mixin = Mixin.create( new Class[]{ Interface1.class, Interface2.class, MixinInterface.class }, new Object[]{ new Class1(), new Class2() } ); MixinInterface mixinDelegate = (MixinInterface) mixin; assertEquals("first behaviour", mixinDelegate.first()); assertEquals("second behaviour", mixinDelegate.second());

Los métodos de llamada en mixinDelegate invocarán implementaciones de Class1 y Class2.

7. Conclusión

En este artículo, analizamos el cglib y sus construcciones más útiles. Creamos un proxy usando una clase Enhancer . Usamos un BeanCreator y finalmente creamos un Mixin que incluía comportamientos de otras clases.

Cglib se usa ampliamente en Spring Framework. Un ejemplo del uso de un proxy cglib de Spring es agregar restricciones de seguridad a las llamadas a métodos. En lugar de llamar a un método directamente, Spring Security primero verificará (a través de un proxy) si pasa una verificación de seguridad especificada y delegará al método real solo si esta verificación fue exitosa. En este artículo, vimos cómo crear dicho proxy para nuestro propio propósito.

La implementación de todos estos ejemplos y fragmentos de código se puede encontrar en el proyecto GitHub; este es un proyecto de Maven, por lo que debería ser fácil de importar y ejecutar tal como está.