Persistência sem complexidade (minha experiência com o MentaBean)

42 respostas
E

A um certo tempo atrás eu estava em busca de uma ferramenta que me ajudasse a fazer os mapeamentos Objeto-Relacional para minhas aplicações. Testei algumas das alternativas usadas atualmente porém parecia que elas não serviam para o que eu precisava. Fiz vários testes com o Hibernate porém ele não se mostrou uma solução viável, visto que o projeto já estava em andamento e seria loucura de uma hora pra outra anotar todas as beans ou largar XMLs de configuração pelo projeto, sem falar da brusca mudança que ocorreria nas minhas DAOs se eu quisesse adotar a filosofia do Hibernate e ter que aprender HQL ou Criteria o que pra mim não faz sentido uma vez que eu já sei SQL. Procurando mais um pouco eu me deparei com o framework MentaBean idealizado pelo Sérgio (saoj aqui do GUJ) e resolvi estudá-lo mais a fundo. Trocando e-mails, estudando o código-fonte e propondo ideias acabei me tornando membro (committer) do projeto, implementando o suporte do MentaBean para o PostgreSQL e sugerindo funcionalidades que acabaram sendo implementadas em seguida. Depois disso comecei a ver a possibilidade enorme de adotá-lo nos meus projetos devido à sua filosofia focada na simplicidade.

A ideia do projeto é simples: automatizar as operações básicas de CRUD e facilitar bastante a construção de queries, sem mágica, sem códigos intrusivos, sem XMLs e sem Annotations (sim, elas são interessantes em muitos casos, mas o uso abusivo pode acabar em perda de performance e sujeira de código nas camadas básicas da aplicação).

O legal disso é que não precisamos mudar a lógica do sistema para utilizarmos o framework, ou seja, nada impede que por ora você deixe de usá-lo e faça suas operações de CRUD da forma que achar conveniente, pois quem deve decidir como, quando e de que forma alguma operação com o banco de dados deve ocorrer é você, não?

O MentaBean está na versão 1.3.0 e agora possui um site documentado que explica as funcionalidades da API de forma bem objetiva… E em breve estará também em português.

As features do projeto (tais como suporte à nested properties, mapeamento automático, etc.) estão detalhadas no site http://mentabean.soliveirajr.com. Talvez ele seja uma boa dica pra quem precisa uma forma simples de aumentar a produtividade no back-end das aplicações sem a complexidade desnecessária do Hibernate.

Ah, o projeto é open source, portanto os fontes estão disponíveis no site ou a partir do SVN. Se alguém resolver experimentar e quiser compartilhar a experiência, é só postar aqui…

42 Respostas

H

Tem dedo do saoj aí? :lol: :lol:

É, eu olhei aqui e achei legal.

Uma coisa que não gostei muito foi: .field(“username”, DBTypes.STRING). Se o nome do atributo mudar tem que mudar aí também. =/

Uma coisa que fiquei na dúvida foi se o código é portável entre DBs ou se funciona apenas para o Postgres.

G

É sempre bom testar novos frameworks.

Vou dá uma conferida!!!

E

Hebert Coelho:
Tem dedo do saoj aí? :lol: :lol:

É, eu olhei aqui e achei legal.

Ele é idealizador do projeto… Eu entrei nessa porque gostei da filosofia e queria ver como tudo funcionava…

O problema é que ou a gente se prende à atributos ou à colunas no banco. Qual a sua ideia neste caso?

Veja: http://mentabean.soliveirajr.com/mtw/Page/Dialects/mentabean-dialects

H

erico_kl:
Hebert Coelho:
Tem dedo do saoj aí? :lol: :lol:

É, eu olhei aqui e achei legal.

Ele é idealizador do projeto… Eu entrei nessa porque gostei da filosofia e queria ver como tudo funcionava…

O problema é que ou a gente se prende à atributos ou à colunas no banco. Qual a sua ideia neste caso?

Veja: http://mentabean.soliveirajr.com/mtw/Page/Dialects/mentabean-dialects

Legal.
Eu gosto como o JPA faz. Pega basta falar que a classe é entity e ele mapeia todas as colunas como campos da tabela. Se eu quero algo diferente disso, eu altero. [=

S

Se vc estive mapeando automaticamente a mudança no bean vai ser refletida no mapeamento => http://mentabean.soliveirajr.com/mtw/Page/Automapping/mentabean-auto-mapping

É a mesma coisa que vc falou abaixo:

A

Pelo jeito tem isso também: http://mentabean.soliveirajr.com/mtw/Page/Automapping/mentabean-auto-mapping

Concordo contigo em relação a usar strings para representar o nome dos atributos.

Eu gosto do mapeamento em código para manter type-safe.
Se você usa strings acaba perdendo isso.

Mas também não tenho idéia de como resolver isso em java.

H

saoj:
Hebert Coelho:

Uma coisa que não gostei muito foi: .field(“username”, DBTypes.STRING). Se o nome do atributo mudar tem que mudar aí também. =/

Se vc estive mapeando automaticamente a mudança no bean vai ser refletida no mapeamento => http://mentabean.soliveirajr.com/mtw/Page/Automapping/mentabean-auto-mapping

É a mesma coisa que vc falou abaixo:

Legal, mas é sempre programático a configuração? ou tem um modo automático de se fazer? Por exemplo, o JPA já varre todo o código e localizar quem é “entity” no caso. Como ficaria nesse caso? (só a URL mesmo de como fazer já basta :smiley: )

S

O MentaBean é contra anotação pois entende que elas se tornam facilmente uma zona para entender e manter. Como vc lida com o código abaixo:

@Entity
public class User {
 
   @ElementCollection
   @CollectionTable(name="Addresses", joinColumns=@JoinColumn(name="user_id"))
   @AttributeOverrides({ @AttributeOverride(name="street1",
                        column=@Column(name="fld_street"))})
   public Set<Address> getAddresses() {
       // ...
   }
 
   // (...)
}
 
@Embeddable
public class Address {
 
    // (...)
 
    public String getStreet1() {
        // (...)
    }
}

Com configuração programática as suas entidades ficam limpinhas, sem qualquer anotação ou dependência do framework. E o mapeamento via código (fluent API ou DSL) se torna milhares de vezes mais fácil de entender e manter.

Ficar brigando com essa zona de anotação para fazer a coisa funcionar é como ficar batendo naquelas TVs antigas para ver se a imagem melhora.

H

saoj:
Hebert Coelho:

Legal, mas é sempre programático a configuração? ou tem um modo automático de se fazer? Por exemplo, o JPA já varre todo o código e localizar quem é “entity” no caso. Como ficaria nesse caso? (só a URL mesmo de como fazer já basta :smiley: )

O MentaBean é contra anotação pois entende que elas se tornam facilmente uma zona para entender e manter. Como vc lida com o código abaixo:

@Entity
public class User {
 
   @ElementCollection
   @CollectionTable(name="Addresses", joinColumns=@JoinColumn(name="user_id"))
   @AttributeOverrides({ @AttributeOverride(name="street1",
                        column=@Column(name="fld_street"))})
   public Set<Address> getAddresses() {
       // ...
   }
 
   // (...)
}
 
@Embeddable
public class Address {
 
    // (...)
 
    public String getStreet1() {
        // (...)
    }
}

Com configuração programática as suas entidades ficam limpinhas, sem qualquer anotação ou dependência do framework. E o mapeamento via código (fluent API ou DSL) se tornam milhares de vezes mais fácil de entender e manter.

Questão de gosto né?
Beleza, parabéns pela iniciativa. [=

S

Com certeza. Mas meu ponto é que se vc não tomar cuidado, as anotações vão virar uma zona. E elas geralmente viram, porque anotação não é código, é gambiarra.

Olha por exemplo como o Struts2 trabalha com anotações: http://struts.apache.org/2.x/docs/validation-annotation.html

G

AbelBueno:

Eu gosto do mapeamento em código para manter type-safe.
Se você usa strings acaba perdendo isso.

Mas também não tenho idéia de como resolver isso em java.

No VRaptor eles fazem umas mágicas com reflection e manipulação de bytecode. Se usassem a mesma técnica poderiam ter construções do tipo:

User userAttributes = createProxy(User.class); //... .field(userAttributes.getUsername(), DBTypes.STRING) .field(userAttributes.getBirthdate(), "bd", DBTypes.DATE)
O método createProxy retorna uma instância modificada, cujos getters quando chamados guardam em algum lugar o nome do atributo associado ao getter.
O método .field() (que teria o primeiro parâmetro alterado para Object, permitindo qualquer tipo) busca esse nome armazenado anteriormente pelo proxy.

Achei muito criativo, e elimina o problema de ter os nomes dos atributos como literal, sem verificação do compilador.

Aliás, a meu ver essa dificuldade de refatorar é o que falta para o esquema de configuração programática ficar perfeito.

A

gomesrod:

No VRaptor eles fazem umas mágicas com reflection e manipulação de bytecode. Se usassem a mesma técnica poderiam ter construções do tipo:

User userAttributes = createProxy(User.class); //... .field(userAttributes.getUsername(), DBTypes.STRING) .field(userAttributes.getBirthdate(), "bd", DBTypes.DATE)
O método createProxy retorna uma instância modificada, cujos getters quando chamados guardam em algum lugar o nome do atributo associado ao getter.
O método .field() (que teria o primeiro parâmetro alterado para Object, permitindo qualquer tipo) busca esse nome armazenado anteriormente pelo proxy.

Achei muito criativo, e elimina o problema de ter os nomes dos atributos como literal, sem verificação do compilador.

Aliás, a meu ver essa dificuldade de refatorar é o que falta para o esquema de configuração programática ficar perfeito.

Pois é, cheguei a pensar nisso.
Mas não acho que com proxy dinâmico você consiga alterar o tipo de retorno de um método (eu acho).

Daí, imagino, que o seu getUserName e getBirthdate teria que continuar retornando seus tipos originais.
Se você fosse adicionar meta informação ao método, teria que obter de outra forma.
Pelo menos é isso que eu acho.

S

Em Java, via reflection, vc pode pegar o nome de todas as propriedades de um bean e os seus tipos também. Não precisa alterar byte-code pra isso. O AutoBeanConfig faz exatamente isso => http://mentabean.soliveirajr.com/mtw/Page/Automapping/mentabean-auto-mapping

BeanConfig config = new AutoBeanConfig(User.class, "Users");
 
// agora sobre-escreva as propriedades que vc quer definir manualmente, pois a configuração padrão não lhe atende...        
 
config.pk("id", DBTypes.AUTOINCREMENT);
config.field("birthdate", "bd", DBTypes.DATE);
config.field("status", DBTypes.ENUMVALUE.from(User.Status.class));
config.field("deleted", DBTypes.BOOLEANINT);
config.field("insertTime", "insert_time", DBTypes.NOW_ON_INSERT_TIMESTAMP);

Alteração de byte-code é o que o hibernate faz para interceptar um getter e fazer o lazy loading automático, coisa que o MentaBean é totalmente contra. (ver a parte lazy loading no final dessa página => http://mentabean.soliveirajr.com/mtw/Page/OneToOne/mentabean-onehasone-relationships)

Tb não dá para refatorar strings dentro de XML ou Anotações. Vai chegar uma hora que vc vai ter que usar Strings, não tem jeito. Por exemplo: se o nome da propriedade do bean for diferente do nome da coluna no banco, vc vai ter que chapar o nome da coluna no banco somewhere.

A

AbelBueno:
gomesrod:

No VRaptor eles fazem umas mágicas com reflection e manipulação de bytecode. Se usassem a mesma técnica poderiam ter construções do tipo:

User userAttributes = createProxy(User.class); //... .field(userAttributes.getUsername(), DBTypes.STRING) .field(userAttributes.getBirthdate(), "bd", DBTypes.DATE)
O método createProxy retorna uma instância modificada, cujos getters quando chamados guardam em algum lugar o nome do atributo associado ao getter.
O método .field() (que teria o primeiro parâmetro alterado para Object, permitindo qualquer tipo) busca esse nome armazenado anteriormente pelo proxy.

Achei muito criativo, e elimina o problema de ter os nomes dos atributos como literal, sem verificação do compilador.

Aliás, a meu ver essa dificuldade de refatorar é o que falta para o esquema de configuração programática ficar perfeito.

Pois é, cheguei a pensar nisso.
Mas não acho que com proxy dinâmico você consiga alterar o tipo de retorno de um método (eu acho).

Daí, imagino, que o seu getUserName e getBirthdate teria que continuar retornando seus tipos originais.
Se você fosse adicionar meta informação ao método, teria que obter de outra forma.
Pelo menos é isso que eu acho.

O que você pode é receber Object no método .field() e lá de dentro pegar o método chamado (semelhante ao que o EasyMock faz pra configurar os mocks). O problema dessa solução é que ela faz você expor atributos que podem ser desnecessários pois precisaria chamar o getter.

Isso seria algo para facilitar o refactoring, mas, como a configuração é centralizada, refatorar a classe de configuração após a troca da propriedade seria o menor dos problemas. Dependendo do caso, teríamos que procurar nas páginas referências à propriedade também (caso o IDE não faça a refatoração nelas).

S

O Érico levantou essa bola, por isso adicionamos o método remove(propName) no AutoBeanConfig pois ele inicialmente vai ter que mapear todas as propriedades. Aí depois se vc quiser excluir alguma propriedade que não deve ir para o banco vc usa esse método.

A

saoj:
Ataxexe:

O problema dessa solução é que ela faz você expor atributos que podem ser desnecessários pois precisaria chamar o getter.

O Érico levantou essa bola, por isso adicionamos o método remove(propName) no AutoBeanConfig pois ele inicialmente vai ter que mapear todas as propriedades. Aí depois se vc quiser excluir alguma propriedade que não deve ir para o banco vc usa esse método.

Na verdade eu me referia à solução de proxy para configuração dos mapeamentos :slight_smile:

Se você tiver que mapear as coisas com .field(entity.getSeiLaOQue()) vai precisar criar getters pra coisas que não deveriam ser expostas, por isso sou mais a favor de configurar na mão mesmo. É sempre bom prover automatizações, mas acho que nunca se deve tirar o desenvolvedor do controle da situação nesses casos.

Ah! E meus parabéns pelo projeto. Conheço a família Menta há um tempo e acho muito legal a filosofia de configuração programática. Também detesto XML e não curto anotações de infraestrutura, acho que o domínio da aplicação deveria conter anotações de domínio e não de infra.

S

Não entendi o que um proxy vai ajudar para configuração, uma vez que via reflection é possível pegar o nome e o tipo de todas as propriedades. Pensando melhor acho que entendi:

O AutoBeanConfig faz o mapeamento automático de forma implícita (obviamente vai pegar o refactory dos nomes das propriedades mas não para os campos que vc configurou na mão / sobreescreveu). E o proxy sugerido faz o mapeamento não-automático de forma explícita com suporte a refactoring do nome das propriedades. Tem seu uso sim, pois eu muitas vezes prefiro VER o que está sendo configurado do que confiar que foi configurado automaticamente. O único drawback que eu vejo é quanto tivermos beans com 100 propriedades, terão que haver 100 linhas para configurá-las via o proxy e com o AutoBeanConfig apenas uma + as propriedades que vc quer sobre-escrever.

Sim! Eu só usuaria o mapeamento automático para um bean com muitas propriedades e eu fiquei com preguiça de escrever um caminhão de linhas para configurar as Strings e Integers que são triviais e tem o mesmo nome no banco de dados.

E

saoj:

Olha por exemplo como o Struts2 trabalha com anotações: http://struts.apache.org/2.x/docs/validation-annotation.html

Impressionante como tem casos desse tipo onde Annotation “é o que há”…

A

Neste ponto tenho que concordar contigo.

Na prática, não vejo tantos casos de atributos persistidos sem um get, mas não é papel do framework limitar isso.

Só ainda não entendi como um proxy atuaria ali, como você comentou no caso do EasyMock.
Tem uma idéia de como seria o código do método field() para isso?

G

AbelBueno:

Mas não acho que com proxy dinâmico você consiga alterar o tipo de retorno de um método (eu acho).
Daí, imagino, que o seu getUserName e getBirthdate teria que continuar retornando seus tipos originais.
Se você fosse adicionar meta informação ao método, teria que obter de outra forma.
Pelo menos é isso que eu acho.

Acho que não expliquei bem o que eu estava querendo mostrar.
A manipulação de bytecode não é para mudar o tipo de retorno, nem para fazer operações comuns de Reflection como descobrir atributos de uma classe.

Ela serve para criar um proxy dinâmico do objeto User, sobrescrevendo os métodos get. (pois a API de proxy padrão do Java só permite implementar interfaces, não sobrescrever classes!)
Vou colocar mais em detalhe o que aconteceria:

  • Ao executar a chamada .field([color=red]userAttributes.getUsername()[/color], DBTypes.STRING) , o método userAttributes.getUsername() será avaliado antes do .field(), ok?
  • A implementação do proxy , ao receber a chamada, vai calcular o nome do atributo (usando o nome do próprio método invocado) e guardar em algum lugar (pensando em um exemplo bem porco, seria uma variável static, mas claro que não estou sugerindo fazer assim… é só para ficar mais simples de entender, imagine que não existem situações de concorrência).
  • Em seguida o .field() é invocado, e pega o valor nessa variável temporária. O valor do parâmetro em si é ignorado.
  • E assim tudo se encaixa, a cada chamada encadeada de .field() e userAttributes.getXXX() os nomes dos campos vão sendo atribuídos.
  • O tipo retornado por getUsername() não importa, porque ele usou outra maneira para passar a informação que interessas; O método field() pode receber Object nesse parâmetro apenas para permitir que se passe qualquer coisa para ele.

Isso é o que eu inferi vendo o funcionamento do VRaptor (e suas dependências, muitas são libs para manipular bytecode e criar proxy de classes), se tiver algum desenvolvedor do framework por aqui pode corrigir se eu disser alguma besteira.


Mas o que eu quero é tirar TODOS esses atributos identificados por nome, e usar coisas mais amigáveis para refatoração. Inclusive porque refatoração é o grande ponto forte da configuração programática.

É disso mesmo que eu estava falando, mas de uma forma mais suave: no Hibernate você trabalha com objetos proxy o tempo todo, já nesse caso eles estariam em ação apenas durante a configuração.

Ora, vamos lá, você não quer ser como eles, quer? 8)

A

Neste ponto tenho que concordar contigo.

Na prática, não vejo tantos casos de atributos persistidos sem um get, mas não é papel do framework limitar isso.

Só ainda não entendi como um proxy atuaria ali, como você comentou no caso do EasyMock.
Tem uma idéia de como seria o código do método field() para isso?

Seria uma abordagem parecida com a do Mock de Annotation que criei:

https://github.com/ataxexe/trugger/blob/master/src/main/java/org/atatec/trugger/util/mock/AnnotationMock.java

Resource resource = new AnnotationMock&lt;Resource&gt;(){{ map("name").to(annotation.name()); map(false).to(annotation.shareable()); }}.mock();

O método annotation.name() é de um proxy que vai colocar o método Resource#name como último método chamado para o AnnotationMock. Quando o método “to” for chamado, o método “name” já populou aquele valor e “to” saberá que “name” se refere ao último método chamado. (Não sei se ficou claro…acho que me embananei pra explicar.)

Analogamente, o método field já saberia que o método getSeiLaOQue foi chamado e usaria o nome da propriedade em questão para mapear o atributo.

No EasyMock, quando queremos simular comportamento de métodos void, usamos, em vez do “expect”, “expectLastCall”, já que não podemos alterar o retorno do método void. No caso do MentaBean isso não seria necessário (nem do meu mock de Annotations).

S

Entendi e é uma sacada legal mesmo. É o tipo de coisa que só dá para fazer com configuração programática. :slight_smile:

Mas ficou uma dúvida: o método userAttributes.getAge() retorna um int no compile time. Isso porque o userAttributes é do tipo User, certo? Como de repente esse cara vai me retornar um String no run-time? É aí que entra a magia negra da alteração de byte-code ou eu não entendi alguma coisa?

A

Na verdade não rs…
Mas pela explicação do gomesrod já tinha entendido.

Queria entender o que eles faziam com o retorno do método em si, mas realmente é jogado fora.
Era isso que tava dizendo sobre não consegui aproveitar o retorno assim.

Era minha dúvida…dá uma olhada na explicação do gomesrod

A
saoj:
Ataxexe:
...

Entendi e é uma sacada legal mesmo. É o tipo de coisa que só dá para fazer com configuração programática. :)

Mas ficou uma dúvida: o método userAttributes.getAge() retorna um int no compile time. Isso porque o userAttributes é do tipo User, certo? Como de repente esse cara vai me retornar um String no run-time? É aí que entra a magia negra da alteração de byte-code ou eu não entendi alguma coisa?

Só pra ilustrar, um trecho do método map:

public &lt;E&gt; Mapper&lt;E, T&gt; map(final E value) {
    return new Mapper&lt;E, T&gt;() {

      public org.atatec.trugger.util.mock.AnnotationMock&lt;T&gt; to(E expected) {
        mappings.put(lastCall, value);
        return org.atatec.trugger.util.mock.AnnotationMock.this;
      }
    };
  }

Repare que eu nem me importo com o valor de expected, ele é usado aqui simplesmente por comodidade, assim a chamada ao método do proxy pode ficar na mesma linha da configuração.

S
Ataxexe:
public &lt;E&gt; Mapper&lt;E, T&gt; map(final E value) {
    return new Mapper&lt;E, T&gt;() {

      public org.atatec.trugger.util.mock.AnnotationMock&lt;T&gt; to(E expected) {
        mappings.put(lastCall, value);
        return org.atatec.trugger.util.mock.AnnotationMock.this;
      }
    };
  }

Entendi ainda não. :)

O próprio gomesrod não explicou isso:

O tipo retornado por getUsername() não importa, porque ele usou outra maneira para passar a informação que interessas;

Qual é essa outra maneira? :)

O meu método é assim:

public int getAge() {
    return age;
}

O que eu faço para ele retornar Object no runtime?

Nunca trabalhei com alteração de bytecode e cglib, logo não tenho idéia.

A
saoj:
Ataxexe:
public &lt;E&gt; Mapper&lt;E, T&gt; map(final E value) {
    return new Mapper&lt;E, T&gt;() {

      public org.atatec.trugger.util.mock.AnnotationMock&lt;T&gt; to(E expected) {
        mappings.put(lastCall, value);
        return org.atatec.trugger.util.mock.AnnotationMock.this;
      }
    };
  }

Entendi ainda não. :)

O próprio gomesrod não explicou isso:

O tipo retornado por getUsername() não importa, porque ele usou outra maneira para passar a informação que interessas;

Qual é essa outra maneira? :)

O meu método é assim:

public int getAge() {
    return age;
}

O que eu faço para ele retornar Object no runtime?

Nunca trabalhei com alteração de bytecode e cglib, logo não tenho idéia.

Entendi o que você quis dizer.

Nesse caso, o Java faz Autoboxing de int pra Integer, que pode ser tratado como um Object.

O fluxo é mais ou menos assim:

map(false).to(annotation.shareable())

- Aqui a variável lasCall é preenchida pelo proxy (annotation aqui é um Proxy) com o método Resource#shareable (esta é a outra maneira :) )

private class AnnotationMockInterception implements Interception {

    @Override
    public Object intercept(InterceptionContext context) throws Throwable {
      Method method = context.method();
      String name = method.getName();
      if (!mocked) {
        lastCall = name;
      }
      return mappings.containsKey(name) ? mappings.get(name) : context.nullReturn();
    }

  }

map(false).to(annotation.shareable())

nesse ponto, a variável lasCall está preenchida, por causa disso, pode-se colocar o valor "false" no mapeamento. Repare que não usamos o "retorno" do método shareable, pois esse método só é chamado para que o proxy popule a variável lastCall. A propriedade shareable é um boolean, por causa disso, o Java faz a conversão para Boolean, assim ele pode ir como um Object para o método to sem problemas.

Era isso?

S

Acho que não, pois tem que retornar String, ou um Object com o toString() sobrescrito para te informar o nome do atributo.

Se fosse: Integer getAge() vc poderia retornar um Integer com o toString() hackeado.

Mas como é int getAge() como vc vai retornar um Object aí visto que esse método retorna um primitivo? Autoboxing aqui não influi porque ele só acontece depois do primitivo ser retornado e não antes.

No meu entendimento se um método retorna primitivo, ele tem que retornar primitivo, a não ser que houve algum byte-code alteration sinistro.

A
saoj:
Ataxexe:
Era isso?

Acho que não, pois tem que retornar String, ou um Object com o toString() sobrescrito para te informar o nome do atributo.

Se fosse: Integer getAge() vc poderia retornar um Integer com o toString() hackeado.

Mas como é int getAge() como vc vai retornar um Object aí visto que esse método retorna um primitivo? Autoboxing aqui não influi porque ele só acontece depois do primitivo ser retornado e não antes.

Bom, vamos por partes:

Acho que não, pois tem que retornar String, ou um Object com o toString() sobrescrito para te informar o nome do atributo.

A String que irá informar o nome do atributo será informada pelo proxy em cima da entidade. Quando a chamada ao getAge for feita, o proxy verá que foi o método getAge, logo, a propriedade será "age", e colocará "age" em uma variável que o método "field" irá usar.

Como o getAge será chamado antes de field, no método field teremos como obter o valor "age", que foi atribuído pelo proxy.

Se fosse: Integer getAge() vc poderia retornar um Integer com o toString() hackeado.

Não será necessário, pois não faremos nada com o retorno de getAge, ele é usado apenas para sinalizar ao proxy que deve popular a variável de propriedade com o valor "age".

Mas como é int getAge() como vc vai retornar um Object aí visto que esse método retorna um primitivo? Autoboxing aqui não influi porque ele só acontece depois do primitivo ser retornado e não antes.

Um exemplo:

public static Object foo(Object o) {
    return 1;
  }
  
  public static void bar() {
    foo(1);
    foo(false);
  }

Isso compila normalmente (dentro de uma classe, claro). O Autoboxing é feito antes da chamada ao foo e antes do retorno de foo.

A

De forma bem simplista, quando criar um proxy dinâmico, terá nele um método assim:

public Object novoMetodo(Method metodoOriginal, Object objetoOriginal ) {
        
        String nomeDoMetodo = metodoOriginal.getName();
        
        armazenaNomeParaUsarDepois(nomeDoMetodo);
                                
        return metodoOriginal.invoke(objetoOriginal);
        
    }

Este novoMetodo intercepta todas chamadas originais, getAge, getBirthday, etc.

Como recebe um objeto Method, você consegue meta informação dos métodos: nome, tipo de retorno, etc.

No exemplo, estou chamado armazenaNomeParaUsarDepois para trabalhar com o nome do método depois.
Ele executa o método original no retorno, mas no caso do mapeamento ele poderia simplesmente retornar null e ignorar o original.

S

Dei uma googlada e parece que o method interceptor do CGLIB sempre retorna Object. Então um método que no compile retorna int not runtime vai retornar Integer. Se a variável ou parâmetro que está recebendo ele for um int, autoboxing acontece, se for um Object, então viola: um método que antes retornava um primitivo passou a retornar um objeto.

EDIT: Mas mesmo assim ainda fico com a pulga atrás da orelha se esse esquema de proxy não vai fazer o unboxing ANTES de retornar o objeto, para garantir o contrato do método com o retorno primitivo. Um método que retorna primitivo de repente retornar Objeto é algo legal mas assustador também.

A

Não só ele como todos os outros também. Como a assinatura do proxy precisa ser a mais genérica possível, isso só acontecerá se retornarmos Object, inclusive para os métodos void, que “retornam” null no proxy.

S

Não só ele como todos os outros também. Como a assinatura do proxy precisa ser a mais genérica possível, isso só acontecerá se retornarmos Object, inclusive para os métodos void, que “retornam” null no proxy.

Entendi. O interessante é que o objeto chega no cliente ao invés de ser convertido (unboxing por exemplo) no meio do caminho para que o cliente receba o retorno esperado no contrato (primitivo por exemplo) e não algo diferente. Acho que aí escolheram flexibilidade sobre segurança. Poderia haver algo entre o proxy e o cliente para garantir que o contrato do tipo fosse respeitado.

A

saoj:
EDIT: Mas mesmo assim ainda fico com a pulga atrás da orelha se esse esquema de proxy não vai fazer o unboxing ANTES de retornar o objeto, para garantir o contrato do método com o retorno primitivo. Um método que retorna primitivo de repente retornar Objeto é algo legal mas assustador também.

Se você mandar algo que não possa ser convertido dá um ClassCastException, ou um NullPointerException caso mande null em tipos primitivos. É uma lambança que só, mas é culpa dos tipos primitivos :slight_smile:

Essas exceptions são meio que o recurso da JVM pra indicar isso, mas colocar algo no proxy seria interessante mesmo. Dá pra implementar mole mole, acho que vou fazer uns testes com isso depois.

S

Ataxexe:
saoj:
EDIT: Mas mesmo assim ainda fico com a pulga atrás da orelha se esse esquema de proxy não vai fazer o unboxing ANTES de retornar o objeto, para garantir o contrato do método com o retorno primitivo. Um método que retorna primitivo de repente retornar Objeto é algo legal mas assustador também.

Se você mandar algo que não possa ser convertido dá um ClassCastException, ou um NullPointerException caso mande null em tipos primitivos. É uma lambança que só, mas é culpa dos tipos primitivos :slight_smile:

Essas exceptions são meio que o recurso da JVM pra indicar isso, mas colocar algo no proxy seria interessante mesmo. Dá pra implementar mole mole, acho que vou fazer uns testes com isso depois.

Digo que a JVM não deveria permitir isso. Um proxy pode fazer o que quiser mas na hora de retornar o valor ele teria que honrar o retorno do método original. Para os primitivos bastaria dar um unboxing ou jogar uma exception caso não fosse um wrapper (Integer, Long, etc.). Entendo que ele tem que retornar um Objeto que é genérico, mas antes dele bater na variável ou no parâmetro que o espera a JVM “enforçaria” o tipo esperado no contrato.

O que vc está me falando é que a JVM não faz isso então eu posso esperar um int e receber um Integer. :shock:

Assim eu retornaria um Integer com o toString() alterado para me retornar o nome da propriedade.

Se chegasse pra mim um int como o contrato determina eu não poderia fazer nada…

E pelo que entendi a coisa é ainda pior:

Eu posso esperar um int e receber uma String. :shock:

Acho que o pessoal assume que se o cara for fazer byte-code manipulation então tudo é possível. Acho que faz sentido… graças a isso essa sacada aí é possível.

E

Resumindo… teremos que utilizar a orientação a aspectos para usarmos as vantagens da refatoração? :shock:

G

Só um detalhezinho que não está deixando ter a visão correta: O nome da propriedade não vem do retorno do método get no proxy, na verdade o retorno desse método é totalmente ignorado!

Seguindo com essa pseudo-implementação, que ficou muito boa:

AbelBueno:
public Object novoMetodo(Method metodoOriginal, Object objetoOriginal ) { String nomeDoMetodo = metodoOriginal.getName(); armazenaNomeParaUsarDepois(nomeDoMetodo); return metodoOriginal.invoke(objetoOriginal); }

O método armazenaNomeParaUsarDepois(nomeDoMetodo) vai guardar esse nome em algum lugar (por exemplo, em uma variavel estática ou outros exemplos que vc vai ver no link abaixo). O retorno pode ser qualquer coisa, desde que não quebre na hora da conversão para o tipo esperado na assinatura do método original.

Aí seu método .field() ficaria mais ou menos assim:

public BeanConfig field(Object property, DBTypes type) {
       String lastCalledGetter = buscaNoMesmoLugarQueOMetodoAnteriorColocou();
       // O primeiro parametro é ignorado! A informação vem daquele lugar usado no método armazenaNomeParaUsarDepois() do proxy

       // ... faz o que mais tiver que fazer para configurar esse campo....
       // ...

       return this;
}

A conclusão é que isso:

config.field(userAttributes.getName(), DBTypes.STRING);

é apenas um atalho para isso:

userAttributes.getName(); // Guarda o nome do método
config.field(null, DBTypes.STRING); // usa o nome do método.

Agora para realmente acrescentar uma novidade: aqui tem um exemplo completo de implementação desse tipo de estratégia:

S

Ficou claro agora, obrigado gomesrod.

O valor não vem do retorno do método. Vem de outro lugar.

S

Quem tiver curiosidade para ver como isso ficou implementado no MentaBean pode dar uma olhada aqui:

http://mentabean.soliveirajr.com/mtw/Page/ProxyMapping/pt/mentabean-mapeamento-via-proxy

Utilizei o Javassist para fazer isso:

PropertiesProxy.java
BeanConfig.java

E

Fica interessante com proxy mesmo, mas alguém teria alguma ideia de como ficariam as nested properties? Pois isso aqui:

... userProxy.getAddress().getId();
irá estourar uma NPE…

G

saoj:
Quem tiver curiosidade para ver como isso ficou implementado no MentaBean pode dar uma olhada aqui:

http://mentabean.soliveirajr.com/mtw/Page/ProxyMapping/pt/mentabean-mapeamento-via-proxy


Caraca, não é que o cara fez mesmo? :smiley:

Show de bola!

S

erico_kl:
Fica interessante com proxy mesmo, mas alguém teria alguma ideia de como ficariam as nested properties? Pois isso aqui:

... userProxy.getAddress().getId();
irá estourar uma NPE…

Conseguimos fazer o proxy suportar nested proxies para nested properties. Agora ao invés de:

.field("user.id", "user_id", DBTypes.INTEGER)

Temos:

.field(postProps.getUser().getId(), "user_id", DBTypes.INTEGER)
J

O Hibernate para Java é deficiente por não ter uma maneira programática de mapeamento, só por gambiarra ou XML. Por isso esse MentaBean tem seu valor, mas o ideal mesmo seria o Hibernate do Java ter como mapear separadamente via código.

Criado 13 de novembro de 2012
Ultima resposta 24 de dez. de 2012
Respostas 42
Participantes 8