Перейти к основному содержимому

Введение в cglib

· 5 мин. чтения

1. Обзор

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

2. Зависимость от Maven

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

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>

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

Обратите внимание, что первый метод возвращает строку , а второй — целое число.

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() не будет перехвачен, поскольку его возвращаемый тип — целое число.

5. Создатель бобов

Еще одна полезная конструкция из cglib — это класс BeanGenerator . Это позволяет нам динамически создавать bean-компоненты и добавлять поля вместе с методами установки и получения. Он может использоваться инструментами генерации кода для создания простых объектов 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 сначала проверит (через прокси), проходит ли указанная проверка безопасности, и делегирует фактическому методу, только если эта проверка прошла успешно. В этой статье мы увидели, как создать такой прокси для своих целей.

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