Metaprogramação em Java? O papel do APT.

Em 2004, com o lançamento do Java 5, muitas novidades entraram pra linguagem. As anotações são um recurso hoje fundamental, que utilizamos como metadados. O Hibernate, junto com a JPA/EJB 3.0, popularizaram muito o uso das anotações para afetar o comportamento em tempo de execução do framework em relação às suas classes anotadas.

Em linguagens dinâmicas isso vai além. O Ruby utiliza muito metadata, até mesmo para gerar acessores e modificadores de atributos em tempo de execução:

class ContaPagar
  attr_accessor :descricao, :valor, :data
end

Como poderíamos fazer um exemplo simples desse em Java? Infelizmente não é algo trivial como o uso de anotações feito pelo Hibernate/JPA, pois, mesmo usando manipulação de bytecode, não adianta gerar os getters ou setters em tempo de execução, já que não conseguiremos utilizá-los pela característica de tipagem estática da linguagem. Precisaremos usar uma abordagem em tempo de compilação.

O JAX-WS utiliza bastante de anotações para a geração de código cliente dos webservices através de duas feramentas: o wsimport para trabalhar com WSDL e o Annotation Processor Tool (APT) para trabalhar com código fonte Java. Através do APT, incluso apenas como ferramenta no Java 5, você pode criar Processadores de anotações que são acionados pelo APT e podem gerar novos códigos Java.

O Java 6 leva o APT para um outro nível: além de fazer parte da API (não apenas uma ferramenta com.sun) agora é possível que um Processor sejá executado pelo próprio compilador, sem a necessidade de executá-lo a parte. Para isso, basta que exista um jar no seu classpath que contenha o arquivo javax.annotation.processing.Processor dentro de META-INF/services, fazendo com que os processadores lá citados sejam executados a cada compilação, possivelmente gerando novo código Java.

Onde isso é útil?

Um dos aguardados recursos da JPA 2.0 no Java EE 6 é o sistema de Criteria parecido com o do Hibernate, que pode ser agora utilizado de uma maneira typesafe sem o uso de Strings. Como iremos nos referenciar à um atributo de uma classe sem String? Para uma classe, sabemos que podemos escrever NomeDaClasse.class, e um Class<NomeDaClasse> carregado pelo mesmo classloader será retornado.

Apesar de ser um dos 25 recursos mais pedidos pela comunidade, o Java não possui suporte para literais de construtores, métodos e atributos. Isso é, se quero pegar uma referência para um Method que tenho certeza absoluta que existe, preciso de qualquer forma utilizar a API de reflection e lidar com as checked exceptions e diversos passos necessários. Uma forma de contornar isso e fazer a Criteria typesafe da JPA 2.0 ser viável foi o uso do StaticMetaModel.

Imagine que para a classe ContaPagar a seguir:

@Entity
public class ContaPagar {

  @Id
  @GeneratedValue
  private int id;

  private String descricao;

  private Double valor;

  @Temporal(TemporalType.DATE)
  private Calendar data;

  @ManyToOne
  private Fornecedor fornecedor;

  private boolean pago;

  // metodos
}

Precisaríamos criar uma classe paralela, a ContaPagar_, que contém atributos estáticos que de certa forma representam os atributos persistidos da nossa entidade JPA:

@StaticMetamodel(ContaPagar.class)
public abstract class ContaPagar_ {

  public static 
    volatile SingularAttribute<ContaPagar, Integer> id;
  public static 
    volatile SingularAttribute<ContaPagar, String> descricao;
  public static 
    volatile SingularAttribute<ContaPagar, Double> valor;
  public static 
    volatile SingularAttribute<ContaPagar, Calendar> data;
  public static 
    volatile SingularAttribute<ContaPagar, Fornecedor> fornecedor;
  public static 
    volatile SingularAttribute<ContaPagar, Boolean> pago;
}

Daria um certo trabalho manter a estranha classe ContaPagar_ atualizada de acordo com toda modificação na classe ContaPagar. A solução é utilizar um gerador de código, e esse gerador é um processador do APT, que será invocado toda vez que o javac rodar (no Eclipse é necessário ativar o APT). O Hibernate 3.5-beta2 já disponibiliza esse gerador (mas com limitado suporte ao Criteria do JPA2), assim como o EclipseLink. Dessa forma podemos selecionar o atributo valor de todas as ContaPagar, com garantia de tipos através de ContaPagar_.valor:

CriteriaBuilder cb = manager.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Double.class);
Root cpagar = cq.from(ContaPagar.class);
CriteriaQuery select =
	cq.select(cpagar.get(ContaPagar_.valor));
return this.manager.createQuery(select).getResultList();

Se isso é uma vantagem ou não, é outra discussão: alguns dizem que esse ganho de tipagem forte na Criteria não compensa a quantidade de código extra, além de que seus testes unitários pegariam erros no caso de String erradas. Diferente do que parece a primeira vista, o código não é refatorável da maneira clássica: renomear o atributo valor para valorTotal vai gerar um novo atributo ContaPagar_.valorTotal, mas quem se referenciava ao atributo com nome antigo não será refatorado, pois refatoramos o nome do modelo, e não do metamodelo. As ferramentas devem evoluir para conseguir lidar com casos como esse.

Mas seria essa metaprogramação tão poderosa quanto a do Ruby, onde métodos são inclusos na classe de acordo com o uso de metadados (como o attr_acessor)?

O Sérgio Lopes e o Nico Steppat da Caelum me apresentaram ao lombok, um processador do APT que, através de recursos da API não pública da JVM da Sun, gera código na própria classe que está sendo compilada:

meta programação com Java e APT

À esquerda um código que não possui métodos, à direita uma surpresa: o outline do Eclipse acusando a exitência de métodos no bytecode dessa classe.


Com isso podemos gerar métodos em tempo de compilação que podem ser acessados pela sua IDE, fornecendo até mesmo code completion! Isso abre caminho para recursos que se assemelham aos mixins, e com tipagem estática, e ao mesmo tempo para prejuízos parecidos com os do monkey patching. A força de uma linguagem mais dinâmica permitiu a criação do grails com recursos similares aos de ActiveRecord do rails, onde – no caso do rails – atributos do banco podem ser acessados diretamente sem requerer a declaração no modelo.

O APT abre caminho para implementações similares na plataforma Java usando a própria linguagem, alguém poderá em um futuro próximo adicionar os getters e setters necessários para seu modelo baseado na engenharia reversa de tabelas, relacionamentos e configurações, como o Rails faz, mas com algumas limitações. Há também a questão de que se isso é útil para uma linguagem como Java.

17 Comentários

  1. Rodrigo Urubatan 09/12/2009 at 10:33 #

    Eu não conhecia o Lombok, achei muito show de bola, é a coisa mais legal que eu já vi em java nos últimos 8 anos (que é mais ou menos o tempo que eu programo em java 😀 )

    Minha cabeça explodiu!
    Eu enxerguei uma anotação que adiciona metodos save, destroy, update e alguns finders estáticos em uma classe qualquer utilizando JPA 😀
    (não sei se isto não seria deturpar a idéia inicial, mas foi a primeira coisa que passou pela minha cabeça 😀 )

  2. Paulo Silveira 09/12/2009 at 10:37 #

    Oi Urubatan
    Nao é deturpar. Faltou um último paragrafo ai dizendo “Sera que vão aparecer implementações de ActiveRecord em Java?”

    Mais ainda, usando uma anotação pode ser que em tempo de compilação o APT crie os _atributos_ para você, assim como os relacionamentos com as outras classes. Se vale a pena, não sei.

    Atualizei o fim do artigo com isso!

  3. Thiago Senna 09/12/2009 at 10:52 #

    Olá,

    também gosto muito da idéia sugerida do APT. Além do que vocês sugeriram (tanto no post como nos comentários) ainda há muito o que inventar! Por exemplo, poderiamos criar uma API de suporte para a aplicação. Imagine você gerar o Controller para os CRUD’s utilizando APT? Outro exemplo, no caso do wicket para cada entidade você poderia gerar automaticamente as classes ‘Entidade’LoadableModel, ‘Entidade”DataProvider, ‘Entidade’DataTable, ‘Entidade”Form, ‘Entidade’DeleteConfirmPanel’ e por ai vai. Varios trabalhos que temos que fazer no braço poderiamos automatizar com a geração de código usando APT. Não apenas isso.. em alguns casos você pode optar por simplesmente gerar código ao invés de “generalizar” demais uma classe. As vezes para criar uma classe genérica você acaba poluindo com informações extras (metadados) as classes que utilizarão a tal classe genérica. Com geração de código vc gera mais código e polui menos o código que realmente importa. Isso no meu ver faz muita diferença.

    Se alguém tiver idéias para implementar usando APT, deixem-me saber!

    Parabéns pelo artigo, está faltando contribuições nesta área.

    Thiago Senna

  4. Edufa 09/12/2009 at 11:04 #

    Acho q @EqualsAndHashCode e @ToString deveriam ser aceitos nos atributos, para evitar ter de colocar o nome do campo, maioria das classes usam poucos atributos para esses métodos.

    POrém realmente existe o problema de entupir a classe de anotações, mas para as coisas mais simples e repetitivas seria muito bom.

  5. Thiago Senna 09/12/2009 at 11:13 #

    Opa, só para contribuir mais um pouco com o blog, tem um framework que utiliza apt que é bem interessante. Ele ajudaria muito para você personalizar a geração de código utilizando templates, no caso, freemarker, se não me engano.

    http://apt-jelly.sourceforge.net/

    Eu explorei pouco ele, mas a idéia é interessante. Se fosse criado um ambiente/infraestrutura ou coisa do tipo que fosse um pouco mais simples e fácil de usar e com uma boa integração com o eclipse (que não deve ser difícil) não seria difícil para que alguns programadores pudessem aderir práticas em desenvolver sistemas java orientado a geração de código. É só buscarmos em comunidades paralelas (MDSD) quais seriam as boas práticas de fato em geração de código e aplicá-las em um ambiente utilizando APT.

    Enfim, pode ser que eu esteja viajando na batatinha, mas é fato que ainda é possível elevar e muito a produtividade em um ambiente de desenvolvimento java. Daria trabalho, mas a recompensa seria enorme e até mesmo projetos não java poderia tirar benefício disso.

    Abraço,
    Thiago

  6. Angelo Belchior 09/12/2009 at 11:36 #

    Interessante o “Sera que vão aparecer implementações de ActiveRecord em Java?”

    Porém, acredito que o LINQ do .Net é mais legal.

    Mas ai, cada caso é um caso…

  7. Fábio Zoroastro 09/12/2009 at 12:07 #

    Olá Tiago, eu já utilizei o APT-Jelly em um projeto que gerava códigos para a utilização de EJB 2.0. Com o auxílio do apt-jelly e templates em freemarker foi possível gerar o código para atender as nossas necessidades na época.
    Lembro-me que um colega de trabalho havia feito uma anotação para gerar classes de teste unitário com JUnit para realizar os testes da realidade do projeto.

    Concluindo: Gostei da experiência que tive com o apt-jelly. Já está armazenado em meu ReadLater o link do projeto lombok.

    Até mais.

  8. Raphael Lacerda 09/12/2009 at 13:12 #

    lombok!! isso é magia negra, comprada no mercado negro, vindo de algum lugar muiiiiiiiiittoo tenso!! uhauhauhahua! Clandestinidade total!

  9. Raphael Lacerda 09/12/2009 at 13:14 #

    “..Mais ainda, usando uma anotação pode ser que em tempo de compilação o APT crie os _atributos_ para você, assim como os relacionamentos com as outras classes. Se vale a pena, não sei…”
    A ideia então é tirar o que? 90% do código java para ORM? hehe

  10. Eduardo Guerra 09/12/2009 at 15:04 #

    Heheheheh!!! Como adoro essas bruxarias!

    Vou começar a destrichar esse Lombok agora mesmo!!!

  11. Ribeiro 10/12/2009 at 14:23 #

    Não sei não, mas essa prática me cheira um “Generate Getters and Setters” atômico…
    No JAX-WS faz todo sentido, mas no Criteria parece ser uma gambiarra daquelas, onde seu uso maquia a verdadeira solução: Literais de atributos e métodos no Java.

  12. Rodrigo Pinto 10/12/2009 at 15:29 #

    Isto é muito interessante, a diversão está garantida. 😉
    Concordo com o Urubatan.
    Agora imaginem pequenos frameworks fazendo uso do lombok?
    Poder criar anotações específicas e olha o poder nas mãos.
    Quanto a questão do *monkey patch*, sou suspeito, pois com todo o risco, é um poder que nós teremos nas mãos, ofato é saber usar com cuidado.

  13. Paulo Silveira 10/12/2009 at 16:47 #

    @Ribeiro pois é, uma grande pena não termos os literais de atributos construtores e métodos. não entendo qual seria o grande problema. Da até para fazer como syntatic sugar do compilador, sem necessidade de mudar formato do bytecode.

  14. Marcelo Zeferino 10/12/2009 at 17:37 #

    Legal o artigo, mas tenho uma curiosidade…

    Foi falado sobre Java e feito um relacionamento com algumas funcionalidades do Ruby e Rails. Tenho visto muitos post que acabam comparando as duas linguagens (não é o caso aqui).

    Vocês não acham que trazer pontos que são características fortes de outras linguagens para dentro do Java podem decaracterizá-lo?

    Ir para o Ruby com a cabeça em Java ou para o Java com a cabeça em Ruby não seria prejudicial?

    Só uma curiosidade, quem sabe rola um post, Paulo? (rs)

    A[]´s
    Marcelo Zeferino

  15. Marcio Duran 26/12/2009 at 19:14 #

    Olá , Marcelo Zeferino !!!

    Concordo com você, e isso requer muita a atenção aos designer de projetos que vão usar soluções cuja a especificação não é caracteristica da linguagem, acredito que isso que esta acontecendo seja uma saída forçada programatica pela a deficiência ou limitação da linguagem.
    Na observação como disseram “Da até para fazer como syntatic sugar do compilador, sem necessidade de mudar formato do bytecode.”, é algo chutado sobre questões de previnibilidade ao comportamento do codigo áte sua arquitetura.
    Modernidade, Avanço , Inovação não talvez um outro paradigma para os programadores ou Matemáticos frustrados.

  16. Marcio Duran 27/12/2009 at 07:39 #

    Corrigindo a palavra acima é “Previsibilidade” Obrigado !!!!

  17. AV 18/05/2016 at 16:48 #

    So para ressuscitar o post ehehheeh. Ja existe algum Framework para o java com padrão active record ?

Deixe uma resposta