Аспектно-ориентированное программирование. Основы

    Продолжим тему, начатую в прошлой слесарно-программистской статье про 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 в проекте — чтобы не получилась кастрюля вишни с ложкой крема сверху!

    Share post

    Comments 15

      +3
      Как-то неожиданно оборвалось повествование
        +1
        В 3 ночи захотелось спать) К тому же я не претендую на монографию. Примеры будут в следующих статьях
          0
          Что по, вашему мнению, стоило бы рассказать еще про основы?
            0
            Суть то понятна, хотелось бы увидеть примеры решения задач с сабжем и без (т.е. чтобы оценить профит). Я «краем глаза» уже сталкивался с AspectJ в одном проекте, но заметного профита заметить не удалось.
              0
              Я вас понял. Есть задачи по тестированию, которые без АОП сделать можно только самостоятельно модифицируя байткод приложения или написав кодогенератор для исходников. осмотрите статью по ссылке. По-моему показательно как можно добраться в потроха плагина
          +1
          Какие примеры были бы интересны? В следующих публикациях постараюсь учесть
            +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. Спасибо за статьи по аспектам!
            0
            Мы сейчас пишем примерно так (язык Scala):

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


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

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


            И чтобы в метод measure передавалось имя класса и имя метода. Пробовали извлекать их из стэктрейсов, но при нагрузочном тестировании из-за этого возникают постоянные блокировки, производительность просаживается. Было бы интересно посмотреть, как решить проблему с помощью AOP.
              0
              Со Scala я года 3 как не сталкивался. Если бы вы сделали простой тест и проект дня него на SBT/maven, то я бы попробовал бы решить эту задачу
            +1
            AspectJ — это очень хорошо, используем в проекте для сборка метрик и трассировки в распределенном приложении (через библиотеку Kamon). Не представляю, как бы жили без него.

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

              Не нашел в статье объяснения самой сути АОП для новичка.

              Only users with full accounts can post comments. Log in, please.