27 January, 2012

Немножко магии от AspectJ

Наверно, вы уже сталкивались с таким понятием, как AOП - аспектно-ориентированное программирование.

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

Но это не единственные области применения АОП.

Я хочу показать ещё пару областей применения из реальных проектов:

1. Модификация исходного кода для реализации дополнительных возможностей.
2. Принудительная проверка контракта между модулями.

Модификация исходного кода для реализации дополнительных возможностей

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

service.doUsefulThing();

наш вызов превращается в

try {
        service.doUsefulThing();
    } catch ( FirstServiceException e) {
        processException(e);
    } catch ( SecondServiceException e) {
        processException(e);
    }

Дополнительная проблема в том, что у модуля количество модулей 10+, количество методов также велико, что приводит к тому, что блоки try/catch замусоривают код. Решение с использованием паттерна Callback также приведёт к замусориванию кода.

Вариант решения проблемы с использованием AOP
Решение данной проблемы было таким - а что, если используя возможности AOP трансформировать проверяемое исключение в непроверяемое? Таким образом, мы сможем избавиться от скучной проверки на исключения в нашем коде, а для обработки исключения (ведь мы всегда его будем обрабатывать одинаково) достаточно будет использовать обработку исключения на верхнем уровне абстракции.

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

package com.blogger.atamanenko;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Inherited
public @interface SuppressExceptions {
}
    

А также аспект, который бы делал всю нужную нам функциональность:

public aspect ExceptionSupressingAspect {

        declare soft :ServiceException:  execution(@com.blogger.atamanenko.annotation.SuppressExceptions * *.*(..));

}

Данный аспект делает в точности следующее: "Смягчает" исключение ServiceException для метода, который помечен аннотацией @SuppressExceptions.

Пример использования:

@SuppressExceptions
    protected Entity findEntity(final Identifiable id) {
        return entityService.findById(id);
    }

Принудительная проверка контракта между модулями

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

Для реализации таких проверок можно также использовать возможности AOP.

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

aspect ForbidAccessToModelAspect {

//      Full prohibition of access to model:
        pointcut accessModel(): call(* com.blogger.atamanenko.app.model..*.*(..));
        declare error: accessModel() : "Illegal call to model";

}

После объявления такого аспекта, мы получим ошибку компиляции, что, очевидным образом, приведёт к выполнению архитектурного ограничения.

Если же нам необходимо разрешить доступ к одному пакету только из какого-то определённого другого, то мы можем модифицировать наш аспект следующим образом:

aspect ForbidAccessToModelAspect2 {

        pointcut accessModel(): call(* com.blogger.atamanenko.app.model.**.*(..));

        // Allow access to model from specific package for methods and constructors
        pointcut allowAccessModelFromSpecificPackage(): withincode(* com.blogger.atamanenko.app.allowedpackage..*.*(..));
        pointcut allowAccessModelFromSpecificPackage2(): withincode(com.blogger.atamanenko.app.allowedpackage..*.new(..));

        // forbid usage from any other methods.
        declare error: accessModel() && !(allowAccessModelFromSpecificPackage() || allowAccessModelFromSpecificPackage()):"Illegal call to Model from forbidden package";

}


Такой аспект, созданный в нашем модуле запретит нам использовать классы модели из всех пакетов, кроме com.blogger.atamanenko.app.allowedpackage

Сборка приложения

Файл аспекта нужно положить в каталог src/main/aspect, а для сборки приложения необходимо использовать не стандартный Oracle Java Compiler, а AspectJ compiler.

Пример конфигурации для Apache Maven:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>${aspectj-maven-plugin.version}</version>
    <configuration>
        <complianceLevel>1.6</complianceLevel>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
        <verbose>true</verbose>
    </configuration>
    <executions>
        <execution>
            <phase>process-sources</phase>
            <goals>
                <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Заключение

Вот в общем-то и всё. Я сознательно не стал описывать языковые конструкции аспектов, так как они подробно описаны в руководстве AspectJ

9 comments:

  1. Добрый день, прочитал Вашу статью - интересно! =) На данный момент пытаюсь вникнуть в АОП в БД, но не совсем понимаю как можно реализовать аспекты при работе с БД (именно с точки зрения системы - код, компиляция и пр. т.к. теоретически все прозрачно и ясно). Не подскажите - где можно найти, для наглядности, простенькую БД с реализованным аспектами и хоть какое-то пояснение по реализованному "проекту" - на чем писали, как внедряли аспекты и пр. Мэйл для связи - utopia2005@yandex.ru

    ReplyDelete
    Replies
    1. Тут всё зависит от того, какие именно аспекты вас интересуют. Я, к сожалению, не могу показать вам готовое решение по причине NDA. Но могу сказать, что для полезные аспекты для БД - журналирование запросов, обработка исключений, мониторинг производительности и проверка прав доступа. Впрочем, тут тоже ничего нового.

      Delete
    2. интересует - журнализация, проверка прав доступа, защита информации. Если где-то, в самом простом виде, смогу найти пример, то будет ясно - как вообще реализовывать. Материалов, как русскоязычных, так и англоязычных, очень мало по данной тематике. Компиляторов и методов реализации/внедрения аспектов в БД вообще не смог найти. Теперь вот пытаюсь найти людей кто хоть как-то занимался данной проблемой и найти примеры/литературу.

      Delete
    3. Информации в сети достаточно, нужно лишь уметь искать. Навскидку - http://www.springer.com/computer/database+management+%26+information+retrieval/book/978-3-540-00948-1

      А вообще вопрос - для чего вам в БД АОП? Мне кажется, что у вас должны быть веские основания для этого. Ведь всё вышеперечисленное (журналирование, проверка прав доступа, защита информации) решается штатными средствами базы данных.

      Delete
    4. Спасибо за ссылку, книжка именно то, что нужно, кое чего правда нет, но для начала самое то =) Если есть еще какие-то ссылки или книги, пожалуйста скиньте ссылки, ну или сами пособия, здесь или на почту (см. первый пост). А причина использования АОП - по данной тематике в дальнейшем предполагается написание научных работ и в случае, если данная методология применительно к БД действительно актуальна, то дальнейшее ее продвижение!

      Delete
    5. Да, и кстати, я так понял,что книга Awais Rashid в свободном доступе нет и купить можно только в Европе (не в России)???

      Delete
    6. На амазоне можно заказать используя посреднические службы доставки.

      Delete
  2. к сожалению не получается заказать книжку, везде один и тот же ответ - можно сделать на заказ, но скорее всего не получится и никто заморачиваться не хочет =(
    может быть еще какие-то книжки посоветуете? пжжллааллууйййссттаааа! =)

    ReplyDelete
    Replies
    1. Можно же купить электронную версию книги для Amazon Kindle - http://www.amazon.com/Awais-Rashid/e/B001HCXO76/ref=ntt_athr_dp_pel_pop_1

      Delete