Utilizando a API CGLIB para interceptar chamadas de métodos em objetos Java

Por Carlos Eduardo Gusso Tosin | 25/01/2010 | Tecnologia

Colaboração: Carlos Tosin (instrutor oficial dos Cursos On-Line de Java da Softblue)

1. Introdução

Este artigo tem por objetivo mostrar como utilizar a API CGLIB para interceptar chamadas de métodos em objetos no Java. Se você estiver se perguntando “mas por que eu deveria interceptar a chamada de um método?”, pretendo fazer com que você entenda como isso pode aumentar a sua produtividade e reduzir os erros no código.

2. O CGLIB

O CGLIB (Code Generation Library) é uma API open source escrita em Java. A página oficial do projeto encontra-se nesse link: http://cglib.sourceforge.net. O CGLIB é capaz de gerar classes que implementam interfaces e/ou estendem classes da aplicação em tempo de execução. Fazendo isso, o CGLIB faz com que chamadas aos métodos da classe passem por um proxy antes que os métodos sejam efetivamente invocados no objeto desejado. Através da utilização desse mecanismo o CGLIB é capaz de interceptar chamadas de métodos de forma transparente ao desenvolvedor. Na próxima seção mostrarei um exemplo prático de funcionamento do CGLIB.

3. Um exemplo prático

Chegou a hora de vermos o CGLIB funcionando na prática. Você vai perceber que ele realmente é muito fácil de usar. A primeira coisa a ser feita é o download dos JARs necessários. Este artigo baseia-se na versão 2.2_beta1, que pode ser obtida em http://cglib.sourceforge.net. Além do JAR do CGLIB, é necessário também o JAR da API ASM, que pode ser obtido em http://forge.objectweb.org/projects/asm. A versão do ASM utilizada é a 2.2.3.

Depois de os JARs terem sido corretamente adicionados ao classpath da aplicação, vamos agora criar a nossa aplicação. A aplicação consiste em um método main que instancia um objeto que representa um serviço qualquer e invoca um método neste serviço. O CGLIB vai interceptar essa chamada, chamando um método designado pelo programador, antes de invocar o método no objeto de destino.

O primeiro passo é criarmos o nosso serviço. O código abaixo mostra a classe MyService:

public class MyService {
    public static MyService newInstance() {
        MyInterceptor c = new MyInterceptor();
        MyService instance = (MyService)Enhancer.create(MyService.class, c);
        return instance;
    }
    public void doSomething() {
        System.out.println("Método invocado");
    }
}

Como o CGLIB irá criar um proxy da classe MyService, esta classe obrigatoriamente deve possuir um construtor público sem argumentos (se você não declarar nenhum construtor, como foi o caso acima, o Java gera um construtor público sem argumentos para você). Vamos analisar o código acima. O método doSomething() é um método padrão da nossa classe, que representa a execução de alguma ação no nosso serviço. Nesse caso, tudo o que este método irá fazer será imprimir no console uma mensagem.

O método newInstance() é uma factory de objetos da classe MyService. Note que para que o CGLIB funcione, os objetos da classe MyService não podem ser instanciados através do operador new. A instanciação deve seguir algumas regras ditadas pelo CGLIB. Primeiro devemos criar um objeto da classe que contém o código que será executado quando a interceptação ocorrer. Nesse caso, esta classe é a MyInterceptor (ela será explicada na seqüência). Então usamos o método Enhancer.create() para criar o objeto da classe MyService e o retornamos. Uma alternativa ao Enhancer.create() que também pode ser utilizada é a seguinte:

public static MyService newInstance() {
    MyInterceptor callback = new MyInterceptor();
    Enhancer e = new Enhancer();
    e.setSuperclass(MyService.class);
    e.setCallback(callback);
    MyService instance = (MyService) e.create();
    return instance;
}

Vamos agora analisar a classe MyInterceptor. O código da classe é mostrado abaixo:

public class MyInterceptor implements MethodInterceptor {
    public Object intercept(Object object, Method method, Object[] args,
    MethodProxy methodProxy) throws Throwable {
        System.out.println("Executando método " + method.getName());

        Object r = methodProxy.invokeSuper(object, args);
        System.out.println("Método " + method.getName() + " executado");
        return r;
    }
}

Primeiramente, observe que esta classe deve implementar a interface MethodInterceptor do CGLIB e, por conseqüência, o método intercept(). Este método será chamado pelo CGLIB quando algum método da classe MyService for executado e os seguintes argumentos serão fornecidos:

Object object: o objeto que sofrerá a execução do método (neste caso a nossa instância de MyService).

Method method: o método que será chamado (nesse caso o método doSomething()).

Object[] args: os argumentos que serão passados ao método (nesse caso não serão passados argumentos, já que doSomething() não recebe argumentos).

MethodProxy methodProxy: proxy para o método a ser executado. Este objeto permite que você execute o método no objeto de destino. No nosso exemplo, o método intercept() mostra uma mensagem antes do método ser executado, executa o método e mostra uma mensagem após a execução do método. Para ver isso funcionando, vamos agora criar um método main() para podermos executar esta aplicação:

public class Main {
    public static void main(String[] args) {
        MyService service = MyService.newInstance();
        service.doSomething();
    }
}

O método main() é bem simples: apenas instancia MyService utilizando a factory que criamos anteriormente e invoca o método doSomething(). Executando esta aplicação, a saída será a seguinte:

Executando método doSomething
Método invocado
Método doSomething executado

Pronto, você acaba de ver o CGLIB em ação. O seu método foi interceptado de forma transparente. Está curioso pra saber como isso funciona? Na verdade é um mecanismo simples. Quando o Enhancer.create() é chamado, o CGLIB cria uma nova classe, em tempo de execução, que estende MyService. E é um objeto desta classe que é retornado pelo método newInstance(). Essa classe tem um nome meio esquisito do tipo MyService$EnhancerByCGLIB$e5fcc311@e32802. Internamente, ela sobrescreve o método doSomething() da classe MyService para que ele chame o método intercept() da classe MyInterceptor. Portanto, quando service.doSomething() é executado, na verdade quem é executado é o método doSomething() da classe gerada pelo CGLIB.

4. Por que interceptar chamadas de métodos?

Interceptar chamadas de métodos pode ser algo extremamente benéfico para suas aplicações. O exemplo mostrado nesse artigo foi bem simples, já que interceptamos o método apenas para mostrar duas mensagens. Mas a interceptação pode ser utilizada por vários outros motivos:

Segurança: você pode checar, antes da execução do método, se o método pode realmente ser executado.

Logging: você pode fazer log das chamadas dos métodos (quando foi chamado, quais foram os argumentos, quais foram os retornos, etc.).

Performance: você pode analisar quanto tempo os métodos levam para executar.

Filtro de dados: você pode alterar os parâmetros a serem passados para o método antes de executá-lo e alterar o retorno do método antes de retornar o objeto para quem invocou o método.

Acesso a banco de dados: você pode abrir e fechar conexões com um banco de dados, gerenciar transações, etc.

Estas são apenas algumas das utilidades da interceptação de métodos. Seu código vai ficar bem mais claro, já que não será mais necessário implementar os mesmos códigos de segurança, por exemplo, em todos os métodos. Além disso, vai economizar um trabalho considerável e vai fazer com que você gaste menos tempo programando coisas que não estão diretamente relacionadas com o propósito da sua aplicação.

Esta separação entre o código relativo ao negócio da tua aplicação do código que é apenas acessório também traz outra vantagem importante: se você tiver um mecanismo de factories implementado adequadamente, você será capaz de habilitar e desabilitar o logging, a segurança, o medidor de performance, etc. de forma independente e sem alterar qualquer linha de código. Para aplicações de médio e grande porte, isto pode economizar bastante trabalho e reduzir a chance de erros, o que vai aumentar a sua produtividade. Para mais informações a respeito dessa questão de habilitar e desabilitar esses recursos, recomendo que você busque informações sobre AOP (Aspect Oriented Programming). A base da programação orientada a aspectos é justamente esta: separar código inerente ao negócio do código que não está diretamente relacionado ao propósito da aplicação, como logging, segurança, etc.

5. Conclusão

Neste artigo, busquei mostrar como utilizar a API CGLIB para interceptar chamadas de método em objetos no Java. Através de um exemplo simples, procurei mostrar como isso funciona na prática e porque a utilização dessa prática pode ser interessante para o programador.

Carlos Tosin é instrutor oficial dos Cursos On-Line de Java da Softblue, formado em Ciência da Computação pela PUC-PR, pós-graduado em Desenvolvimento de Jogos para Computador pela Universidade Positivo e Mestre em Informática na área de Sistemas Distribuídos, também pela PUC-PR. Trabalha profissionalmente com Java há 7 anos e possui 4 anos de experiência no desenvolvimento de sistemas para a IBM dos Estados Unidos, utilizados a nível mundial. Atua há mais de 2 anos com cursos e treinamentos de profissionais em grandes empresas. Possui as certificações da Sun SCJP, SCJD, SCWCD, SCBCD, SCEA, IBM SOA e ITIL Foundation.