Я хотел бы поделиться с вами своим опытом изучения парадигмы АОП и разработки ее использования в большом проекте.
Аспектно-ориентированное программирование, или АОП, — это парадигма, которая берет сквозную функциональность и изолирует ее в форме так называемого аспекта или аспектного класса.
Это подразумевает наличие семантических инструментов и механизмов для скрытого внедрения аспекта в код приложения.
Таким образом, получается, что аспект сам определяет, какие части приложения ему необходимо обработать, при этом приложение не имеет ни малейшего представления (до компиляции, разумеется), что в его части нагло и бессовестно внедряется внешний код. Допустим, у нас достаточно тривиальная задача — обеспечить в приложении поддержку некоторых языков (русского, английского, итальянского, французского и т. д.).
Вы скажете, что у нас есть языковая и региональная дифференциация всех ресурсов, и будете правы.
За исключением случая, когда приложение не использует встроенные ресурсы, а «тянет» их с сервера.
В общем, такая ситуация встречается часто и решается тривиально — добавляем пару строк в обработчик абстрактного класса 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
-
Настройки Электронной Почты Sbcglobal
19 Oct, 24 -
Acm Icpc 2017: Окончательные Итоги
19 Oct, 24 -
Тестируемый Виджет Microsoft Translator
19 Oct, 24