...

Aop что это за программа

Механизмы проксирования

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 элемента в true, как показано ниже:

Чтобы в принудительном порядке осуществить CGLIB-проксирование при использовании поддержки авто-прокси в @AspectJ, установите атрибут proxy-target-class элемента в true , как показано ниже:

Несколько разделов сворачиваются в одно единое средство создания (creator) авто-прокси во время выполнения программы, который применяет самые строгие настройки прокси, указанные в любом из разделов (обычно из разных файлов определения бинов XML). Это также относится к элементам и .

Для большего понимания, использование proxy-target-class=”true” в элементах , или принудительно используется CGLIB-прокси для всех трех элементов.

Основные сведения об АОП-прокси

Spring AOP основан на прокси. Крайне важно, чтобы вы поняли семантику того, что означает последняя инструкция, прежде чем писать свои собственные аспекты или использовать какие-либо аспекты на основе Spring AOP, поставляемые со Spring Framework.

Рассмотрим сначала сценарий, в котором у вас есть обыкновенная, типовая, непроксированная, ничем особенным не выделяющаяся, прямая ссылка на объект, как показано в следующем фрагменте кода:

public class SimplePojo implements Pojo < public void foo() < // следующий вызов метода является прямым вызовом ссылки "this" this.bar(); >public void bar() < // немного логики. >>
class SimplePojo : Pojo < fun foo() < // следующий вызов метода является прямым вызовом ссылки "this" this.bar() >fun bar() < // немного логики. >>

Если вы вызываете метод по ссылке на объект, то метод вызывается непосредственно по этой ссылке на объект, как показано на следующем изображении и в листинге:

public class Main < public static void main(String[] args) < Pojo pojo = new SimplePojo(); // это прямой вызов метода по ссылке "pojo" pojo.foo(); >>
fun main() < val pojo = SimplePojo() // это прямой вызов метода по ссылке "pojo" pojo.foo() >

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

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 = (Pojo) factory.getProxy(); // это вызов метода для прокси! pojo.foo(); >>
fun main() < val factory = ProxyFactory(SimplePojo()) factory.addInterface(Pojo::class.java) factory.addAdvice(RetryAdvice()) val pojo = factory.proxy as Pojo // это вызов метода для прокси! pojo.foo() >

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

Ладно, но что же с этим делать? Лучший подход (термин “лучший” здесь используется условно) – это рефакторинг вашего кода таким образом, чтобы самовызов не происходил. Это требует некоторых усилий с вашей стороны, но это лучший, наименее агрессивный подход. Следующий подход абсолютно ужасен, и мы не уверены, что стоит заострять на нем внимание, именно потому, что он столь ужасен. Вы можете (как бы это болезненно для нас ни выглядело) полностью привязать логику внутри вашего класса к Spring AOP, как показано в следующем примере:

public class SimplePojo implements Pojo < public void foo() < // это работает, но. фу! ((Pojo) AopContext.currentProxy()).bar(); >public void bar() < // немного логики. >>
class SimplePojo : Pojo < fun foo() < // это работает, но. фу! (AopContext.currentProxy() as Pojo).bar() >fun bar() < // немного логики. >>

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

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 = (Pojo) factory.getProxy(); // это вызов метода для прокси! pojo.foo(); >>
fun main() < val factory = ProxyFactory(SimplePojo()) factory.addInterface(Pojo::class.java) factory.addAdvice(RetryAdvice()) factory.isExposeProxy = true val pojo = factory.proxy as Pojo // это вызов метода для прокси! pojo.foo() >

Наконец, следует отметить, что AspectJ не имеет этой проблемы с самовызовом, поскольку он не является АОП-фреймворком на основе прокси.

Программное создание прокси @AspectJ

В дополнение к объявлению аспектов в конфигурации с помощью или , можно также программно создавать прокси, которые снабжают советами целевые объекты. В этой части мы хотим сосредоточиться на возможности автоматического создания прокси с помощью аспектов @AspectJ.

Вы можете использовать класс org.springframework.aop.aspectj.annotation.AspectJProxyFactory для создания прокси для целевого объекта, который был снабжен советом одним или несколькими аспектами @AspectJ. Базовое использование этого класса выглядит крайне просто, как показано в следующем примере:

// создаем фабрику, которая может генерировать прокси для заданного целевого объекта AspectJProxyFactory factory = new AspectJProxyFactory(targetObject); // добавляем аспект, класс должен быть аспектом @AspectJ // можно вызывать его столько раз, сколько нужно, с различными аспектами factory.addAspect(SecurityManager.class); // также можно добавить существующие экземпляры аспектов, тип предоставленного объекта должен быть аспектом @AspectJ factory.addAspect(usageTracker); // теперь получаем объект прокси. MyInterfaceType proxy = factory.getProxy();
// создаем фабрику, которая может генерировать прокси для заданного целевого объекта val factory = AspectJProxyFactory(targetObject) // добавляем аспект, класс должен быть аспектом @AspectJ // можно вызывать его столько раз, сколько нужно, с различными аспектами factory.addAspect(SecurityManager::class.java) // также можно добавить существующие экземпляры аспектов, тип предоставленного объекта должен быть аспектом @AspectJ factory.addAspect(usageTracker) // теперь получаем объект прокси. val proxy = factory.getProxy()

Введение в аспектно-ориентированное программирование

В вычислительной технике аспектно-ориентированное программирование (AOP) – это парадигма программирования, направленная на повышение модульности за счет разделения сквозных задач.

Общие проблемы – это аспекты программы, которые влияют на другие проблемы. Эти проблемы часто невозможно четко отделить от остальной системы как при разработке, так и при реализации, и они могут привести либо к разбросу (дублирование кода), либо к запутыванию (существенные зависимости между системы).

Примеры общих проблем:

  • Кеширование
  • Проверка данных
  • логирование
  • Форматировать данные
  • Обработка транзакции

В качестве примера возьмем этот сценарий:

Вы разрабатываете службу с множеством getter методов для получения данных из базы данных и форматирования результата в виде строки JSON.

class Service < getUser() < if (!Number.isInteger(id)) return null const user = DAO.fetch(User, id, name) return JSON.stringify(user) > getArticle() < if (!Number.isInteger(id)) return null const article = DAO.fetch(Article, id) return JSON.stringify(article) > // getXyz . >

Объект доступа к данным (DAO) можно упростить как:

const DAO = < // database access, query and retrieve the results here fetch(model, . params) < return new model(. params) >>

А вот и ваши модели:

class Article < constructor(id) < this.id = id >> class User < constructor(id, name) < this.id = id this.name = name >>

Как видите, любые getter методы Service можно разделить на 3 части (или 3 задачи):

  • Проверка данных: проверьте, является ли id целым числом.
  • Основная проблема: взаимодействие с уровнем доступа к данным.
  • Форматировать данные: преобразовать результат в строку JSON.

В этом примере эти 3 проблемы чрезмерно упрощены. Но в реальных приложениях основная проблема, проверка данных и логика форматирования данных могут стать очень сложными.

Логика проверки данных и форматирования данных – это сквозная проблема, и ее следует изолировать от основной проблемы, поскольку они отвлекать разработчиков от основной бизнес-логики.

Таким образом, мы должны разделить и поместить их в 3 разных места:

  • Модуль Служба должен только заниматься основной проблемой или основной бизнес-логикой.
  • Модуль проверки данных должен только заботиться о проверке данных.
  • Модуль форматирования данных должен только заботиться о форматировании данных.

Применить АОП с Javascript

Существует множество библиотек и фреймворков, которые позволяют нам реализовать решение АОП для решения сквозных проблем.

Здесь мы используем библиотеку Javascript: aspect.js

Удалив все сквозные проблемы из основной проблемы, мы переписываем модуль Service следующим образом:

import Advised> from 'aspect.js' @Advised() class Service < getUser() < return DAO.fetch(User, id, name) >getArticle() < return DAO.fetch(Article, id) >>

@Advised декоратор помогает связать ValidateAspect и FormatAspect с классом Service . Реализация Service очень понятна и легко читается. Он фокусируется только на своей основной ответственности.

Наконец, мы можем попробовать вызвать некоторые методы, чтобы увидеть результат:

const service = new Service() console.log(service.getArticle()) // null console.log(service.getUser()) //

Вы можете клонировать полный исходный код здесь:

Объяснение основных концепций АОП

1 / Аспект

Аспект – это модуль, который имеет набор сквозных функций.

E.g: ValidateAspect , FormatAspect

Чтобы сформировать аспект, мы определяем pointcut и advice.

2 / точка соединения

Точка соединения – это особая точка в приложении, куда мы можем подключить аспект AOP. Например, выполнение метода, обработка исключений, изменение переменных… В большинстве случаев точка соединения представляет выполнение метода.

3 / Совет

Совет – это действие, предпринимаемое в определенной точке соединения. Другими словами, это фактический код, который выполняется до или после точки соединения.

E.g: @beforeMethod , @afterMethod , @aroundMethod

4 / Pointcut

pointcut – это предикат, который соответствует точке соединения, чтобы определить, нужно ли запускать advice. В зависимости от библиотек или фреймворков pointcut может быть задан по-разному с шаблоном или выражением.

В библиотеке aspect.js, используемой в этой статье, pointcut указывается с помощью регулярного выражения Javascript (Regex):

5 / Целевой объект

Целевой объект – это объект, рекомендованный одним или несколькими аспектами. Также называется рекомендованный объект.

Например: класс Service , использованный в примере.

6 / Ткачество

Плетение – это процесс связывания аспектов с другими объектами для создания рекомендованного объекта.

Например: примените плетение к классу Service с помощью декоратора @Advised .

@Advised() class Service

Несколько мыслей об АОП

Преимущества

  • Примите модульность.
  • Уменьшите связь между зависимостями.
  • Сделайте код более удобным для чтения, повторного использования и обслуживания.

Недостатки

  • Не входит в состав языков программирования, для работы необходимо полагаться на библиотеки.
  • Отладка может быть сложной, потому что поток кода – это нечто большее, чем кажется на первый взгляд.
  • Требуйте дисциплины, чтобы не злоупотреблять.

Аспектно-ориентированное программирование считается «темным искусством» в мире ООП, потому что оно может вмешиваться в ваш код и выполнять некую черную магию за кулисами. Например, он может изменить ваш метод, заменить результат или даже заблокировать выполнение вашего кода.

Несмотря на всю мощь, которую он дает, мы должны использовать АОП с осторожностью и глубоким пониманием. Ненужное использование АОП в вашем приложении может нанести вред и привести к множеству неожиданных ошибок.

Аспектно-ориентированное программирование не полностью заменяет объектно-ориентированное программирование. Вместо этого они товарищи.

Аспектно-ориентированное программирование дополняет объектно-ориентированное программирование, предоставляя другой способ мышления о структуре программы. Ключевой единицей модульности в ООП является класс, тогда как в АОП единицей модульности является аспект.

При подготовке материала использовались источники:
https://javarush.com/quests/lectures/questspring.level01.lecture64
https://skine.ru/articles/127310/

Добавить комментарий