Реализация Aspectj В Приложении Android

Я хотел бы поделиться с вами своим опытом изучения парадигмы АОП и разработки ее использования в большом проекте.



Реализация AspectJ в приложении Android

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

Это подразумевает наличие семантических инструментов и механизмов для скрытого внедрения аспекта в код приложения.

Таким образом, получается, что аспект сам определяет, какие части приложения ему необходимо обработать, при этом приложение не имеет ни малейшего представления (до компиляции, разумеется), что в его части нагло и бессовестно внедряется внешний код. Допустим, у нас достаточно тривиальная задача — обеспечить в приложении поддержку некоторых языков (русского, английского, итальянского, французского и т. д.).

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

За исключением случая, когда приложение не использует встроенные ресурсы, а «тянет» их с сервера.

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

И даже без базового класса.

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

Итак, задача ясна, давайте напишем.

  
  
  
   

package com.archinamon.example.xpoint; import android.support.v7.app.AppCompatActivity; import android.app.Application; public aspect LocaleMonitor { pointcut saveLocale(): execution(* MyApplication.onCreate()); pointcut checkLocale(AppCompatActivity activity): this(activity) && execution(* AppCompatActivity+.

onCreate(.

)); after(): saveLocale() { saveCurrentLocale(); } before(AppCompatActivity activity): checkLocale(activity) { if (isLocaleChanged()) { saveCurrentLocale(); restartApplication(activity); } } void saveCurrentLocale() {/* implementation */} void restartApplication(AppCompatActivity context) {/* implementation */} boolean isLocaleChanged() {/* implementation */} }

Добавив этот класс в наше приложение, мы научим его перезапускаться при изменении языка в системе.

Некоторое время я искал готовые решения и опробовал их.

В итоге я написал свою версию.

Как все это работает, почему пришлось изобретать велосипед и что из всего этого вышло – под катом! Следует отметить, что выше описаны только общие рамки аспекта; вся механика заставляет добавить несколько дополнительных строк советы брать контекстные данные из ломтики И точки подключения .

Полный код аспекта вы найдете в демо-приложении в конце статьи.

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



Философия

Этот отказ от ответственности подразумевает, что аспектный класс является декоратором поверх объектного класса.

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

Плохой путь — когда аспект превращается в нечитаемый класс, напичканный служебными инструментами.

Лучше всего описать всю логику в отдельном объектном модуле, максимально изолированном от внешнего мира, который подключается к приложению через декоратор аспекта.

Говоря о аспектном подходе к разработке как о своеобразном механизме декорирования, следует отметить его основные возможности, частично продемонстрированные на примере.

Понятие процедур, функций и методов заменяется термином "совет" .

Совет можно применить до ( до ), после ( после ) или вместо ( вокруг ) того раздела кода, куда мы встроили через кусочек .

В свою очередь, концепция резать ( точка ) скрывает некоторое описание точек в нашей программе, к которым подключен аспектный класс.

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



Тестирование

Еще одна область, где аспекты могут проявить себя, — это тестирование и отладка.

Написать универсальный профайлер для методов и классов проще, чем может показаться.

аспект MyProfilerImpl расширяет Profiler

package com.archinamon.example.xpoint; aspect MyProfilerImpl extends Profiler { private pointcut strict(): within(com.archinamon.example.*) && !within(*.

xpoint.*); pointcut innerExecution(): strict() && execution(!public !static * *(.

)); pointcut constructorCall(): strict() && call(*.

new(.

)); pointcut publicExecution(): strict() && execution(public !static * *(.

)); pointcut staticsOnly(): strict() && execution(static * *(.

)); private pointcut catchAny(): innerExecution() || constructorCall() || publicExecution() || staticsOnly(); before(): catchAny() { writeEnterTime(thisJoinPointStaticPart); } after(): catchAny() { writeExitTime(thisJoinPointStaticPart); } } abstract aspect Profiler issingleton() { abstract pointcut innerExecution(); abstract pointcut constructorCall(); abstract pointcut publicExecution(); abstract pointcut staticsOnly(); protected static final Map<String, Long> sTimeData = new ConcurrentHashMap<>(); protected void writeEnterTime(JoinPoint.StaticPart jp) {/* implementation */} protected void writeExitTime(JoinPoint.StaticPart jp) {/* implementation */} }

Кусочек строгий() обрезает обход точек соединения, чтобы избежать пересечения самих классов аспектов.

В остальном описанная структура предельно проста и интуитивно понятна.

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

Маркер иссинглтон() в описании абстрактного аспектного класса явно объявляется, что каждый преемник будет синглтоном.

На самом деле в записи нет необходимости, поскольку все классы аспектов по умолчанию являются синглтонами.

В нашем случае здесь этот маркер нужен, чтобы сообщить стороннему застройщику об этом объекте.

В своей практике я предпочитаю отмечать неявную функциональность — тем самым повышая понимание и читабельность модуля для других.

Перейдем непосредственно к тестированию.

Насколько эффективны аспекты?

  • Прежде всего, своей внутренней механикой.

    Тестируем в нем участок кода привычный среде, а не пытаться эмулировать контекст применения определенного функционала (функции, процедуры, самого объекта и т. д.).

  • Второе важное преимущество — обилие отладочной информации, доступной в точке подключения благодаря внедрениям на этапе компиляции.

  • Третье и очень важное преимущество заключается в возможностях так называемого NamePattern, который описывает все параметры среза.

    Шаблон имени может охватывать огромное количество однотипных и схожих областей (например, захватывать все геттеры-сеттеры с разрезом в одну строку).

Все это дает большую прибыль при написании модульных и функциональных тестов.

Но всегда есть важное «но».

Аспектное тестирование — это скорее анализ в реальном времени.

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

Это означает, что АОП не подходит в качестве замены привычных фреймворков.

Подведу итог всему вышесказанному.

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

Например, при ручном тестировании или в качестве бета-версии приложения.

P.S. Полезные мелочи А по некоторым аспектам вы можете покрыть классы и/или методы обработчиками исключений одним движением пальца:

abstract aspect NetworkProtector { abstract pointcut myClass(); Response around(): myClass() && execution(* executeRequest(.

)) { try { return proceed(); } catch (NetworkException ex) { Response response = new Response(); response.addError(new Error(ex)); return response; } } }

Это самый простой вариант, но его можно и усложнить, если подробно и пошагово описать все этапы.



Велосипед v3.14: почему и как?

Вы можете освоить АОП без Android. Чтобы использовать технологию в своем проекте, я начал писать собственный плагин для системы сборки Gradle. Но ведь уже есть готовые решения, скажет информированный читатель! И он будет прав.

И он будет не совсем прав, потому что.

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

Например, ни один плагин не мог корректно работать с ароматами или создать собственный исходный набор для декомпозиции исходного кода.

А некоторые позволяли писать аспекты только в стиле java-аннотаций.

Вот я и собрал все препятствия на пути к реализации собственного плагина.

Что в итоге перекрыло все узкие места.

И я даже приобрел взломанный семантический плагин (привет Spring@AOP из Ultimate-версии!) для личного удобства.

Ждем официального релиза в будущих версиях Android Studio. Название выпуска: Поддержка AspectJ Метки: Улучшение типа Субкомпонент-Инструменты-Студия Субкомпонент-Инструменты-gradle-ide Приоритет-малая цель-1.6 Отмечу несколько очевидных и не очень очевидных вещей, с которыми мне пришлось столкнуться при разработке.

  • Организация стека задач компилятора Здесь и поддержка инструментов препроцессинга, и Retrolambda вызывали головную боль.

    Это были первые и, субъективно, самые сложные грабли.

    Я впервые писал расширение для Gradle, и все подводные камни с управлением стеком задач я собрал на самом первом этапе разработки.

    В результате плагин явно проверяет проект, подключен ли к нему Retrolambda, и, если результат положительный, он ставится в очередь перед выполнением.

    В противном случае он попадает в очередь сразу после компилятора Java.

  • Производительность и инкрементная сборка Правильная организация стека задач — это половина дела.

    Оптимизировать его и дать фору производительности — задача другого уровня.

    AspectJ подключается вместе со своим компилятором — ajc. И именно поэтому все обрабатывается вручную.

    Честно говоря, плагину здесь еще есть куда развиваться.

    Задачи генерации кода препроцессором, сборки java-исходников, сборки dex-файлов (исполняемых файлов среды Android) работают в обычных оптимизированных условиях.

    При этом ajc по-прежнему не работает в инкрементальном режиме.

  • Адаптация рабочего пространства и инструментов Gradle Сначала я реализовал основные функции и поддержку популярных плагинов.

    Исходный код компилируется, аспекты внедряются и приложение создается.

    В проекте, ставшем полигоном, приближалась страшная дата внедрения ароматизаторов.

    Я понял, что весь плагин станет неактуальным, если ему не удастся подружиться с этими инструментами.

    И в то же время хотелось удобно и лаконично оформить рабочую область с исходными кодами.

    Вскоре плагин научился работать с опциями сборки и правильно интегрироваться в свои задачи.

    И в итоге у меня в ресурсах появилась своя папка —spectj, вместе с java, groovy,aidl и другими.



Что взять с собой?

Для пополнения кучи аспектов берем еще синтаксис Java 8 и StreamAPI из той же восьмерки (это необходимо для функциональной работы с массивами, коллекциями и списками) — ведь Android API уже включает в себя Java API, и Новшествами от восьмёрки, увы, не может похвастаться.

Файл build.gradle проекта преобразуется.



buildscript { repositories { mavenCentral() maven { url ' https://raw.github.com/Archinamon/GradleAspectJ-Android/master/ ' } maven { url ' https://raw.github.com/Archinamon/RetroStream/master/ ' } } dependencies { //retrolambda classpath 'me. tatarka:gradle-retrolambda:3.2.3 ' //aspectj classpath 'com. archinamon:AspectJ-gradle:1.0.16 ' } } // Required because retrolambda is on maven central repositories { mavenCentral() } apply plugin: 'com.android.application' //or apply plugin: 'com.android.library' apply plugin: 'me.tatarka.retrolambda' apply plugin: 'com.archinamon.aspectj' dependencies { compile 'com. archinamon:RetroStream:1.0.4 ' }

Вот и все! Подробную настройку я оставлю за кадром, но все подробности вы можете найти в исходном коде на Github.

Ссылки

Демо проект .

Плагин Gradle для Android-студии.

Теги: #java #Android Studio #aspectj #java #Разработка Android

Вместе с данным постом часто просматривают: