Introdução ao cglib

Introdução ao cglib

1. Visão geral

Neste artigo, examinaremos a bibliotecacglib (Biblioteca de geração de código). É uma biblioteca de instrumentação de bytes usada em muitas estruturas Java, comoHibernate ouSpring. A instrumentação de bytecode permite manipular ou criar classes após a fase de compilação de um programa.

2. Dependência do Maven

Para usarcglib em seu projeto, basta adicionar uma dependência Maven (a versão mais recente pode ser encontradahere):


    cglib
    cglib
    3.2.4

3. Cglib

As classes em Java são carregadas dinamicamente no tempo de execução. Cglib está usando este recurso da linguagem Java para tornar possível adicionar novas classes a um programa Java já em execução.

Hibernate usa cglib para geração de proxies dinâmicos. Por exemplo, ele não retornará o objeto completo armazenado em um banco de dados, mas retornará uma versão instrumentada da classe armazenada que carrega preguiçosamente valores do banco de dados sob demanda.

Frameworks de simulação populares, comoMockito,, usamcglib para métodos de simulação. O mock é uma classe instrumentada em que os métodos são substituídos por implementações vazias.

Estaremos olhando para as construções mais úteis decglib.

4. Implementando proxy usandocglib

Digamos que temos uma classePersonService que possui dois métodos:

public class PersonService {
    public String sayHello(String name) {
        return "Hello " + name;
    }

    public Integer lengthOfName(String name) {
        return name.length();
    }
}

Observe que o primeiro método retornaStringe o segundoInteger.

4.1. Retornando o mesmo valor

Queremos criar uma classe de proxy simples que interceptará uma chamada para um métodosayHello(). A classeEnhancer nos permite criar um proxy estendendo dinamicamente uma classePersonService usando um métodosetSuperclass() da classeEnhancer:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((FixedValue) () -> "Hello Tom!");
PersonService proxy = (PersonService) enhancer.create();

String res = proxy.sayHello(null);

assertEquals("Hello Tom!", res);

OFixedValue é uma interface de retorno de chamada que simplesmente retorna o valor do método com proxy. A execução do métodosayHello() em um proxy retornou um valor especificado em um método de proxy.

4.2. Retornando valor dependendo de uma assinatura de método

A primeira versão do nosso proxy tem algumas desvantagens, porque não podemos decidir qual método um proxy deve interceptar e qual método deve ser chamado de uma superclasse. Podemos usar uma interfaceMethodInterceptor para interceptar todas as chamadas para o proxy e decidir se queremos fazer uma chamada específica ou executar um método de uma superclasse:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello Tom!";
    } else {
        return proxy.invokeSuper(obj, args);
    }
});

PersonService proxy = (PersonService) enhancer.create();

assertEquals("Hello Tom!", proxy.sayHello(null));
int lengthOfName = proxy.lengthOfName("Mary");

assertEquals(4, lengthOfName);

Neste exemplo, estamos interceptando todas as chamadas quando a assinatura do método não é da classeObject, ou seja, Os métodostoString() ouhashCode() não serão interceptados. Além disso, estamos interceptando apenas métodos de umPersonService que retorna umString. A chamada para um métodolengthOfName() não será interceptada porque seu tipo de retorno éInteger.

5. Bean Creator

Outra construção útil decglib é uma classeBeanGenerator. Permite criar dinamicamente beans e adicionar campos juntamente com os métodos setter e getter. Ele pode ser usado por ferramentas de geração de código para gerar objetos POJO simples:

BeanGenerator beanGenerator = new BeanGenerator();

beanGenerator.addProperty("name", String.class);
Object myBean = beanGenerator.create();
Method setter = myBean.getClass().getMethod("setName", String.class);
setter.invoke(myBean, "some string value set by a cglib");

Method getter = myBean.getClass().getMethod("getName");
assertEquals("some string value set by a cglib", getter.invoke(myBean));

6. Criando Mixin

Amixin é uma construção que permite combinar vários objetos em um. Podemos incluir um comportamento de duas classes e expô-lo como uma única classe ou interface. Os Mixinscglib permitem a combinação de vários objetos em um único objeto. No entanto, para fazer isso, todos os objetos incluídos em um mixin devem ser apoiados por interfaces.

Digamos que desejamos criar uma combinação de duas interfaces. Precisamos definir as interfaces e suas implementações:

public interface Interface1 {
    String first();
}

public interface Interface2 {
    String second();
}

public class Class1 implements Interface1 {
    @Override
    public String first() {
        return "first behaviour";
    }
}

public class Class2 implements Interface2 {
    @Override
    public String second() {
        return "second behaviour";
    }
}

Para compor as implementações deInterface1 eInterface2, precisamos criar uma interface que estenda ambos:

public interface MixinInterface extends Interface1, Interface2 { }

Usando um métodocreate() da classeMixin, podemos incluir os comportamentos deClass1eClass2 em umMixinInterface:

Mixin mixin = Mixin.create(
  new Class[]{ Interface1.class, Interface2.class, MixinInterface.class },
  new Object[]{ new Class1(), new Class2() }
);
MixinInterface mixinDelegate = (MixinInterface) mixin;

assertEquals("first behaviour", mixinDelegate.first());
assertEquals("second behaviour", mixinDelegate.second());

Chamar métodos emmixinDelegate invocará implementações deClass1eClass2.

7. Conclusão

Neste artigo, examinamoscglibe suas construções mais úteis. Criamos um proxy usando uma classeEnhancer. Usamos umBeanCreatore, por fim, criamos umMixin que incluía comportamentos de outras classes.

O Cglib é amplamente utilizado pela estrutura Spring. Um exemplo do uso de um proxy cglib pelo Spring é adicionar restrições de segurança às chamadas de método. Em vez de chamar um método diretamente, a segurança do Spring primeiro verifica (via proxy) se uma verificação de segurança especificada passa e delega para o método real apenas se essa verificação foi bem-sucedida. Neste artigo, vimos como criar esse proxy para nosso próprio objetivo.

A implementação de todos esses exemplos e trechos de código pode ser encontrada emGitHub project - este é um projeto Maven, portanto, deve ser fácil de importar e executar como está.