Como eu ja comentei em outro post o Guice é um framework de injeção de dependências muito fácil de utilizar.
Depois de utilizar o Guice por algum tempo, comecei também a utilizar alguns recursos de AOP do Guice, e como é muito fácil de utilizar, acho que alguns de vocês podem aproveitar isto (se é que alguem ainda lê este blog cheio de poeira
).
Para quem não sabe o que é AOP segue uma rápida introdução:
AOP = Aspect Oriented Programming ou Programação Orientada a Aspectos
Aspectos são uma forma de de isolar partes de um problema e reutilizar as soluções deste problema em diversos pontos do software que tenham o mesmo problema.
Um exemplo disto, o exemplo mais batido depois do logging, é o controle de transações …
Em todo o código de negócio na sua aplicação deve existir inicio e fim de transações, o que na verdade é uma dependência da regra de negócio mas não faz parte de verdade da regra de negócio sendo implementada.
Então este é um bom candidato a ser implementado como um aspecto, da mesma forma que é feito no EJB3 por exemplo, que é remover o código de controle de transações de dentro do código de negócios, e adicionar um marcador no código informando como se deseja que a transação se comporte para aquele trecho.
Para ficar mais claro de uma olhada neste exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package example; import java.math.BigDecimal; public class CodigoDeNegociosImpl { private InterfaceParaOutroServico outroServico; private ControleDeTransacoes transacao; public void metodoDeNegocios(String parametro1, int parametro2, BigDecimal parametro3) { transacao.inicia(); try { algumMetodo(parametro1); outroMetodo(parametro2); agoraUmCalculo(parametro3); transacao.commita(); } catch (Exception e) { transacao.fazRollback(); } } private void algumMetodo(String parametro1) { outroServico.metodoDeNegocio(parametro1); } private void outroMetodo(int parametro2) { // implementa regra de negocio } private void agoraUmCalculo(BigDecimal parametro3) { // faz um calculo complicado } } |
Este é um código bem simples, onde dentro do “metodoDeNegocios” existe código para iniciar e finalizar transações.
O código esta bem simplificado, mas imagine uma situação um pouco mais complicada, onde por exemplo, dentro de “outroServico.metodoDeNegocio” também fosse necessário participar da mesma transação, mas apenas se a transação ja estivesse iniciada, caso contrario seria necessário criar uma nova transação, o código começaria a ficar cada vez mais complicado, e este é um código que não faz parte da regra de negócios sendo implementada por estes métodos. Agora imagine se fosse possível resolver estes problemas simplesmente alterando o código para o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package example; import java.math.BigDecimal; import com.google.inject.Inject; public class CodigoDeNegociosImpl implements CodigoDeNegocios { @Inject private InterfaceParaOutroServico outroServico; /* (non-Javadoc) * @see example.CodigoDeNegocios#metodoDeNegocios(java.lang.String, int, java.math.BigDecimal) */ @PrecisaDeTransacao public void metodoDeNegocios(String parametro1, int parametro2, BigDecimal parametro3) { algumMetodo(parametro1); outroMetodo(parametro2); agoraUmCalculo(parametro3); } private void algumMetodo(String parametro1) { outroServico.metodoDeNegocios(parametro1); } private void outroMetodo(int parametro2) { // implementa regra de negocio } private void agoraUmCalculo(BigDecimal parametro3) { // faz um calculo complicado } } |
Não seria bem mais simples? (Antes que alguem pergunte, eu acabei de criar a anotação @PrecisaDeTransacao)
Podemos também imaginar outro problema. A aplicação tem um requisito que diz que o tempo total de cada chamada ao “metodoDeNegocio” deve ser logado, e também devem ser logadas todas as chamadas a “outroServico.metodoDeNegocio” informando o tempo total de execução do método.
A forma padrão de fazer isto é em cada um dos métodos que precisa do tempo logado, adicionar código no inicio e no final do método para fazer este registro, mas utilizando AOP é possível fazer sem tocar no código das classes.
Na AOP este logging e o controle de transações, são chamados de “preocupações transversais da aplicação”, ou seja, são aspectos da aplicação que permeam o código de diversas classes, mas não pertencem realmente as partes do código “sujas” por estes “aspectos”.
Ninguem vai programar puramente orientado a aspectos, mas AOP é uma excelente forma de contornar limitações de linguagens burocraticas demais, como o Java.
OK, mas o que o Guice tem a ver com isto?
Bom, o Guice possibilita a implementação de recursos simples de AOP utilizando a criação dinâmica de proxies para as classes gerenciadas por ele.
Acha que eu falei grego? Bom, vamos fazer um exemplo simples então.
Todos vocês sabem que anotações não fazem absolutamente nada, certo? Anotações são apenas metadados, ou dados extras que decoram o código com mais informações.
Então, vamos aproveitar os dados extras adicionados no exemplo anterior, e vamos implementar o controle de transações utilizando AOP e o Guice.
Primeiro vamos criar um modulo com a configuração básica da aplicação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package example.guice; import com.google.inject.AbstractModule; import example.CodigoDeNegocios; import example.CodigoDeNegociosImpl; import example.ControleDeTransacoes; import example.InterfaceParaOutroServico; import example.impl.ExemploDeTransacoes; import example.impl.OutroServicoComRegrasDeNegocio; public class BasicAppModule extends AbstractModule { @Override protected void configure() { bind(ControleDeTransacoes.class).to(ExemploDeTransacoes.class); bind(InterfaceParaOutroServico.class).to(OutroServicoComRegrasDeNegocio.class); bind(CodigoDeNegocios.class).to(CodigoDeNegociosImpl.class); } } |
As classes “ExemploDeTransacoes” e “OutroServicoComRegrasDeNegocio” estão em branco, apenas implementam as interfaces correspondentes sem nenhum código nos métodos.
Até aqui não temos nenhuma novidade, código Guice padrão.
Mas vamos agora criar um outro modulo apenas para a configuração de AOP para deixar tudo bem separado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package example.guice; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import com.google.inject.AbstractModule; import com.google.inject.matcher.Matcher; import com.google.inject.matcher.Matchers; import example.PrecisaDeTransacao; import example.aop.LoggingInterceptor; import example.aop.TransactionInterceptor; public class AOPConfigModule extends AbstractModule { @Override protected void configure() { MethodInterceptor transactionInterceptor = new TransactionInterceptor(); bindInterceptor(Matchers.any(), Matchers.annotatedWith(PrecisaDeTransacao.class), transactionInterceptor); MethodInterceptor loggingInterceptor = new LoggingInterceptor(); Matcher<? super Method> machMethodsByNameMatcher = new MatchMethodsByNameMatcher("metodoDeNegocio"); bindInterceptor(Matchers.any(), machMethodsByNameMatcher, loggingInterceptor); } } |
Agora explicando um pouco o código deste modulo.
Estamos utilizando o metodo “bindInterceptor” que configura o Guice para criar um proxy das classes e métodos que fecharem com os dois matchers passados (o primeiro para classes e o segundo para metodos).
Os matchers são como filtros, eles servem para informar ao Guice onde devem ser aplicados os interceptors.
A classe com.google.inject.matcher.Matchers do Guice facilita bastante a vida provendo diversos matchers prontos, mas nenhum deles leva em consideração o nome do metodo como queriamos fazer para o logging, então eu criei um matcher novo, vejam o código a baixo e verifiquem que é bem simples.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package example.guice; import java.lang.reflect.Method; import com.google.inject.matcher.AbstractMatcher; public class MatchMethodsByNameMatcher extends AbstractMatcher<Method> { private String methodName; @Override public boolean matches(Method method) { return methodName.equals(method.getName()); } public MatchMethodsByNameMatcher(String methodName) { super(); this.methodName = methodName; } } |
O único metodo que precisa ser implementado é o “matches” que faz as comparações no objeto “Method” recebido e retorna true ou false.
Depois disto, a parte mais importante é a implementação do “MethodInterceptor”. Vamos dar uma olhada no código do interceptor de logging.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package example.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class LoggingInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { long start = System.currentTimeMillis(); try { return invocation.proceed(); } finally { long end = System.currentTimeMillis(); System.out.format("%s.%s - %d\n", invocation.getThis().getClass().getName(),invocation.getMethod().getName(),end-start); } } } |
O Guice utiliza as interfaces para aop da aopalliance, as mesmas que as primeiras versões do spring framework, então se você tem algum código AOP que parou de funcionar no spring, migre para o Guice ![]()
Brincadeiras a parte, as unicas interfaces que você precisa realmente conhecer são:
org.aopalliance.intercept.MethodInterceptor – interface que define apenas o método invoke, que sera chamado no lugar de qualquer método interceptado por esta classe.
Exatamente isto, o método invoke sera chamado no lugar do “metodoDeNegocios”.
O invoke recebe como parâmetro uma instância de org.aopalliance.intercept.MethodInvocation:
Que possui informações sobre a chamada atual, como pro exemplo os parâmetros que estão sendo passados para o método, o método que esta sendo chamado, em qual classe, em que instância da classe, …
E possui o método “proceed()” que chama o método real, neste caso o “metodoDeNegocios”.
O resto é só implementar a lógica do interceptor, neste caso capturar o tempo de execução do método, ou seja, o tempo de execução do “proceed()”.
Não vou implementar o interceptor de controle de transações por que não é este o objetivo do post, e alguem iria querer utilizar um exemplo simples em produção em algum momento
Agora só falta criar uma clase para inicializar a aplicação e chamar o método de negocios
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package example; import java.math.BigDecimal; import com.google.inject.Guice; import com.google.inject.Injector; import example.guice.AOPConfigModule; import example.guice.BasicAppModule; public class AOPTest { public static void main(String[] args) { Injector injector = Guice.createInjector(new BasicAppModule(),new AOPConfigModule()); CodigoDeNegocios obj = injector.getInstance(CodigoDeNegocios.class); obj.metodoDeNegocios("a", 3, new BigDecimal(200l)); } } |
Neste código não tem muita novidade para quem leu o primeiro post sobre o Guice.
O output deste código deve ser algo parecido com:
example.impl.OutroServicoComRegrasDeNegocio$$EnhancerByGuice$$eda06b1.metodoDeNegocios - 16 example.CodigoDeNegociosImpl$$EnhancerByGuice$$54b1222d.metodoDeNegocios - 16
Com isto podemos ver que o Guice altera as classes para poder implementar o AOP.
Para finalizar algumas considerações:
Acho que era isto por enquanto, como este não foi o post mais detalhado que ja escrevi até hoje, espero que tenham muitas perguntas, podem perguntar nos comentários que eu respondo o mais rápido possível.
Para quem ficar com preguiça de copiar e colar os códigos, ou estiver enfrentando algum problema por eu ter esquecido de colocar alguma coisa no post segue a Aplicação de exemplo Guice/AOP que desenvolvi enquanto escrevia o post.
Bom dia Urubatan. Muito legal o post.
Você sabe se o Guice utiliza a biblioteca javassist ( http://www.csg.is.titech.ac.jp/~chiba/javassist/ )? Eu não conhecia o Guice e pela leitura que eu fiz do seu post, entendi que ele faz uma especialização do uso do javassist para que o uso de AOP seja mais fácil, concorda?
Urubatan, eu baixei o Guice e não vi nenhuma dependência com o javassist. Pelo que vi, o Guice é apenas um framework parecido, com mais funcionalidades implementadas. A dúvida que tenho é se a criação de um proxy é mais rápida no Guice do que no Javassist… Vou pesquisar. Abraço.
Aleluia !!! até que enfim post novo !!! Brincadeira
Pergunta, valeria a pena usar no lugar do aop do
Spring?
Fábio, não cheguei a fazer testes de performance :
Sei que o javassist faz isto mas num nível mais baixo certo? Nunca cheguei a estudar muito ele.
gamarra, atualmente acho que na maioria dos casos o Spring é um canhão para matar uma mosca. a idéia de lightweight que fez o spring se expandir foi abandonada a bastante tempo …
Então acho sim que vale a pena utilizar o Guice em vez do spring.
Bom dia Urubatan. Obrigado pela resposta. O Javassist faz isto num nível mais baixo realmente. Achei o guice bem legal. Abraço e sucesso.
Bom dia Urubatan;
Eu comecei a trabalhar num protótipo de um sistema de integração utilizando o guice, estou muito satisfeito, até o primeiro momento…
Eu estou tendo dificuldade na hora de interceptar o método, la no interceptor eu consigo pegar os argumentos…
com oeu faço para alterar os argumentos passados
?
eu devo invocar novamente.. pasasndo o que eu preciso…(Só qeu assim gera um loop.. pq cada vez que eu chamo.. mesmo apartir do interceptor.. a chamada é interceptada novamente..)
ou devo alterar os argumentos de que forma eu faço isso?
vou te passar uma parte do código…
Nesse código o loop é infinito
ele nem chega no return…
System.out.println(this.getClass().getName());
Method method = (Method)invocation.getStaticPart();
method.invoke(invocation.getThis(), invocation.getArguments());
return invocation.proceed();
Desde ja agradeço ajuda abraço!
para alterar os parâmetros basta você alterar o array que é passado como último parâmetro apra o método “invoke”, no momento você só esta passando o getArguments que retorna os argumentos originais.
Ahh, mais uma coisa que percebi, você não deve chamar o método invoke do mesmo objeto que foi interceptado, pois isto vai fazer com que o método seja interceptado novamente.
ah tem um detalhe e se eu quizer alterar uma propriedade do objeto…
algo que não esta sendo passado como argumento.
eu ja tenti utilizar o inovation.getThis();
Eu não consigo alterar, pois não da pra fazer cast pois o guice altera a classe para suportar a aop.. como vc mesmo disse…
tem alguma ideia como eu posso fazer?
blz cara consegui aqui fazer o que eu queria…
Esse guice ta fazendo milagre.. muito bom prático.
Estou utilizando ele numa aplicação
e com ele eu consegui poupar muito trabalho.
Valew cara obrigado
Vc ja fez o Guice AOP funcionar com o Seam
Acho que o seam não tem a engine de DI plugável, então a integração entre o seam e o Guice não seria muito natural, mas fora esta integração não tem diferença entre usar só o Guice ou o Guice + Seam, claro que só os objetos criados pelo Guice seriam afetados pelo AOP do Guice.