динамічний проксі

динамічний проксі

Трохи теорії ... Створюється проксі-клас з допомогою виклику методу Proxy.getProxyClass, який приймає клас-лоадер і масив інтерфейсів (interfaces), а повертає об'єкт класу java.lang.Class, який завантажений за допомогою переданого клас-лоадера і реалізує переданий масив інтерфейсів .

На передаються параметри є ряд обмежень:
  1. Всі об'єкти в масиві interfaces повинні бути інтерфейсами. Вони не можуть бути класами або примітивами.
  2. У масиві interfaces не може бути двох однакових об'єктів.
  3. Всі інтерфейси в масиві interfaces повинні бути завантажені тим клас-лоадером, який передається в метод getProxyClass.
  4. Все не публічні інтерфейси повинні бути визначені в одному і тому ж пакеті, інакше генерується проксі-клас не зможе їх все реалізувати.
  5. Ні в яких двох інтерфейсах не може бути методу з однаковою назвою та сигнатурою параметрів, але з різними типами, що повертається.
  6. Довжина масиву interfaces обмежена 65535-ю інтерфейсами. Ніякої Java-клас не може реалізовувати більш 65535 інтерфейсів (а так хотілося!).

Якщо будь-яка з перерахованих вище обмежень порушено - буде викинуто виключення IllegalArgumentException. а якщо масив інтерфейсів interfaces дорівнює null, то буде викинуто NullPointerException.

Властивості динамічного проксі-класу
  1. Проксі-клас є публічним, забезпечений модифікатором final і не є абстрактним.
  2. Ім'я проксі-класу по-замовчуванням не визначено, однак починається на $ Proxy. Весь простір імен, що починаються на $ Proxy зарезервовано для проксі-класів.
  3. Проксі-клас успадковується від java.lang.reflect.Proxy.
  4. Проксі-клас реалізує всі інтерфейси, передані при створенні, в порядку передачі.
  5. Якщо проксі-клас реалізує непублічний інтерфейс, то він буде згенеровано в тому пакеті, в якому визначено цей самий непублічна інтерфейс. У загальному випадку пакет, в якому буде згенеровано проксі-клас невизначений.
  6. Метод Proxy.isProxyClass повертає true для класів, створених за допомогою Proxy.getProxyClass і для класів об'єктів, створених за допомогою Proxy.newProxyInstance і false в іншому випадку. Даний метод використовується підсистемою безпеки Java і потрібно розуміти, що для класу, просто успадкованого від java.lang.reflect.Proxy він поверне false.
  7. java.security.ProtectionDomain для проксі-класу такий же, як і для системних класів, завантажених bootstrap-завантажувачем, наприклад - для java.lang.Object. Це логічно, тому що код проксі-класу створюється самою JVM і у неї немає причин собі не довіряти.
Примірник динамічного проксі-класу і його властивості

Конструктор проксі-класу приймає один аргумент - реалізацію інтерфейсу InvocationHandler. Відповідно, об'єкт проксі-класу можна створити за допомогою рефлексії, викликавши метод newInstance об'єкта класу Class. Однак, існує й інший спосіб - викликати метод Proxy.newProxyInstance. який приймає на вхід завантажувач класів, масив інтерфейсів, які буде реалізовувати проксі-клас, і об'єкт, який реалізує InvocationHandler. Фактично, даний метод комбінує отримання проксі-класу за допомогою Proxy.getProxyClass і створення екземпляра даного класу через рефлексію.

Властивості створеного екземпляра проксі-класу наступні:

  1. Об'єкт проксі-класу наводимо до всіх інтерфейсів, переданим в масиві interfaces. Якщо IDemo - один з переданих інтерфейсів, то операція proxy instanceof IDemo завжди поверне true. а операція (IDemo) proxy завершиться коректно.
  2. Статичний метод Proxy.getInvocationHandler повертає обробник викликів, переданий при створенні екземпляра проксі-класу. Якщо переданий в даний метод об'єкт не є екземпляром проксі-класу, то буде викинуто IllegalArgumentException виняток.
  3. Клас-обробник викликів реалізує інтерфейс InvocationHandler. в якому визначено метод invoke. має наступну сигнатуру:

public Object invoke (Object proxy. Method method. Object [] args) throws Throwable

Тут proxy - екземпляр проксі-класу, який може використовуватися при обробці виклику того або іншого методу. Другий параметр - method є екземпляром класу java.lang.reflect.Method. Значення даного параметра - один з методів, визначених в будь-якому з переданих при створенні проксі-класу інтерфейсів або їх супер-інтерфейсів. Третій параметр - масив значень аргументів методу. Аргументи примітивних типів будуть замінені примірниками своїх класів-обгорток, таких як java.lang.Boolean або java.lang.Integer. Конкретна реалізація методу invoke може змінювати даний масив.

Значення, що повертається методом invoke повинно мати тип, сумісний з типом значення, що повертається інтерфейсним методом, для якого викликається дана обгортка. Зокрема, якщо інтерфейсний метод повертає значення примітивного типу - необхідно повернути екземпляр класу-обгортки даного примітивного типу. Якщо повертається null, а очікується значення примітивного типу, - буде викинуто NullPointerException. У разі непрімітівних типів, клас повертається методу invoke повинен бути наводимо до класу повертається интерфейсного методу, інакше буде викинуто ClassCastException.

Усередині методу invoke повинні кидатися тільки ті перевіряються винятку, які визначені в сигнатурі викликається интерфейсного методу або приводяться до них. Крім цих типів виключень дозволяється кидати тільки непроверяемие виключення (такі як java.lang.RuntimeException) або помилки (наприклад, java.lang.Error). Якщо всередині методу invoke викинуто перевіряється виключення непорівнянне з описаними в сигнатурі интерфейсного методу - то буде так само викинуто виключення UndeclaredThrowableException.

Методи hashCode, equals і toString, певні в класі Object, так само будуть викликатися нема на пряму, а через метод invoke нарівні з усіма інтерфейсними методами. Інші публічні методи класу Object будуть викликатися безпосередньо.

Об'єкт User - цілком звичайний, ніякої магії.

Proxy.newProxyInstance - самі витоки магії, параметри виклику наступні:

  • ClassLoader класу User, про нього трохи нижче;
  • Масив тіпаClass. повинен приймати масив інтерфейсів. які реалізує наш клас (User). МЕТОДИ ЦИХ ІНТЕРФЕЙСІВ перехоплюватимуться (invocationHandler-му).
  • Примірник InvocationHandler. який буде перехвативатьметоди викликані для об'єкта user (насправді, виклики будуть йти через новостворений userProxy).

На виході отримуємо примірник нікого класу (проксі), що дає наступну магічну функціональність:

  • Виконує всі методи інтерфейсів, переданих у 2-му параметрі на вхід при виклику Proxy.newProxyInstance (в нашому прикладі це getName, setName, rename). У цьому він схожий на Person;
  • При виклику цих методів нашого екземпляра (наприклад userProxy.setName) викликається метод INVOKE () InvocationHandler-а. InvocationHandler вже далі вирішує, як йому вчинити -
    • викликати відповідний метод реального класу Person
    • Зробити щось ще, в нашому випадку

    Тобто в нашому прикладі InvocationHandler просто виводить ім'я викликається методу в консоль і викликає його для збереженого об'єкта. Таким чином, перед кожним виконанням методів user буде виводитися назва викликаного методу.

    Скажімо є у нас «жирний» клас з купою методів в яких ми захотіли виконувати одне і те ж дію - наприклад логирование, audit trail, security, сериализация результатів. У класичному OOП таке завдання вирішується або копі-паст, або виділенням кожного методу в окремий клас, що теж не зручно. І в цей самий момент на допомогу приходить магія під назвою рефлексія у вигляді Proxy. Досить виділити методи класу в інтерфейс, щоб створити обгортку, яка буде виконувати дії однакові для всіх методів (до або після логіки самого методу). В цілому це вже нетрі АОП кому потрібні подробиці прошу сюди.

    Звідки ж узявся цей «якийсь клас», екземпляр якого ми отримали на виході Proxy.newProxyInstance?

    Це динамічно створений клас, створений ІЗ МАСИВУ БАЙТ.
    Ланцюжок викликів: Proxy.newProxyInstance -> Proxy.getProxyClass -> sun.misc.ProxyGenerator.generateProxyClass
    Цей останній метод повертає масив байтів, який потім за допомогою ClassLoader.defineClass перетворюється в Class. і далі newInstance.
    В результаті ми отримуємо програму яка генерує сама себе.
    Природно, хочеться подивитися, що з генерував Peter Jones.

    На щастя, можна отримати байт-код генерується класу, встановивши магічне (і нікому не відоме) системне властивість «sun.misc.ProxyGenerator.saveGeneratedFi les» в true перед створенням класу.

    Схожі статті