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, поэтому его должно быть легко импортировать и запускать как есть.