Введение в cglib

Введение в cglib

1. обзор

В этой статье мы рассмотрим библиотекуcglib (Библиотека генерации кода). Это байтовая инструментальная библиотека, используемая во многих фреймворках Java, таких какHibernate илиSpring. Инструментарий байт-кода позволяет манипулировать или создавать классы после фазы компиляции программы.

2. Maven Dependency

Чтобы использоватьcglib в своем проекте, просто добавьте зависимость Maven (последнюю версию можно найтиhere):


    cglib
    cglib
    3.2.4

3. CGLIB

Классы в Java загружаются динамически во время выполнения. Cglib использует эту функцию языка Java, чтобы сделать возможным добавление новых классов в уже работающую программу Java.

Hibernate использует cglib для генерации динамических прокси. Например, он не вернет полный объект, хранящийся в базе данных, но вернет инструментальную версию хранимого класса, которая лениво загружает значения из базы данных по требованию.

Популярные фреймворки имитации, такие какMockito,, используютcglib для имитации методов. Макет - это инструментальный класс, в котором методы заменяются пустыми реализациями.

Мы рассмотрим наиболее полезные конструкции изcglib.

4. Реализация прокси с использованиемcglib

Допустим, у нас есть классPersonService с двумя методами:

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

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

Обратите внимание, что первый метод возвращаетString, а второйInteger.

4.1. Возврат того же значения

Мы хотим создать простой прокси-класс, который будет перехватывать вызов методаsayHello(). КлассEnhancer позволяет нам создавать прокси, динамически расширяя классPersonService, используя методsetSuperclass() из классаEnhancer:

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);

FixedValue - это интерфейс обратного вызова, который просто возвращает значение из проксированного метода. Выполнение методаsayHello() на прокси-сервере вернуло значение, указанное в методе прокси.

4.2. Возвращаемое значение в зависимости от подписи метода

Первая версия нашего прокси-сервера имеет некоторые недостатки, потому что мы не можем решить, какой метод прокси-сервер должен перехватывать, и какой метод должен вызываться из суперкласса. Мы можем использовать интерфейсMethodInterceptor, чтобы перехватить все вызовы прокси и решить, хотите ли вы сделать конкретный вызов или выполнить метод из суперкласса:

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);

В этом примере мы перехватываем все вызовы, когда подпись метода не принадлежит классуObject, что означает, что МетодыtoString() илиhashCode() не будут перехвачены. Кроме того, мы перехватываем только методы изPersonService, которые возвращаютString. Вызов методаlengthOfName() не будет перехвачен, потому что его возвращаемый тип -Integer.

5. Bean Creator

Еще одна полезная конструкция изcglib - это классBeanGenerator. Это позволяет нам динамически создавать bean-компоненты и добавлять поля вместе с методами set и getter. Он может использоваться инструментами генерации кода для генерации простых объектов POJO:

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. Создание миксина

mixin - это конструкция, позволяющая объединить несколько объектов в один. Мы можем включить поведение пары классов и представить это поведение как отдельный класс или интерфейс. Примесиcglib позволяют объединять несколько объектов в один. Однако для этого все объекты, включенные в миксин, должны быть поддержаны интерфейсами.

Допустим, мы хотим создать смесь двух интерфейсов. Нам нужно определить как интерфейсы, так и их реализации:

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";
    }
}

Чтобы составить реализацииInterface1 иInterface2, нам нужно создать интерфейс, расширяющий их обе:

public interface MixinInterface extends Interface1, Interface2 { }

Используя методcreate() из классаMixin, мы можем включить поведениеClass1 иClass2 вMixinInterface:

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());

Вызов методов наmixinDelegate вызовет реализации изClass1 иClass2.

7. Заключение

В этой статье мы рассмотрелиcglib и его наиболее полезные конструкции. Мы создали прокси, используя классEnhancer. Мы использовалиBeanCreator и, наконец, создалиMixin, который включал поведение других классов.

Cglib широко используется средой Spring. Одним из примеров использования прокси-сервера cglib в Spring является добавление ограничений безопасности к вызовам методов. Вместо непосредственного вызова метода Spring Security сначала проверит (через прокси-сервер), прошла ли указанная проверка безопасности, и делегирует действительному методу только, если эта проверка прошла успешно. В этой статье мы увидели, как создать такой прокси для наших собственных целей.

Реализация всех этих примеров и фрагментов кода можно найти вGitHub project - это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.