Продолжим тему, начатую в прошлой слесарно-программистской статье про AspectJ и open source расширение для этой библиотеки aspectj-scripting. В этой заметке рассмотрим какие задачи решает аспектно-ориентированное программирование (AOP) на примере и синтаксисе самой известной библиотеки среди адептов этой методологии. AspectJ — дитя Xerox PARC, теперь совершеннолетнее и живущее в Eclipse Foundation.


(фото из статьи на lurkmore про евроремонт)

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

Disclaimer: Я не теоретик, поэтому рассказанное в статье субъективно, пропущено через призму опыта.


Зачем все эти сложности, другой подход к проектированию систем, когда есть объектно-ориентированное программирование? Новый синтаксис, какие-то аспекты, срезы(pointcut) — ведь все в итоге превращается в те же инструкции выполняемые jvm.

Вопрос в удобстве разработки, легкости модификации, тестирования и стоимости поддержки системы. Аспекты — это дополнение к ООП программам, другая парадигма при разработке и проектировании программ, не противоречащая использованию объектной модели в приложении. Аналогия, которая сразу приходит на ум — анализ звуковых данных. В временной области с исходным сигналом сложно выполнить обработку и анализ. Но при переводе в частотное представление легко отфильтровать шумы определенной частоты, выделить ноты в мелодии, усилить звук голоса и т.п. Так же и аспекты позволяют легко работать со многими задачами в другом представлении программы и выделить сквозную функциональность в виде аспекта. При умелом применении AOP помогает «распутать» «запутанный» код программы.

Возникает возможность убрать инструкции ведения журнала операций(логгинга) и обработки системных ошибок из бизнес-логики приложения, уменьшить количество бойлерплейт кода, проверять права доступа пользователя при обращении к определенным методам или полям класса, декларативно управлять транзакциями базы данных, выполнять свое инструментирующее профилирование интересующего вас участка кода, в котором учитывается не только факт вызова и время выполнения операции но и некоторый контекст, кеширование «тяжелых» операций бизнес логики если у них нет побочного эффекта, поиск мест программы в которых конструирование объектов определенного типа. Этим не ограничивается список того, для чего может быть удобно AOP. Например, в проекте на работе, я применяю аспекты для тестирования распределенного приложения, сбора метрик в нем, имитации ошибок базы данных и таймаутов внутри jdbc драйвера oracle и сетевых взаимодействий.

Начнем с понятия что такое advice в AspectJ — это то как будет применяться аспект в срезе кода(pointcut): перед срезом (BEFORE), после (AFTER), после успешного возврата (AFTER RETURNING), в случае ошибки в точке среза (AFTER THROWING), или полный контроль над ситуацией (AROUND) где самостоятельно надо вызывать исходный код в срезе, передавать параметры и обрабатывать ошибки выполнения в точке среза.

Pointcut или срез — это описание того, где мы будем внедрять в исходную программу аспект. Синтаксис достаточно богатый и позволяет описать сложные правила, определяющие точки среза. Например, конструирование объекта, блоки статической инициализации, вызов конструктора, доступ к полям объекта на чтение/запись, конструирование объекта, блок catch, методы с какой-либо аннотацией, вызов метода с параметрами определенного типа, имя метода по маске и т.п. + логические операции в синтаксисе pointcut. Конечно, волшебства в программировании нет, что добавляет ограничения на то что инлайнится в байт коде и не доступно в pointcut. Так же не все мыслимые точки среза можно указать декларативно, что иногда требует анализа контекста вызова в коде реализации аспекта.

Параметр точки соединения (join point) в аспекте позволяет получить значения аргументов, узнать место где фактически произошел вызов аспекта в срезе, получить this объекта и т.п. В случае с AROUND advice тут же можно выполнить фактический вызов, изменить его параметры и получить возращаемое значение или обработать ошибку.

Все то, что на первый взгляд кажется магией, в AspectJ либо реализуется плагином во время сборки проекта (weaving to bytecode), либо java агентом с помощью модификации байт кода программы во время загрузки классов(load-time weaving). AspectJ — зрелый AOP фреймворк с огромным комьюнити, множеством публикаций про него, хорошей документацией, достаточно стабильный, интегрированный в разнообразные системы сборки, интеграция в Spring, с хорошей поддержкой в IDE.

В aspectj-scripting (расширении AspectJ java агента) есть возможность для аспектов во время выполнения загружать классы из maven репозитария, считывать конфигурацию агента не только из файла и classpath, но и с http сервера. Это может сильно помочь в тестировании, профилировании и модификации распределенного java приложения. Код доступен на github, а агент в центральном репозитарии. В прошлой публикации использовал эту библиотеку для модификации поведения maven plugin Про другие примеры использования аспектно-ориентированного программирования в практике, расскажу в следующих статьях.

Как вишенка на торте украшает его и делает более вкусным, так же и аспектно-ориентированное программирование в объектно-ориентированной программе упрощает разработку, модификацию и тестирование. Важно не увлекаться применением AOP в проекте — чтобы не получилась кастрюля вишни с ложкой крема сверху!

Комментарии (12)


  1. zencd
    03.04.2015 12:20
    +3

    Как-то неожиданно оборвалось повествование


    1. igor_suhorukov Автор
      03.04.2015 12:35
      +1

      В 3 ночи захотелось спать) К тому же я не претендую на монографию. Примеры будут в следующих статьях


    1. igor_suhorukov Автор
      03.04.2015 13:57

      Что по, вашему мнению, стоило бы рассказать еще про основы?


      1. isden
        03.04.2015 15:59

        Суть то понятна, хотелось бы увидеть примеры решения задач с сабжем и без (т.е. чтобы оценить профит). Я «краем глаза» уже сталкивался с AspectJ в одном проекте, но заметного профита заметить не удалось.


        1. igor_suhorukov Автор
          03.04.2015 16:26

          Я вас понял. Есть задачи по тестированию, которые без АОП сделать можно только самостоятельно модифицируя байткод приложения или написав кодогенератор для исходников. осмотрите статью по ссылке. По-моему показательно как можно добраться в потроха плагина


  1. igor_suhorukov Автор
    03.04.2015 13:48
    +1

    Какие примеры были бы интересны? В следующих публикациях постараюсь учесть


    1. TimReset
      03.04.2015 14:24
      +1

      Проблема, с который мы недавно столкнулись и которую пробовал решить с помощью аспектов — логгирование CallableStatement. Т.е. стандартный код:

      CallableStatement c = con.createCallableStatement("{call package.procedure(?,?,?,?)}");
      c.setString(1,"a");
      c.setInteger(2,2);
      c.setStruct(3,struct);
      c.execute();
      

      И я пробовал с помощью аспектов получить {call package.procedure(?,?,?,?)} и потом отследить вызов set методов с логгированием переданных параметров и при вызове execute() записывать эти параметры в лог.
      В итоге не осилил — при включении аспектов на нашем проекте компиляция стала занимать какое-то нереальное время. И пошёл по другому пути — сделал wrapper вокруг connection, а он возвращает wrapper вокруг CallableStatement который уже производит логгирование. Кода получилось очень мало — большая часть кода wrapper была сгенерирована IDEA, а я в итоге написал сохранение параметров в set методах и вывод их в лог при выполнении execute().

      Но мне бы было интересно, если бы Вы показали на этом примере, как можно применить аспекты.

      Успешный пример их применения у меня был на гораздо меньшем проекте — там нужно было логировать просто время выполнения определённых методов (проект был по нагрузочному тестированию). И там было всё гораздо проще — написал pointcut для поиска этих методов, сохранял текущее время, вызывал метод, писал разницу между текущим временем и сохранённым.

      Но в примере с CallableStatement по другому — нужно не просто подменить вызов метода своим, а ещё и отследить что вызывались set методы у объекта и отследить вызов exceute().

      P.S. Спасибо за статьи по аспектам!


      1. igor_suhorukov Автор
        03.04.2015 14:30
        +2

        Отличная задача)
        Такое решал с помощью ThreadLocal. Напишу пример


    1. afiskon
      03.04.2015 14:43

      Мы сейчас пишем примерно так (язык Scala):

      class SomeClass {
        def someMethod = measure("SomeClass.someMethod") {
           // ...
        }
      }
      


      А хотелось бы:

      class SomeClass {
        def someMethod = measure {
           // ...
        }
      }
      


      И чтобы в метод measure передавалось имя класса и имя метода. Пробовали извлекать их из стэктрейсов, но при нагрузочном тестировании из-за этого возникают постоянные блокировки, производительность просаживается. Было бы интересно посмотреть, как решить проблему с помощью AOP.


      1. igor_suhorukov Автор
        03.04.2015 15:06

        Со Scala я года 3 как не сталкивался. Если бы вы сделали простой тест и проект дня него на SBT/maven, то я бы попробовал бы решить эту задачу


  1. afiskon
    03.04.2015 14:40
    +1

    AspectJ — это очень хорошо, используем в проекте для сборка метрик и трассировки в распределенном приложении (через библиотеку Kamon). Не представляю, как бы жили без него.

    С нетерпением жду новых постов, посвященных AOP!


    1. igor_suhorukov Автор
      03.04.2015 15:09

      Смотрел на kamon — интересный проект. Из разработчиков видел комиты наших соотечественников. В моем проекте на гитхабе есть зависимость на их артефакт — перепакованный sigar с нативными библиотеками внутри