Byte Buddy es una biblioteca de generación y manipulación de código para crear y modificar las clases de Java durante el tiempo de ejecución de una aplicación Java y sin la ayuda de un compilador. Además de las utilidades de generación de códigos que se envían con la Biblioteca Java Class, Byte Buddy permite la creación de clases arbitrarias y no se limita a implementar interfaces para la creación de proxies de tiempo de ejecución. Además, Byte Buddy ofrece una API conveniente para cambiar las clases manualmente, usando un agente Java o durante una construcción.
Para usar Byte Buddy, uno no requiere una comprensión del código de byte Java o el formato de archivo de clase. Por el contrario, la API de Byte Buddy apunta al código que es conciso y fácil de entender para todos. Sin embargo, Byte Buddy sigue siendo totalmente personalizable hasta la posibilidad de definir el código de byte personalizado. Además, la API fue diseñada para ser lo más no intrusiva posible y, como resultado, Byte Buddy no deja ningún rastro en las clases creadas por él. Por esta razón, las clases generadas pueden existir sin requerir un amigo de byte en la ruta de clase. Debido a esta característica, la mascota de Byte Buddy fue elegida para ser un fantasma.
Byte Buddy está escrito en Java 5, pero admite la generación de clases para cualquier versión de Java. Byte Buddy es una biblioteca de peso ligero y solo depende de la API de los visitantes de la biblioteca Parser del código de Byte Java ASM que no requiere más dependencias.
A primera vista, la generación de código de ejecución puede parecer una especie de magia negra que debe evitarse y solo pocos desarrolladores escriben aplicaciones que generan explícitamente código durante su tiempo de ejecución. Sin embargo, esta imagen cambia al crear bibliotecas que necesitan interactuar con el código y los tipos arbitrarios que se desconocen en el momento de la compilación. En este contexto, un implementador de la biblioteca a menudo debe elegir entre exigir que un usuario implemente interfaces propietarias de la biblioteca o que genere código en tiempo de ejecución cuando los tipos del usuario se conocen por primera vez por la biblioteca. Muchas bibliotecas conocidas, como, por ejemplo, Spring o Hibernate, eligen el último enfoque que es popular entre sus usuarios bajo el término de usar objetos Java simples . Como resultado, la generación de código se ha convertido en un concepto ubicuo en el espacio Java. Byte Buddy es un intento de innovar la creación de tiempo de ejecución de los tipos de Java para proporcionar una mejor herramienta establecida para quienes dependen de la generación de código.
En octubre de 2015, Byte Buddy se distinguió con un Premio Duke's Choice de Oracle. El premio aprecia a Byte Buddy por su " tremenda cantidad de innovación en la tecnología Java ". Nos sentimos muy honrados por haber recibido este premio y queremos agradecer a todos los usuarios y a todos los que ayudaron a hacer de Byte Buddy el éxito en el que se ha convertido. ¡Realmente lo apreciamos!
Byte Buddy ofrece un excelente rendimiento en la calidad de producción. Es estable y en uso por marcos y herramientas distinguidas como Mockito, Hibernate, Jackson, el sistema de compilación de bazel de Google y muchos otros. Byte Buddy también es utilizado por una gran cantidad de productos comerciales para un gran resultado. Actualmente se descarga más de 75 millones de veces al año.
Détete hola mundo con byte buddy es lo más fácil posible. Cualquier creación de una clase Java comienza con una instancia de la clase ByteBuddy
que representa una configuración para crear nuevos tipos:
Class <?> dynamicType = new ByteBuddy ()
. subclass ( Object . class )
. method ( ElementMatchers . named ( "toString" ))
. intercept ( FixedValue . value ( "Hello World!" ))
. make ()
. load ( getClass (). getClassLoader ())
. getLoaded ();
assertThat ( dynamicType . newInstance (). toString (), is ( "Hello World!" ));
La configuración de ByteBuddy
predeterminada que se utiliza en el ejemplo anterior crea una clase Java en la versión más reciente del formato de archivo de clase que entiende la máquina virtual Java de procesamiento. Como es de esperar, obvio del código de ejemplo, el tipo creado ampliará la clase Object
y anula su método toString
que debería devolver un valor fijo de Hello World!
. El método a anular se identifica por un llamado ElementMatcher
. En el ejemplo anterior, se utiliza un elemento predefinido named(String)
que identifica los métodos por sus nombres exactos. Byte Buddy viene con numerosos matchers predefinidos y bien probados que se recolectan en la clase ElementMatchers
y que se pueden componer fácilmente. Sin embargo, la creación de Matchers personalizados es tan simple como implementar la interfaz (funcional) ElementMatcher
.
Para implementar el método toString
, la clase FixedValue
define un valor de retorno constante para el método anulado. Definir un valor constante es solo un ejemplo de muchos interceptores de métodos que se envían con Byte Buddy. Al implementar la interfaz Implementation
, un método podría incluso definirse mediante el código de byte personalizado.
Finalmente, la clase Java descrita se crea y luego se carga en la máquina virtual Java. Para este propósito, se requiere un cargador de clase de destino. Finalmente, podemos convencernos del resultado llamando al método toString
en una instancia de la clase creada y encontrando el valor de retorno para representar el valor constante que esperábamos.
Por supuesto, un ejemplo de Hello World es un caso de uso demasiado simple para evaluar la calidad de una biblioteca de generación de código. En realidad, un usuario de dicha biblioteca quiere realizar manipulaciones más complejas, por ejemplo, introduciendo ganchos en la ruta de ejecución de un programa Java. Usar Byte Buddy, sin embargo, hacerlo es igualmente simple. El siguiente ejemplo da un sabor de cómo se pueden interceptar las llamadas de método.
Byte Buddy expresa implementaciones de métodos definidos dinámicamente por instancias de la interfaz Implementation
. En el ejemplo anterior, FixedValue
que implementa esta interfaz ya se demostró. Al implementar esta interfaz, un usuario de Byte Buddy puede ir a la duración de la definición del código de byte personalizado para un método. Normalmente, es más fácil usar las implementaciones predefinidas de Byte Buddy, como MethodDelegation
, lo que permite implementar cualquier método en Java simple. El uso de esta implementación es sencillo, ya que funciona delegando el flujo de control a cualquier POJO. Como ejemplo de tal pojo, Byte Buddy puede, por ejemplo, redirigir una llamada al único método de la siguiente clase:
public class GreetingInterceptor {
public Object greet ( Object argument ) {
return "Hello from " + argument ;
}
}
Tenga en cuenta que el GreetingInterceptor
anterior no depende de ningún tipo de compañero de byte. ¡Esta es una buena noticia porque ninguna de las clases que genera Byte Buddy requiere Byte Buddy en el camino de la clase! Dado el GreetingInterceptor
anteriores, podemos usar Byte Buddy para implementar el Java 8 java.util.function.Function
interfaz y su método apply
abstracta:
Class <? extends java . util . function . Function > dynamicType = new ByteBuddy ()
. subclass ( java . util . function . Function . class )
. method ( ElementMatchers . named ( "apply" ))
. intercept ( MethodDelegation . to ( new GreetingInterceptor ()))
. make ()
. load ( getClass (). getClassLoader ())
. getLoaded ();
assertThat (( String ) dynamicType . newInstance (). apply ( "Byte Buddy" ), is ( "Hello from Byte Buddy" ));
Ejecutando el código anterior, Byte Buddy implementa la interfaz Function
de Java e implementa el método apply
como una delegación a una instancia del POJO GreetingInterceptor
que definimos antes. Ahora, cada vez que se llama a la Function::apply
el método, el flujo de control se envía a GreetingInterceptor::greet
y el valor de retorno del último método se devuelve del método de la interfaz.
Los interceptores pueden definirse para tomar con entradas y salidas más genéricas anotando los parámetros del interceptor. Cuando Byte Buddy descubre una anotación, la biblioteca inyecta la dependencia que requiere el parámetro del interceptor. Un ejemplo para un interceptor más general es la siguiente clase:
public class GeneralInterceptor {
@ RuntimeType
public Object intercept ( @ AllArguments Object [] allArguments ,
@ Origin Method method ) {
// intercept any method of any signature
}
}
Con el interceptor anterior, cualquier método interceptado podría coincidir y procesarse. Por ejemplo, cuando coinciden Function::apply
, los argumentos del método se pasarían como el elemento único de una matriz. Además, una referencia Method
a Fuction::apply
se aprobaría como el segundo argumento del interceptor debido a la anotación @Origin
. Al declarar la anotación @RuntimeType
en el método, Byte Buddy finalmente lanza el valor devuelto al valor de retorno del método interceptado si esto es necesario. Al hacerlo, Byte Buddy también aplica boxeo automático y unboxing.
Además de las anotaciones que ya se mencionaron, existen muchas otras anotaciones predefinidas. Por ejemplo, cuando se usa la anotación @SuperCall
en un tipo Runnable
o Callable
, Byte Buddy inyecta instancias proxy que permiten una invocación de un súper método no abstracto si existe dicho método. E incluso si Byte Buddy no cubre un caso de uso, Byte Buddy ofrece un mecanismo de extensión para definir anotaciones personalizadas.
Es posible que el uso de estas anotaciones vincule su código con Byte Buddy. Sin embargo, Java ignora las anotaciones en caso de que no sean visibles para un cargador de clase. ¡De esta manera, el código generado todavía puede existir sin Byte Buddy! Puede encontrar más información sobre el MethodDelegation
y sobre todas sus anotaciones predefinidas en su Javadoc y en el tutorial de Byte Buddy.
Byte Buddy no se limita a la creación de subclases, pero también es capaz de redefinir el código existente. Para hacerlo, Byte Buddy ofrece una API conveniente para definir los llamados agentes de Java. Los agentes de Java son programas Java simples que pueden usarse para alterar el código de una aplicación Java existente durante su tiempo de ejecución. Como ejemplo, podemos usar Byte Buddy para cambiar los métodos para imprimir su tiempo de ejecución. Para esto, primero definimos un interceptor similar a los interceptores en los ejemplos anteriores:
public class TimingInterceptor {
@ RuntimeType
public static Object intercept ( @ Origin Method method ,
@ SuperCall Callable <?> callable ) {
long start = System . currentTimeMillis ();
try {
return callable . call ();
} finally {
System . out . println ( method + " took " + ( System . currentTimeMillis () - start ));
}
}
}
Usando un agente Java, ahora podemos aplicar este interceptor a todos los tipos que coinciden con un ElementMatcher
para una TypeDescription
. Para el ejemplo, elegimos agregar el interceptor anterior a todos los tipos con un nombre que termina en Timed
. Esto se hace en aras de la simplicidad, mientras que una anotación probablemente sería una alternativa más apropiada para marcar tales clases para un agente de producción. Utilizando la API AgentBuilder
de Byte Buddy, crear un agente Java es tan fácil como definir la siguiente clase de agente:
public class TimerAgent {
public static void premain ( String arguments ,
Instrumentation instrumentation ) {
new AgentBuilder . Default ()
. type ( ElementMatchers . nameEndsWith ( "Timed" ))
. transform (( builder , type , classLoader , module , protectionDomain ) ->
builder . method ( ElementMatchers . any ())
. intercept ( MethodDelegation . to ( TimingInterceptor . class ))
). installOn ( instrumentation );
}
}
Similar al método main
de Java, el método premain
es el punto de entrada a cualquier agente de Java desde el cual aplicamos la redefinición. Como un argumento, un agente de Java recibe una instancia de la interfaz Instrumentation
que permite a Byte Buddy conectarse a la API estándar de JVM para la redefinición de la clase de tiempo de ejecución.
Este programa está empaquetado junto con un archivo manifiesto con el atributo Premain-Class
que apunta al TimerAgent
. El archivo JAR resultante ahora se puede agregar a cualquier aplicación Java configurando -javaagent:timingagent.jar
similar a agregar un jar a la ruta de clase. Con el agente activo, todas las clases que terminan en Timed
ahora imprimen su tiempo de ejecución en la consola.
Byte Buddy también es capaz de aplicar los llamados archivos adjuntos de tiempo de ejecución al deshabilitar los cambios de formato de archivo de clase y usar la instrumentación Advice
. Consulte el Javadoc de los Advice
y la clase AgentBuilder
para obtener más información. Byte Buddy también ofrece el cambio explícito de las clases de Java a través de una instancia ByteBuddy
o mediante el uso de los complementos de Byte Buddy Maven y Gradle .
Byte Buddy es una biblioteca integral y solo rascamos la superficie de las capacidades de Byte Buddy. Sin embargo, Byte Buddy tiene como objetivo ser fácil de usar al proporcionar un lenguaje específico de dominio para crear clases. La mayoría de la generación de código de ejecución se puede hacer escribiendo código legible y sin ningún conocimiento del formato de archivo de clase de Java. Si desea obtener más información sobre Byte Buddy, puede encontrar tal tutorial en la página web de Byte Buddy (también hay una traducción china disponible).
Además, Byte Buddy viene con una documentación detallada en el código y una amplia cobertura de casos de prueba que también puede servir como código de ejemplo. Finalmente, puede encontrar una lista actualizada de artículos y presentaciones sobre Byte Buddy en el wiki. Cuando use Byte Buddy, asegúrese de leer la siguiente información sobre cómo mantener una dependencia del proyecto.
El uso de Byte Buddy es gratuito y no requiere la compra de una licencia. Para aprovechar al máximo la biblioteca o asegurar un comienzo fácil, es posible comprar capacitación, horas de desarrollo o planes de soporte. Las tarifas dependen del alcance y la duración de un compromiso. Póngase en contacto con [email protected] para obtener más información.
Byte Buddy aparece en TidElift. Si no está utilizando Byte Buddy hasta cierto punto en el que desea comprar un soporte explícito y desea apoyar a la comunidad de código abierto en general, considere una suscripción.
Puede apoyar mi trabajo a través de patrocinadores de GitHub. Tenga en cuenta que esta opción solo está destinada a actores comerciales que buscan un canal de pago simple y que no esperan soporte a cambio. El apoyo a través de los patrocinadores de GitHub no es posible mantener el cumplimiento del IVA. Comuníquese con un acuerdo de soporte directo.
Se pueden hacer preguntas generales en el desbordamiento de pila o en la lista de correo de Byte Buddy que también sirve como un archivo para las preguntas. Por supuesto, los informes de errores se considerarán también fuera de un plan comercial. Para proyectos de código abierto, a veces es posible recibir ayuda prolongada para llevar a Byte Buddy a usar.
Byte Buddy está escrito sobre ASM, una biblioteca madura y bien probada para leer y escribir clases de Java compiladas. Para permitir manipulaciones de tipo avanzado, Byte Buddy está exponiendo intencionalmente la ASM API a sus usuarios. Por supuesto, el uso directo de ASM sigue siendo completamente opcional y la mayoría de los usuarios probablemente nunca lo requerirán. Esta elección se tomó de tal manera que un usuario de Byte Buddy no está restringido a su funcionalidad de nivel superior, sino que puede implementar implementaciones personalizadas sin un alboroto cuando es necesario.
ASM ha cambiado previamente su API pública, pero agregó un mecanismo para la compatibilidad de la API que comienza con la versión 4 de la biblioteca. Para evitar conflictos de versión con versiones tan anteriores, Byte Buddy reempaza la dependencia de ASM en su propio espacio de nombres. Si desea usar ASM directamente, el artefacto byte-buddy-dep
ofrece una versión de Byte Buddy con una dependencia explícita para ASM. Al hacerlo, debe volver a empaquetar tanto Byte Buddy como ASM en su espacio de nombres para evitar conflictos de versión.
Tenga en cuenta la política de seguridad de este proyecto.
Byte Buddy admite la ejecución en todas las versiones JVM desde la versión cinco y en un solo frasco. Esto se hace para aliviar el desarrollo de agentes Java que a menudo requieren apoyar aplicaciones mayores o desconocidas que no se actualizan activamente. Para permitir esto, al mismo tiempo que admite Java moderno y características como CDS o validación de clase con marcos de mapas de pila, los frascos principales para Byte Buddy Ship como frascos de liberación múltiple que contienen archivos de clase en la versión cinco y ocho. Como resultado, el tamaño del frasco de Byte Buddy es más alto como cabría esperar. El tamaño del archivo JAR normalmente no es un problema, ya que la mayoría de las clases de Byte Buddy nunca se cargarán. Sin embargo, el tamaño del archivo puede ser un problema al distribuir agentes Java. Como los agentes ya necesitan ser agrupados como un solo frasco, por lo tanto, se recomienda eliminar la versión básica de Java Five, o la versión Java Ocho de liberación múltiple de los archivos de clase contenidos, para reducir este problema. Esto es compatible con la mayoría de los complementos de compilación para este propósito, como el complemento Maven Shade.
Byte Buddy tiene licencia bajo la licencia de Apache liberal y amigable para los negocios, versión 2.0 y está disponible gratuitamente en GitHub. Además, el Byte-Buddy Distribution Bundle ASM que se lanza bajo una licencia BSD de 3 cláusula.
Los binarios de bytes se publican a los repositorios de Maven Central y en JCenter. Las firmas de artefactos se pueden validar con esta clave pública PGP que comienza con Byte Buddy 1.10.3. Las versiones anteriores pueden validarse contra este certificado más antiguo y más débil.
El proyecto se construye usando Maven. Desde su caparazón, la clonación y la construcción del proyecto serían algo como esto:
git clone https://github.com/raphw/byte-buddy.git
cd byte-buddy
mvn package
En estos comandos, Byte Buddy está clonado de Github y construido en su máquina. Opciones de compilación adicionales se enumeran en el archivo POM root. Byte Buddy se puede construir con cualquier JDK de al menos la versión 6. Sin embargo, se recomienda utilizar un JDK de al menos la versión 8, ya que las compilaciones para la versión 6 y 7 requieren el uso de HTTP sin cifrar. Su soporte solo está destinado a ejecutar pruebas contra esta versión JDK y puede exponerlo a ataques de hombre en el medio. Por lo tanto, estas construcciones deben evitarse. Byte Buddy se prueba actualmente para las versiones 6 y más de los servidores JDK en CI.
Utilice el rastreador de problemas de GitHub para informar errores. Al cometer un código, proporcione casos de prueba que prueben la funcionalidad de sus características o que demuestren una solución de errores. Además, asegúrese de no romper ningún caso de prueba existente. Si es posible, tómese el tiempo para escribir alguna documentación. Para solicitudes de funciones o comentarios generales, también puede usar el rastreador de problemas o contactarnos en nuestra lista de correo.
El trabajo en Byte Buddy también es posible gracias a una fila de seguidores que tienen recursos regulares y atención dedicados al proyecto. Tómese su tiempo para echar un vistazo a esos seguidores y sus ofertas.