Byte Buddy é uma biblioteca de geração de código e manipulação para criar e modificar as classes Java durante o tempo de execução de um aplicativo Java e sem a ajuda de um compilador. Além dos utilitários de geração de código que enviam com a biblioteca de classes Java, o Byte Buddy permite a criação de classes arbitrárias e não se limita à implementação de interfaces para a criação de proxies de tempo de execução. Além disso, o Byte Buddy oferece uma API conveniente para mudar de aulas manualmente, usando um agente Java ou durante uma construção.
Para usar o Byte Buddy, não é necessário entender o código Java Byte ou o formato de arquivo de classe. Por outro lado, a API de Byte Buddy pretende o código conciso e fácil de entender para todos. No entanto, o Byte Buddy permanece totalmente personalizável até a possibilidade de definir o código de byte personalizado. Além disso, a API foi projetada para não ser o mais intrusiva possível e, como resultado, o Byte Buddy não deixa nenhum rastro nas classes criadas por ele. Por esse motivo, as classes geradas podem existir sem a necessidade de byte Buddy no caminho da classe. Por causa desse recurso, o mascote de Byte Buddy foi escolhido para ser um fantasma.
Byte Buddy está escrito em Java 5, mas suporta a geração de classes para qualquer versão Java. Byte Buddy é uma biblioteca leve e depende apenas da API do visitante da Biblioteca de Parsers de Código Java Byte, que não requer mais dependências.
À primeira vista, a geração de código de tempo de execução pode parecer algum tipo de magia negra que deve ser evitada e apenas poucos desenvolvedores escrevem aplicativos que geram explicitamente código durante o tempo de execução. No entanto, essa imagem muda ao criar bibliotecas que precisam interagir com código e tipos arbitrários que são desconhecidos no momento da compilação. Nesse contexto, um implementador da biblioteca deve escolher entre exigir que um usuário implemente interfaces de biblioteca-proprietária ou para gerar código em tempo de execução quando os tipos do usuário se tornarem conhecidos pela primeira vez na biblioteca. Muitas bibliotecas conhecidas, como, por exemplo, primavera ou hibernato, escolhem a última abordagem, que é popular entre seus usuários sob o termo do uso de objetos java antigos . Como resultado, a geração de código se tornou um conceito onipresente no espaço Java. Byte Buddy é uma tentativa de inovar a criação de tempo de execução dos tipos de Java, a fim de fornecer uma ferramenta melhor definida para aqueles que dependem da geração de código.
Em outubro de 2015, Byte Buddy foi distinguido com o Duke's Choice Award da Oracle. O prêmio aprecia o Byte Buddy por sua " enorme quantidade de inovação na tecnologia Java ". Sentimos -nos muito honrados por ter recebido esse prêmio e queremos agradecer a todos os usuários e a todos que ajudaram a fazer byte Buddy o sucesso que ele se tornou. Nós realmente apreciamos isso!
Byte Buddy oferece excelente desempenho na qualidade da produção. É estável e em uso por estruturas e ferramentas distintas, como Mockito, Hibernate, Jackson, Bazel Build System do Google e muitos outros. O Byte Buddy também é usado por um grande número de produtos comerciais em grande resultado. Atualmente, é baixado mais de 75 milhões de vezes por ano.
Dizer olá mundo com byte buddy é o mais fácil possível. Qualquer criação de uma classe Java começa com uma instância da classe ByteBuddy
, que representa uma configuração para criar novos 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!" ));
A configuração padrão ByteBuddy
, que é usada no exemplo acima, cria uma classe Java na versão mais recente do formato de arquivo de classe que é entendida pela máquina virtual de processamento Java. Como espero óbvio no código de exemplo, o tipo criado estenderá a classe Object
e substituirá seu método toString
, que deve retornar um valor fixo do Hello World!
. O método a ser substituído é identificado por um chamado ElementMatcher
. No exemplo acima, é usado um elemento predefinido named(String)
, que identifica métodos por seus nomes exatos. O Byte Buddy vem com inúmeros fósforos predefinidos e bem testados que são coletados na classe ElementMatchers
e que podem ser facilmente compostos. A criação de correspondentes personalizados é tão simples quanto implementar a interface (funcional) ElementMatcher
.
Para implementar o método toString
, a classe FixedValue
define um valor de retorno constante para o método substituído. Definir um valor constante é apenas um exemplo de muitos interceptores de método que são enviados com byte Buddy. Ao implementar a interface Implementation
, um método pode, no entanto, ser definido pelo código de byte personalizado.
Finalmente, a classe Java descrita é criada e depois carregada na máquina virtual Java. Para esse fim, é necessário um carregador de classe de destino. Eventualmente, podemos nos convencer do resultado, chamando o método toString
em uma instância da classe criada e encontrando o valor de retorno para representar o valor constante que esperávamos.
Obviamente, um exemplo do Hello World é um caso de uso muito simples para avaliar a qualidade de uma biblioteca de geração de código. Na realidade, um usuário dessa biblioteca deseja realizar manipulações mais complexas, por exemplo, introduzindo ganchos no caminho de execução de um programa Java. Usando o Byte Buddy, fazê -lo, no entanto, é igualmente simples. O exemplo a seguir fornece uma amostra de como as chamadas do método podem ser interceptadas.
Byte Buddy expressa implementações de métodos definidos dinamicamente por instâncias da interface Implementation
. No exemplo anterior, FixedValue
que implementa essa interface já foi demonstrado. Ao implementar essa interface, um usuário do Byte Buddy pode ir para a duração da definição do código de byte personalizado para um método. Normalmente, é mais fácil usar implementações predefinidas de Byte Buddy, como MethodDelegation
, que permite a implementação de qualquer método em Java simples. O uso dessa implementação é diretamente para a frente, pois opera delegando o fluxo de controle a qualquer POJO. Como exemplo de tal pojo, o Byte Buddy pode, por exemplo, redirecionar uma chamada para o único método da seguinte classe:
public class GreetingInterceptor {
public Object greet ( Object argument ) {
return "Hello from " + argument ;
}
}
Observe que o GreetingInterceptor
acima não depende de nenhum tipo de amigo de byte. Esta é uma boa notícia, porque nenhuma das aulas que o Byte Buddy gera exige amigo de byte no caminho da classe! Dado o GreetingInterceptor
acima, podemos usar o Byte Buddy para implementar a interface Java 8 java.util.function.Function
e seu Método apply
Abstracia:
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" ));
Executando o código acima, o Byte Buddy implementa a interface Function
de Java e implementa o método apply
como uma delegação a uma instância do Pojo GreetingInterceptor
que definimos antes. Agora, toda vez que a Function::apply
o método é chamado, o fluxo de controle é despachado para GreetingInterceptor::greet
e o valor de retorno do último método é retornado do método da interface.
Os interceptores podem ser definidos para levar com entradas e saídas mais genéricas anotando os parâmetros do interceptador. Quando o Byte Buddy descobre uma anotação, a biblioteca injeta a dependência que o parâmetro interceptador exige. Um exemplo para um interceptador mais geral é a seguinte classe:
public class GeneralInterceptor {
@ RuntimeType
public Object intercept ( @ AllArguments Object [] allArguments ,
@ Origin Method method ) {
// intercept any method of any signature
}
}
Com o interceptador acima, qualquer método interceptado pode ser correspondido e processado. Por exemplo, ao corresponder Function::apply
, os argumentos do método seriam passados como o elemento único de uma matriz. Além disso, uma referência Method
a Fuction::apply
seria aprovada como o segundo argumento do interceptador devido à anotação @Origin
. Ao declarar a anotação @RuntimeType
no método, o Byte Buddy finalmente lança o valor retornado ao valor de retorno do método interceptado, se isso for necessário. Ao fazer isso, o Byte Buddy também aplica boxe e unboxing automáticos.
Além das anotações que já foram mencionadas, existem muitas outras anotações predefinidas. Por exemplo, ao usar a anotação @SuperCall
em um tipo Callable
Runnable
ou tipo de byte injeta instâncias de proxy que permitem uma invocação de um método não abstrato, se houver esse método. E mesmo que o Byte Buddy não cubra um caso de uso, o Byte Buddy oferece um mecanismo de extensão para definir anotações personalizadas.
Você pode esperar que o uso dessas anotações vincule seu código a Byte Buddy. No entanto, o Java ignora as anotações, caso não sejam visíveis para um carregador de classe. Dessa forma, o código gerado ainda pode existir sem byte Buddy! Você pode encontrar mais informações sobre o MethodDelegation
e todas as suas anotações predefinidas em seu tutorial Javadoc e Byte Buddy.
O Byte Buddy não se limita à criação de subclasses, mas também é capaz de redefinir o código existente. Para fazer isso, o Byte Buddy oferece uma API conveniente para definir os chamados agentes Java. Os agentes Java são programas Java antigos simples que podem ser usados para alterar o código de um aplicativo Java existente durante seu tempo de execução. Como exemplo, podemos usar o Byte Buddy para alterar os métodos para imprimir seu tempo de execução. Para isso, primeiro definimos um interceptador semelhante aos interceptores nos exemplos 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 um agente Java, agora podemos aplicar esse interceptador a todos os tipos que correspondem a um ElementMatcher
a uma TypeDescription
. Para o exemplo, optamos por adicionar o interceptador acima a todos os tipos com um nome que termina em Timed
. Isso é feito por uma questão de simplicidade, enquanto uma anotação provavelmente seria uma alternativa mais apropriada para marcar essas classes para um agente de produção. Usando a API do Byte Buddy's AgentBuilder
, a criação de um agente Java é tão fácil quanto definir a seguinte classe 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 );
}
}
Semelhante ao método main
de Java, o método premain
é o ponto de entrada para qualquer agente Java do qual apliquemos a redefinição. Como um argumento, um agente Java recebe uma instância da interface Instrumentation
que permite que o Byte Buddy se conecte à API padrão da JVM para redefinição de classe de tempo de execução.
Este programa é empacotado junto com um arquivo de manifesto com o atributo Premain-Class
apontando para o TimerAgent
. O arquivo JAR resultante agora pode ser adicionado a qualquer aplicativo Java configurando -javaagent:timingagent.jar
semelhante a adicionar um jar ao caminho da classe. Com o agente ativo, todas as classes que terminam em Timed
agora imprimem seu tempo de execução no console.
O Byte Buddy também é capaz de aplicar os chamados anexos de tempo de execução, desativando as alterações no formato de arquivo de classe e usando a instrumentação Advice
. Consulte o Javadoc dos Advice
e da classe AgentBuilder
para obter mais informações. O Byte Buddy também oferece a mudança explícita das classes Java por meio de uma instância ByteBuddy
ou usando os plugins de Byte Buddy Maven e Gradle .
Byte Buddy é uma biblioteca abrangente e apenas arranhamos a superfície das capacidades de Byte Buddy. No entanto, o Byte Buddy pretende ser fácil de usar, fornecendo uma linguagem específica para o domínio para criar classes. A maioria das gerações de código de tempo de execução pode ser feita escrevendo código legível e sem nenhum conhecimento do formato de arquivo de classe de Java. Se você quiser saber mais sobre o Byte Buddy, pode encontrar um tutorial na página da web do Byte Buddy (também há uma tradução chinesa disponível).
Além disso, o Byte Buddy vem com uma documentação detalhada no código e uma extensa cobertura de casos de teste, que também pode servir como código de exemplo. Finalmente, você pode encontrar uma lista atualizada de artigos e apresentações sobre Byte Buddy no Wiki. Ao usar o Byte Buddy, também leia as seguintes informações sobre como manter uma dependência do projeto.
O uso do Byte Buddy é gratuito e não requer a compra de uma licença. Para tirar o máximo proveito da biblioteca ou garantir um começo fácil, é possível comprar treinamento, horas de desenvolvimento ou planos de suporte. As taxas dependem do escopo e da duração de um engajamento. Entre em contato com [email protected] para obter mais informações.
Byte Buddy está listado no Tidelift. Se você não estiver usando o Byte Buddy em uma extensão em que deseja comprar suporte explícito e deseja apoiar a comunidade de código aberto em geral, considere uma assinatura.
Você pode apoiar meu trabalho via patrocinadores do GitHub. Observe que esta opção é destinada apenas a atores comerciais que procuram um canal de pagamento simples e que não esperam suporte em troca. O suporte via patrocinadores do GitHub não é possível manter a conformidade com o IVA. Entre em contato com um contrato de apoio direto.
Perguntas gerais podem ser feitas no transbordamento da pilha ou na lista de discussão de bytes, que também serve como um arquivo para perguntas. Obviamente, os relatórios de bugs também serão considerados fora de um plano comercial. Para projetos de código aberto, às vezes é possível receber ajuda prolongada para levar o Byte Buddy em uso.
Byte Buddy está escrito no topo da ASM, uma biblioteca madura e bem testada para ler e escrever aulas de java compilados. Para permitir manipulações de tipo avançado, o Byte Buddy está intencionalmente expondo a API ASM a seus usuários. Obviamente, o uso direto do ASM permanece totalmente opcional e a maioria dos usuários provavelmente nunca o exigirá. Essa opção foi feita de tal forma que um usuário do Byte Buddy não é restrito à sua funcionalidade de nível superior, mas pode implementar implementações personalizadas sem problemas quando for necessário.
A ASM mudou anteriormente sua API pública, mas adicionou um mecanismo para a compatibilidade da API, começando com a versão 4 da biblioteca. Para evitar conflitos na versão com versões tão antigas, o Byte Buddy reembala a dependência do ASM em seu próprio espaço para nome. Se você deseja usar diretamente o ASM, o artefato de byte-buddy-dep
oferece uma versão do byte buddy com uma dependência explícita ao ASM. Ao fazer isso, você deve reembalar o Byte Buddy e o ASM em seu espaço para o nome para evitar conflitos de versão.
Observe a política de segurança deste projeto.
Byte Buddy suporta a execução em todas as versões da JVM da versão cinco e em um único frasco. Isso é feito para facilitar o desenvolvimento de agentes Java, que geralmente precisam apoiar aplicativos mais antigos ou desconhecidos que não são atualizados ativamente. Para permitir isso, além de suportar Java moderno e recursos como CDs ou validação de classe com quadros de mapa de pilha, os frascos principais do Byte Buddy Ship como frascos de multi-lançamento que contêm arquivos de classe na versão cinco e oito. Como resultado, o tamanho do jarra de byte Buddy é mais alto, como seria de esperar. O tamanho do arquivo JAR não é normalmente um problema, pois a maioria das classes de Byte Buddy nunca será carregada. No entanto, o tamanho do arquivo pode ser um problema ao distribuir agentes Java. Como os agentes já precisam ser incluídos como um único frasco, é recomendável remover a versão básica Java Cinco ou a versão Java oito de vários lançamentos dos arquivos de classe contidos, para reduzir esse problema. Isso é suportado pela maioria dos plug -ins de construção para esse fim, como o plug -in MAVEN Shade.
O Byte Buddy está licenciado sob a licença Apache liberal e favorável aos negócios, versão 2.0 e está disponível gratuitamente no GitHub. Além disso, a distribuição de bytes acumula o ASM, que é liberado sob uma licença BSD de 3 cláusulas.
Os binários de bytes são publicados nos repositórios da Maven Central e no JCenter. As assinaturas de artefatos podem ser validadas em relação a essa chave pública do PGP, começando com o Byte Buddy 1.10.3. As versões mais antigas podem ser validadas com esse certificado mais antigo e mais fraco.
O projeto é construído usando o MAVEN. De sua concha, a clonagem e a construção do projeto iriam algo assim:
git clone https://github.com/raphw/byte-buddy.git
cd byte-buddy
mvn package
Nesses comandos, o Byte Buddy é clonado do GitHub e construído em sua máquina. Outras opções de construção estão listadas no arquivo root pom. O Byte Buddy pode ser construído com qualquer JDK de pelo menos a versão 6. No entanto, é recomendável usar um JDK de pelo menos a versão 8, pois as compilações para as versão 6 e 7 exigem o uso de HTTP não criptografado. Seu suporte é destinado apenas à execução de testes contra esta versão JDK e pode expô-lo a ataques de man-in-the-middle. Portanto, essas construções devem ser evitadas. Atualmente, o Byte Buddy é testado para as versões 6 e mais do JDK nos servidores CI.
Use o rastreador de problemas do Github para relatar bugs. Ao comprometer o código, forneça casos de teste que comprovam a funcionalidade de seus recursos ou que demonstrem uma correção de bug. Além disso, verifique se você não está quebrando nenhum caso de teste existente. Se possível, reserve um tempo para escrever alguma documentação. Para solicitações de recursos ou feedback geral, você também pode usar o rastreador de problemas ou entrar em contato conosco em nossa lista de discussão.
O trabalho em Byte Buddy também é possível graças a uma questão de apoiadores que dedicaram recursos regulares e atenção ao projeto. Por favor, reserve um tempo para dar uma olhada nesses apoiadores e suas ofertas.