Byte Buddy est une bibliothèque de génération de code et de manipulation pour créer et modifier les classes Java pendant l'exécution d'une application Java et sans l'aide d'un compilateur. Outre les utilitaires de génération de code qui expédient avec la bibliothèque de classe Java, Byte Buddy permet la création de classes arbitraires et ne se limite pas à la mise en œuvre d'interfaces pour la création de procurations d'exécution. De plus, Byte Buddy propose une API pratique pour changer les classes manuellement, en utilisant un agent Java ou pendant une construction.
Afin d'utiliser Byte Buddy, on ne nécessite pas de compréhension du code d'octet Java ou du format de fichier de classe. En revanche, l'API de Byte Buddy vise un code concis et facile à comprendre pour tout le monde. Néanmoins, Byte Buddy reste entièrement personnalisable jusqu'à la possibilité de définir le code octet personnalisé. De plus, l'API a été conçue pour être aussi non intrusive que possible et, par conséquent, Byte Buddy ne laisse aucune trace dans les classes qui ont été créées par elle. Pour cette raison, les classes générées peuvent exister sans exiger un copain d'octets sur le chemin de la classe. En raison de cette fonctionnalité, la mascotte de Byte Buddy a été choisie pour être un fantôme.
Byte Buddy est écrit dans Java 5 mais prend en charge la génération de classes pour toute version Java. Byte Buddy est une bibliothèque de poids légère et ne dépend que de l'API visiteur de la bibliothèque d'analyse du code Java ASM qui ne nécessite pas d'autres dépendances.
À première vue, la génération de code d'exécution peut sembler être une sorte de magie noire qui doit être évitée et seuls quelques développeurs écrivent des applications qui génèrent explicitement du code pendant leur exécution. Cependant, cette image change lors de la création de bibliothèques qui doivent interagir avec le code arbitraire et les types qui sont inconnus au moment de la compilation. Dans ce contexte, un implémentateur de bibliothèque doit souvent choisir entre exiger qu'un utilisateur implémente les interfaces de bibliothèque-propulsion ou de générer du code à l'exécution lorsque les types de l'utilisateur deviennent d'abord connus de la bibliothèque. De nombreuses bibliothèques connues telles que par exemple Spring ou Hibernate choisissent cette dernière approche qui est populaire parmi leurs utilisateurs sous le terme d'utilisation d'anciens objets Java simples . En conséquence, la génération de code est devenue un concept omniprésent dans l'espace Java. Byte Buddy est une tentative d'innover la création d'exécution des types Java afin de fournir un meilleur ensemble d'outils pour ceux qui s'appuient sur la génération de code.
En octobre 2015, Byte Buddy s'est distingué avec un Duke's Choice Award d'Oracle. Le prix apprécie Byte Buddy pour son " énorme quantité d'innovation dans la technologie Java ". Nous nous sentons très honorés d'avoir reçu ce prix et nous tenons à remercier tous les utilisateurs et tous ceux qui ont aidé à faire du Byte Buddy le succès qu'il est devenu. Nous l'apprécions vraiment!
Byte Buddy offre d'excellentes performances à la qualité de la production. Il est stable et utilisé par des frameworks et des outils distingués tels que Mockito, Hibernate, Jackson, le système de construction Bazel de Google et bien d'autres. Byte Buddy est également utilisé par un grand nombre de produits commerciaux pour un excellent résultat. Il est actuellement téléchargé plus de 75 millions de fois par an.
Dire bonjour World avec Byte Buddy est aussi simple que possible. Toute création d'une classe Java commence par une instance de la classe ByteBuddy
qui représente une configuration pour la création de nouveaux types:
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 configuration ByteBuddy
par défaut qui est utilisée dans l'exemple ci-dessus crée une classe Java dans la dernière version du format de fichier de classe qui est comprise par la machine virtuelle Java Processing. Aussi, espérons-le, à partir de l'exemple de code, le type créé prolongera la classe Object
et remplace sa méthode toString
qui devrait renvoyer une valeur fixe de Hello World!
. La méthode à remplacer est identifiée par un soi-disant ElementMatcher
. Dans l'exemple ci-dessus, un correspondant d'élément prédéfini named(String)
est utilisé qui identifie les méthodes par leurs noms exacts. Byte Buddy est livré avec de nombreux matchs prédéfinis et bien testés qui sont collectés dans la classe ElementMatchers
et qui peuvent être facilement composés. La création de matchs personnalisés est cependant aussi simple que d'implémenter l'interface (fonctionnelle) ElementMatcher
.
Pour implémenter la méthode toString
, la classe FixedValue
définit une valeur de retour constante pour la méthode remplacée. La définition d'une valeur constante n'est qu'un exemple de nombreux intercepteurs de méthode qui expédient avec Byte Buddy. En implémentant l'interface Implementation
, une méthode pourrait cependant être définie par code d'octet personnalisé.
Enfin, la classe Java décrite est créée puis chargée dans la machine virtuelle Java. À cette fin, un chargeur de classe cible est requis. Finalement, nous pouvons nous convaincre du résultat en appelant la méthode toString
sur une instance de la classe créée et en trouvant la valeur de retour pour représenter la valeur constante que nous attendions.
Bien sûr, un exemple de Hello World est un cas d'utilisation trop simple pour évaluer la qualité d'une bibliothèque de génération de code. En réalité, un utilisateur d'une telle bibliothèque souhaite effectuer des manipulations plus complexes, par exemple en introduisant des crochets dans le chemin d'exécution d'un programme Java. En utilisant Byte Buddy, le faire est cependant tout aussi simple. L'exemple suivant donne un avant-goût de la façon dont les appels de méthode peuvent être interceptés.
Byte Buddy exprime les implémentations de méthode définies dynamiquement par des instances de l'interface Implementation
. Dans l'exemple précédent, FixedValue
qui implémente cette interface a déjà été démontré. En implémentant cette interface, un utilisateur de Byte Buddy peut aller à la longueur de la définition du code d'octets personnalisé pour une méthode. Normalement, il est cependant plus facile d'utiliser les implémentations prédéfinies de Byte Buddy telles que MethodDelegation
qui permet d'implémenter n'importe quelle méthode en Java simple. L'utilisation de cette implémentation est simple car elle fonctionne en déléguant le flux de contrôle vers n'importe quel POJO. À titre d'exemple d'un tel pojo, Byte Buddy peut par exemple rediriger un appel vers la seule méthode de la classe suivante:
public class GreetingInterceptor {
public Object greet ( Object argument ) {
return "Hello from " + argument ;
}
}
Notez que le GreetingInterceptor
ci-dessus ne dépend de aucun type de copain d'octets. C'est une bonne nouvelle car aucune des classes que Byte Buddy génère ne nécessite Byte Buddy sur le chemin de la classe! Compte tenu de la GreetingInterceptor
ci-dessus, nous pouvons utiliser Byte Buddy pour implémenter l'interface Java 8 java.util.function.Function
et sa méthode apply
abstraite:
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" ));
En exécutant le code ci-dessus, Byte Buddy implémente l'interface Function
de Java et implémente la méthode apply
en tant que délégation à une instance du pojo GreetingInterceptor
que nous avons défini auparavant. Maintenant, chaque fois que la méthode Function::apply
est appelée, le flux de contrôle est expédié à GreetingInterceptor::greet
et la valeur de retour de la dernière méthode est renvoyée de la méthode de l'interface.
Les intercepteurs peuvent être définis pour prendre avec des entrées et des sorties plus génériques en annotant les paramètres de l'intercepteur. Lorsque Byte Buddy découvre une annotation, la bibliothèque injecte la dépendance dont le paramètre intercepteur a besoin. Un exemple pour un intercepteur plus général est la classe suivante:
public class GeneralInterceptor {
@ RuntimeType
public Object intercept ( @ AllArguments Object [] allArguments ,
@ Origin Method method ) {
// intercept any method of any signature
}
}
Avec l'intercepteur ci-dessus, toute méthode interceptée pourrait être appariée et traitée. Par exemple, lors de Function::apply
, les arguments de la méthode seraient passés comme l'élément unique d'un tableau. En outre, une référence Method
à Fuction::apply
serait passé comme le deuxième argument de l'intercepteur en raison de l'annotation @Origin
. En déclarant l'annotation @RuntimeType
sur la méthode, Byte Buddy jette enfin la valeur renvoyée à la valeur de retour de la méthode interceptée si cela est nécessaire. Ce faisant, Byte Buddy applique également la boxe et le déballage automatique.
Outre les annotations déjà mentionnées, il existe de nombreuses autres annotations prédéfinies. Par exemple, lors de l'utilisation de l'annotation @SuperCall
sur un type Runnable
ou Callable
, Byte Buddy injecte des instances de proxy qui permettent une invocation d'une méthode Super non abstraite si une telle méthode existe. Et même si Byte Buddy ne couvre pas un cas d'utilisation, Byte Buddy propose un mécanisme d'extension pour définir des annotations personnalisées.
Vous pourriez vous attendre à ce que l'utilisation de ces annotations soit liée à votre code à Byte Buddy. Cependant, Java ignore les annotations au cas où elles ne sont pas visibles par un chargeur de classe. De cette façon, le code généré peut toujours exister sans Byte Buddy! Vous pouvez trouver plus d'informations sur la MethodDelegation
et sur toutes ses annotations prédéfinies dans son javadoc et dans le tutoriel de Byte Buddy.
Byte Buddy ne se limite pas à la création de sous-classes mais est également capable de redéfinir le code existant. Pour ce faire, Byte Buddy propose une API pratique pour définir des agents dits Java. Les agents Java sont des programmes Java simples qui peuvent être utilisés pour modifier le code d'une application Java existante pendant son exécution. À titre d'exemple, nous pouvons utiliser Byte Buddy pour modifier des méthodes pour imprimer leur temps d'exécution. Pour cela, nous définissons d'abord un intercepteur similaire aux intercepteurs dans les exemples précédents:
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 ));
}
}
}
À l'aide d'un agent Java, nous pouvons désormais appliquer cet intercepteur à tous les types qui correspondent à un ElementMatcher
pour une TypeDescription
. Pour l'exemple, nous choisissons d'ajouter l'intercepteur ci-dessus à tous les types avec un nom qui se termine par Timed
. Cela est fait pour la simplicité alors qu'une annotation serait probablement une alternative plus appropriée pour marquer ces classes pour un agent de production. En utilisant l'API AgentBuilder
de Byte Buddy, la création d'un agent Java est aussi simple que de définir la classe d'agent suivante:
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 );
}
}
Semblable à la méthode main
de Java, la méthode premain
est le point d'entrée de tout agent Java à partir duquel nous appliquons la redéfinition. Comme un argument, un agent Java reçoit une instance de l'interface Instrumentation
qui permet à Byte Buddy de s'accrocher à l'API standard de JVM pour la redéfinition de la classe d'exécution.
Ce programme est emballé avec un fichier manifeste avec l'attribut Premain-Class
pointant vers le TimerAgent
. Le fichier JAR résultant peut désormais être ajouté à n'importe quelle application Java en définissant -javaagent:timingagent.jar
similaire à l'ajout d'un pot au chemin de classe. Avec l'agent actif, toutes les classes se terminant par Timed
impriment désormais leur temps d'exécution à la console.
Byte Buddy est également capable d'appliquer des pièces jointes dits d'exécution en désactivant les modifications du format de fichier de classe et en utilisant l'instrumentation Advice
. Veuillez vous référer au Javadoc des Advice
et de la classe AgentBuilder
pour plus d'informations. Byte Buddy propose également le changement explicite des classes Java via une instance ByteBuddy
ou en utilisant les plugins Byte Buddy Maven et Gradle .
Byte Buddy est une bibliothèque complète et nous n'avons fait que gratter la surface des capacités de Byte Buddy. Cependant, Byte Buddy vise à être facile à utiliser en fournissant un langage spécifique au domaine pour la création de classes. La plupart de la génération de code d'exécution peut être effectuée en écrivant du code lisible et sans aucune connaissance du format de fichier de classe de Java. Si vous voulez en savoir plus sur Byte Buddy, vous pouvez trouver un tel tutoriel sur la page Web de Byte Buddy (il y a aussi une traduction chinoise disponible).
En outre, Byte Buddy est livré avec une documentation détaillée en code et une couverture de cas de test approfondie qui peut également servir d'exemple de code. Enfin, vous pouvez trouver une liste à jour d'articles et de présentations sur Byte Buddy dans le wiki. Lorsque vous utilisez Byte Buddy, assurez-vous également de lire les informations suivantes sur la maintenance d'une dépendance d'un projet.
L'utilisation de Byte Buddy est gratuite et ne nécessite pas l'achat d'une licence. Pour tirer le meilleur parti de la bibliothèque ou pour sécuriser un début facile, il est cependant possible d'acheter une formation, des heures de développement ou des plans de soutien. Les taux dépendent de la portée et de la durée d'un engagement. Veuillez contacter [email protected] pour plus d'informations.
Byte Buddy est répertorié sur Tidelift. Si vous n'utilisez pas Byte Buddy dans une mesure où vous souhaitez acheter un support explicite et souhaitez soutenir la communauté open source en général, veuillez envisager un abonnement.
Vous pouvez soutenir mon travail via des sponsors GitHub. Notez que cette option est uniquement destinée aux acteurs commerciaux qui recherchent un canal de paiement simple et qui ne s'attendent pas à l'assistance en retour. Le soutien via les sponsors GitHub n'est pas possible de maintenir la conformité TVA. Veuillez plutôt chercher un accord de soutien direct.
Des questions générales peuvent être posées sur le débordement de pile ou sur la liste de diffusion des copains d'octets qui servent également d'archive pour les questions. Bien sûr, les rapports de bogues seront également pris en compte en dehors d'un plan commercial. Pour les projets open source, il est parfois possible de recevoir une aide prolongée pour mettre en service Byte Buddy.
Byte Buddy est écrit sur ASM, une bibliothèque mature et bien testée pour lire et écrire des classes Java compilées. Afin de permettre des manipulations de type avancé, Byte Buddy exposait intentionnellement l'API ASM à ses utilisateurs. Bien sûr, l'utilisation directe d'ASM reste entièrement facultative et la plupart des utilisateurs ne l'exigeront probablement jamais. Ce choix a été fait de telle sorte qu'un utilisateur de Byte Buddy n'est pas retenu à sa fonctionnalité de niveau supérieur mais peut implémenter des implémentations personnalisées sans histoires lorsqu'elle est nécessaire.
ASM a précédemment modifié son API publique, mais a ajouté un mécanisme de compatibilité API en commençant par la version 4 de la bibliothèque. Afin d'éviter que la version entre les conflits avec de telles versions plus anciennes, Byte Buddy reconditionne la dépendance ASM dans son propre espace de noms. Si vous souhaitez utiliser ASM directement, l'artefact byte-buddy-dep
propose une version de Byte Buddy avec une dépendance explicite à ASM. Ce faisant, vous devez reconditionner à la fois Byte Buddy et ASM dans votre espace de noms pour éviter les conflits de version.
Veuillez noter la politique de sécurité de ce projet.
Byte Buddy prend en charge l'exécution sur toutes les versions JVM à partir de la version cinq et en un seul pot. Ceci est fait pour faciliter le développement d'agents Java qui nécessitent souvent de soutenir des applications plus anciennes ou inconnues qui ne sont pas activement mises à jour. Pour permettre cela tout en prenant en charge le Java et les fonctionnalités modernes comme les CD ou la validation des classes avec des cadres de cartes de pile, les pots principaux pour le navire Byte Buddy en tant que pots multi-libérés qui contiennent des fichiers de classe dans les version cinq et huit. En conséquence, la taille du pot de Byte Buddy est plus élevée comme on pourrait s'y attendre. La taille du fichier JAR n'est normalement pas un problème, car la majorité des classes de Byte Buddy ne seront jamais chargées. Pourtant, la taille du fichier peut être un problème lors de la distribution d'agents Java. Comme les agents doivent déjà être regroupés en tant que pot unique, il est donc recommandé de supprimer soit la version Java Five de base, soit la version Java Eight de Java à libération multiple des fichiers de classe contenus, pour réduire ce problème. Ceci est pris en charge par la plupart des plugins de construction à cet effet, tels que le plugin Maven Shade.
Byte Buddy est sous licence en vertu de la licence Apache libérale et conviviale, version 2.0 et est disponible gratuitement sur GitHub. De plus, la distribution de Byte Buddy regroupe ASM qui est publiée dans le cadre d'une licence BSD à 3 clauses.
Les binaires Byte Buddy sont publiés dans les référentiels de Maven Central et sur JCenter. Les signatures des artefacts peuvent être validées contre cette clé publique PGP à partir de Byte Buddy 1.10.3. Les versions plus anciennes peuvent être validées par rapport à ce certificat plus ancien et plus faible.
Le projet est construit à l'aide de maven. De votre coquille, le clonage et la construction du projet iraient quelque chose comme ceci:
git clone https://github.com/raphw/byte-buddy.git
cd byte-buddy
mvn package
Sur ces commandes, Byte Buddy est cloné à partir de GitHub et construit sur votre machine. D'autres options de construction sont répertoriées dans le fichier Root POM. Byte Buddy peut être construit avec n'importe quel JDK d'au moins la version 6. Il est cependant recommandé d'utiliser un JDK d'au moins la version 8 car les versions pour les version 6 et 7 nécessitent l'utilisation de HTTP non crypté. Son support est uniquement destiné à exécuter des tests par rapport à cette version JDK et peut vous exposer à des attaques d'homme dans le milieu. Par conséquent, ces versions doivent être évitées. Byte Buddy est actuellement testé pour les versions 6 et plus du JDK sur les serveurs CI.
Veuillez utiliser le suivi des problèmes de GitHub pour signaler les bogues. Lors de la validation du code, veuillez fournir des cas de test qui prouvent la fonctionnalité de vos fonctionnalités ou qui démontrent une correction de bogue. De plus, assurez-vous que vous ne rompez aucun cas de test existant. Si possible, veuillez prendre le temps d'écrire une documentation. Pour les demandes de fonctionnalités ou les commentaires généraux, vous pouvez également utiliser le tracker des problèmes ou nous contacter sur notre liste de diffusion.
Le travail sur Byte Buddy est également possible grâce à une rangée de supporters qui ont consacré des ressources régulières et une attention au projet. Veuillez prendre votre temps pour jeter un œil à ces supporters et à leurs offres.