Spring AOP використовує динамічні проксі JDK, або CGLIB для створення проксі для зазначеного цільового об'єкта. Динамічні проксі JDK вбудовані в JDK, в той час як CGLIB — це звичайна бібліотека визначення класів з відкритим вихідним кодом (перепакована в spring-core).

Якщо цільовий об'єкт, що проксується, реалізує хоча б один інтерфейс, використовується динамічний проксі JDK. Усі інтерфейси, які реалізуються цільовим типом, проксуються. Якщо цільовий об'єкт не реалізує жодних інтерфейсів, створюється CGLIB-проксі.

Якщо потрібно примусово використовувати CGLIB-проксування (наприклад, проксувати кожен метод, визначений для цільового об'єкта, а не лише ті, що реалізовані його інтерфейсами), це теж можна здійснити. Однак слід розглянути такі проблеми:

  • У разі CGLIB final методи не можна забезпечити порадами, оскільки вони не можуть бути перевизначені в підкласах, створюваних під час виконання.

  • Починаючи зі Spring 4.0, конструктор об'єкта, що проксується, більше НЕ викликається двічі, оскільки екземпляр CGLIB-проксі створюється через Objenesis. Якщо твоя JVM не дозволяє обходити конструктор, можна помітити подвійні виклики та відповідні записи в журналі налагодження від засобів підтримки АОП у Spring.

Щоб примусово використовувати CGLIB-проксі, встанови значення атрибута proxy-target-class елемента <aop:config> у true, як показано нижче:


<aop:config proxy-target-class="true">
    <!-- інші біни, визначені тут... -->
</aop:config>

Щоб у примусовому порядку здійснити CGLIB-проксування при використанні підтримки автопроксі в @AspectJ, встанови атрибут proxy-target-class елемента <aop:aspectj-autoproxy> у true, як показано нижче:

<aop:aspectj-autoproxy proxy-target-class="true"/> ;

Кілька розділів <aop:config/> згортаються в один єдиний засіб створення (creator) автопроксі під час виконання програми, який застосовує найсуворіші налаштування проксі, зазначені в будь-якому з розділів <aop:config/> (зазвичай із різних файлів визначення бінів XML). Це також стосується елементів <tx:annotation-driven/> та <aop:aspectj-autoproxy/>.

Для більшого розуміння, використання proxy-target-class="true" в елементах <tx:annotation-driven/>, <aop:aspectj-autoproxy/> або <aop:config/> примусово використовується CGLIB-проксі для всіх трьох елементів.

Основні відомості про АОП-проксі

Spring AOP базується на проксі. Вкрай важливо розуміти семантику того, що означає остання інструкція, перш ніж писати свої власні аспекти або використовувати будь-які аспекти на основі Spring AOP, що поставляються зі Spring Framework.

Розглянемо спочатку сценарій, в якому у вас є звичайне, типове, непроксоване, пряме посилання на об'єкт, як показано в наступному фрагменті коду:

Java

public class SimplePojo implements Pojo {
    public void foo() {
        // Наступний виклик методу є прямим викликом посилання
        "this" this.bar();
    } public void bar() {
        // трохи логіки...
    }
}
Kotlin

class SimplePojo : Pojo {
    fun foo() {
        // наступний виклик методу є прямим викликом посилання "this"
        this.bar()
    } fun bar() {
        // трохи логіки...
    }
}

Якщо ти викликаєш метод посилання на об'єкт, то метод викликається безпосередньо за цим посиланням на об'єкт, як показано на наступному зображенні та в лістингу:

Java

public class Main {
    public static void main(String[] args) {
        Pojo pojo = новий SimplePojo();
        // це прямий виклик методу посилання "pojo"
        pojo.foo();
    }
}
Kotlin

fun main() {
    val pojo = SimplePojo()
    // це прямий виклик методу за посиланням "pojo"
    pojo.foo()
}

Ситуація трохи змінюється, якщо посилання, яке міститься в клієнтському коді, є проксі. Розглянемо наступну схему та фрагмент коду:

Java

public class Main {
    public static void main(String[] args) {
        ProxyFactory factory = New ProxyFactory(New SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        Pojo = (Pojo) factory.getProxy();
        // Це виклик методу для проксі!
        pojo.foo();
    }
}
Kotlin

fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface (Pojo::class.java)
    factory.addAdvice(RetryAdvice())
    val pojo = factory.proxy як Pojo
    // це виклик методу для проксі!
    pojo.foo()
}

Тут найважливіше зрозуміти, що клієнтський код усередині методу main(..) класу Main містить посилання на проксі. Це означає, що виклики методів посилання на об'єкт є викликами проксі. В результаті проксі може делегувати всі перехоплювачі (поради), які мають відношення до конкретного виклику методу. Однак, як тільки виклик досягне цільового об'єкта (в даному випадку посилання SimplePojo), всі виклики методів, які він може здійснити для себе, такі як this.bar() або this.foo(), будуть викликані для this посилання, а не для проксі. Це має важливі наслідки. Це означає, що самовиклик не призведе до того, що порада, пов'язана з викликом методу, зможе виконуватись.

Добре, але що з цим робити? Найкращий підхід (термін "найкращий" тут використовується умовно) — це рефакторинг вашого коду таким чином, щоб самовиклик не відбувався. Це вимагає деяких зусиль з твого боку, але це найкращий, найменш агресивний підхід. Наступний підхід абсолютно жахливий, і ми не впевнені, що варто загострювати на ньому увагу саме тому, що він такий жахливий. Ти можеш (хоч би як це болісно для нас виглядало) повністю прив'язати логіку всередині свого класу до Spring AOP, як показано в наступному прикладі:

Java

public class SimplePojo implements Pojo {
    public void foo() {
        // це працює, але... фу!
        ((Pojo) AopContext.currentProxy()).bar();
    } public void bar() {
        // трохи логіки...
    }
}
Kotlin

class SimplePojo : Pojo {
    fun foo() {
        // це працює, але... фу!
        (AopContext.currentProxy() as Pojo).bar()
    } fun bar() {
            // трохи логіки...
    }
}

Цей підхід повністю прив'яже твій код до Spring AOP, а сам клас знатиме про те, що він використовується в контексті АОП, що йде врозріз з АОП. Також він вимагатиме деякого додаткового налаштування при створенні проксі, як показано в наступному прикладі:

Java

public class Main {
    public static void main(String[] args) {
        ProxyFactory factory = New ProxyFactory(New SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);
        Pojo = (Pojo) factory.getProxy();
        // Це виклик методу для проксі!
        pojo.foo();
    }
}
Kotlin

fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface (Pojo::class.java)
    factory.addAdvice(RetryAdvice())
    factory.isExposeProxy = true
    val pojo = factory.proxy як Pojo
    // це виклик методу для проксі!
    pojo.foo()
}

Нарешті, слід зазначити, що AspectJ не має цієї проблеми із самовикликом, оскільки він не є АОП-фреймворком на основі проксі.

Програмне створення проксі @AspectJ

На додаток до оголошення аспектів у конфігурації за допомогою <aop:config> або <aop:aspectj -autoproxy>, можна також програмно створювати проксі, які забезпечують порадами цільові об'єкти. У цій частині ми хочемо зосередитися на можливості автоматичного створення проксі за допомогою аспектів @AspectJ.

Ти можеш використовувати клас org.springframework.aop.aspectj.annotation.AspectJProxyFactory для створення проксі для цільового об'єкта, який був забезпечений порадою одним або декількома аспектами @AspectJ. Базове використання цього класу виглядає дуже просто, як показано в наступному прикладі:

Java

// створюємо фабрику, яка може генерувати проксі для вказаного цільового об'єкта
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// Додаємо аспект, клас має бути аспектом @AspectJ
// можна викликати його стільки разів, скільки потрібно, з різними аспектами
factory.addAspect(SecurityManager.class);
// також можна додати існуючі екземпляри аспектів, тип наданого об'єкта має бути аспектом @AspectJ
factory.addAspect(usageTracker);
// тепер отримуємо об'єкт проксі...
MyInterfaceType proxy = factory.getProxy();
Kotlin

// створюємо фабрику, яка може генерувати проксі для вказаного цільового об'єкта
val factory = AspectJProxyFactory(targetObject)
// додаємо аспект, клас має бути аспектом @AspectJ
// можна викликати його стільки разів, скільки потрібно, з різними аспектами
factory.addAspect(SecurityManager::class.java)
// також можна додати існуючі екземпляри аспектів, тип наданого об'єкта має бути аспектом @AspectJ
factory.addAspect(usageTracker)
// тепер отримуємо об'єкт проксі...
val proxy = factory.getProxy<Any>()