Статьи

AOP стало проще с AspectJ и Spring

Недавно я начал изучать Аспектно-ориентированное программирование (АОП), и мне кажется, что это, по меньшей мере, интересно. Конечно, я был знаком с ним, так как видел, что он используется для управления транзакциями в Spring, но я никогда не рассматривал его подробно. В этой статье я хочу показать, как быстро освоиться с AOP и Spring благодаря AspectJ. Материал в этой статье основан на превосходной книге АОП «АспектJ в действии » Рамниваса Ладдада.

АОП — это не язык, а подход к разработке программного обеспечения. Как и любая методология, она имеет различные реализации, и AspectJ в настоящее время является самым богатым и полным из всех. После объединения AspectJ и AspectWerkz теперь можно создавать аспекты с использованием аннотаций.

Причина, по которой разработчики пишут код, заключается в том, чтобы обеспечить какую-то функциональность Тип функциональности не важен для этого обсуждения: некоторые могут захотеть предоставить бизнес-функциональность, другие могут написать код для исследовательских целей, другие — просто для удовольствия. Дело в том, что у любой информационной системы есть основной мотив, ключевая функциональность, которую она хочет предоставить. Например, я недавно написал PODAM , инструмент тестирования, конечной целью которого является автоматическое заполнение свойств POJO / JavaBean.

У каждой информационной системы также есть потребность в ортогональных услугах (то, что АОП называет сквозными проблемами); например, ведение журнала, безопасность, аудит, управление исключениями и так далее. В то время как информационная система может быть разделена на отдельные части функциональности (то, что АОП определяет точки соединения), ортогональные услуги необходимы по всем направлениям. Например, если кто-то хочет записать, сколько времени занимает выполнение каждого открытого метода, каждый открытый метод должен иметь что-то вроде следующего псевдокода:

01
02
03
04
05
06
07
08
09
10
11
public void someBusinessMethod() {
 
  long start = System.currentTimeInMilliseconds();
 
  doTheBusinessFunctionality();
 
  long end = System.currentTimeInMilliseconds();
 
  log.debug("The execution of someBusinessMethod took " + (end - start) + " milliseconds");
 
}

В приведенном выше методе базовая функциональность определяется только функцией someBusinessMethod (), тогда как все остальное — просто логирование. Было бы неплохо иметь что-то вроде:

1
2
3
4
5
6
7
//Some external magic happens before the invocation of this method to take the start time
public void someBusinessMethod() {
 
  doTheBusinessFunctionality();
 
}
//Some external magic happens after the invocation of this method to take the end time and logs how long the execution took.

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

Это может быть достигнуто различными способами: переплетение может происходить на уровне источника, на двоичном уровне и во время загрузки. Вы можете думать о ткаче как о компоновщике в C и C ++; исходники и библиотеки связаны между собой для создания исполняемого файла; ткач сочетает в себе Java-код и аспекты для создания расширенного поведения.

Spring достигает этого уполномоченного поведения, создавая прокси-сервер AOP вокруг кода, чье поведение должно быть обогащено. Следующий код показывает очень простой пример, основанный на AspectJ; Пример окружает выполнение простого метода некоторой службой аутентификации.

Сервисы аутентификации выглядят очень просто (дело не в том, как реализована эта функциональность, а в том, что сервис аутентификации доступен):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 *
 */
package uk.co.jemos.aop;
 
/**
 * A simple authenticator service.
 *
 * @author mtedone
 *
 */
public class Authenticator {
 
    public void authenticate() {
        System.out.println("Authenticated");
    }
     
}
 
Now let's have a look at the business logic:
 
/**
 *
 */
package uk.co.jemos.aop;
 
/**
 * A simple service which delivers messages
 * @author mtedone
 *
 */
public class MessageCommunicator {
 
    public void deliver(String message) {
        System.out.println(message);
    }
 
    public void deliver(String person, String message) {
        System.out.println(person + ", " + message);
    }
 
}

Нам хотелось бы, чтобы Authenticator вызывался до вызова любого из бизнес-методов MessageCommunicator. Используя синтаксис аннотации AspectJ, мы пишем в Aspect на чистом Java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package uk.co.jemos.aop;
 
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
 
@Aspect
public class SecurityAspect {   
 
    private Authenticator authenticator = new Authenticator();
 
    @Pointcut("execution(* uk.co.jemos.aop.MessageCommunicator.deliver(..))")
    public void secureAccess() {
    };
 
    @Before("secureAccess()")
    public void secure() {
 
        System.out.println("Checking and authenticating user...");
        authenticator.authenticate();
 
    }
 
}
1
  

Код выше немного интереснее. Аспект помечен аннотацией @Aspect. Pointcut — это некоторая точка интереса в нашем коде, где мы бы хотели, чтобы наш Aspect включился. Синтаксис

@Pointcut («выполнение (* uk.co.jemos.aop.MessageCommunicator.deliver (..))») public void secureAccess () {};

означает: «Определить Pointcut с именем secureAccess, который применяется ко всем методам доставки в классе MessageCommunicator, независимо от типа возврата такого метода». То, что следует, называется советом, и именно здесь AOP усиливает поведение нашего класса:

1
2
3
4
5
6
7
@Before("secureAccess()")
public void secure() {
 
   System.out.println("Checking and authenticating user...");
   authenticator.authenticate();
 
}

Приведенный выше код гласит: «Перед любым соответствием SecureAccess () Pointcut применяйте код в блоке». Все вышеперечисленное — чисто Java, хотя аннотации принадлежат среде исполнения AspectJ. Чтобы использовать вышеуказанный аспект с Spring, я определил контекстный файл Spring:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
 
    <aop:aspectj-autoproxy />
     
    <bean id="messageCommunicator" />
     
    <bean id="securityAspect" />
 
</beans>

Элемент XML: <aop: aspectj-autoproxy /> указывает Spring создать прокси для каждого аспекта. Теперь, когда я использую MessageCommunicator от клиента:

01
02
03
04
05
06
07
08
09
10
11
12
/**
* @param args
*/
public static void main(String[] args) {
 ApplicationContext ctx = new ClassPathXmlApplicationContext(
  "classpath:aop-appContext.xml");
 
 MessageCommunicator communicator = ctx.getBean("messageCommunicator",
  MessageCommunicator.class);
 communicator.deliver("Hello World");
 communicator.deliver("Marco", "Hello World");
}

Я получаю следующий вывод:

ИНФОРМАЦИЯ: Загрузка определений XML-бинов из ресурса пути к классу [aop-appContext.xml] 15 мая 2011 г. 11:51:41
org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
ИНФОРМАЦИЯ: Предварительные экземпляры синглетов в org.springframework.beans.factory.support.DefaultListableBeanFactory@21b64e6a: определение bean-компонентов [org.springframework.aop.config.internalAutoProxyCreator, messageCommunicator, securityAspect];
корень фабричной иерархии Проверка и аутентификация пользователя… Аутентифицированный Hello World
Проверка и аутентификация пользователя… Аутентифицированный Marco, Hello World

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

Очевидно, что при внедрении AOP существуют недостатки, в основном это кривая обучения, которая требуется разработчикам для ознакомления с технологией. AspectJ определяет свой собственный язык и синтаксис, как показано в примере выше); аннотация @Before является лишь одной из возможностей: советы могут применяться до, после, вокруг объектов; Кроме того, синтаксис для определения Pointcuts не Java, а скорее как скрипт. Аспекты AspectJ также имеют ключевые слова и нативные объекты для захвата контекста точек соединения, которые они советуют, и этот синтаксис необходимо изучить. Тем не менее, потенциальные выгоды значительно увеличиваются благодаря дополнительным усилиям, необходимым для изучения этой новой и интересной технологии.

Справка: AOP стало проще с AspectJ и Spring от нашего партнера по JCG