Въведение в cglib

1. Общ преглед

В тази статия ще разгледаме библиотеката cglib (библиотека за генериране на кодове). Това е байтова инструментарна библиотека, използвана в много Java рамки като Hibernate или Spring . Инструментацията на байт кода позволява манипулиране или създаване на класове след фазата на компилиране на програма.

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

За да използвате cglib във вашия проект, просто добавете зависимост на Maven (най-новата версия може да бъде намерена тук):

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

5. Bean Creator

Друга полезна конструкция от cglib е класът BeanGenerator . Позволява ни динамично да създаваме боб и да добавяме полета заедно с методите на setter и 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

А Mixin е конструкция, която позволява комбиниране на множество обекти в едно. Можем да включим поведение на няколко класа и да го изложим като отделен клас или интерфейс. На cglib Mixins позволяват комбинирането на няколко обекта в един обект. За целта обаче всички обекти, които са включени в миксин, трябва да бъдат подкрепени от интерфейси.

Да кажем, че искаме да създадем комбинация от два интерфейса. Трябва да дефинираме както интерфейсите, така и техните реализации:

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 - това е проект на Maven, така че трябва да е лесно да се импортира и да се изпълнява както е.