Главная » Spring » Применение  аспектно- ориентированного программирования

0

Хотя DI делает возможным ослабить связь между компонентами приложения,  аспектно-ориентированное   программирование   позво-

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

Аспектно-ориентированное программирование часто определяют

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

Распространение этих задач на несколько компонентов влечет за собой увеличение сложности программного кода:

# программный код, реализующий решение общесистемных за-

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

# компоненты захламляются программным кодом, не имеющим

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

Эта сложность иллюстрируется на рис. 1.2. Бизнес-объекты слева слишком тесно связаны с системными службами. Мало того, что каждый объект знает, что его операции должны фиксироваться в журнале, соответствовать требованиям безопасности и выполнять- ся в рамках транзакции, они еще несут ответственность за исполь- зование этих служб.

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

Рис. 1.2. Вызовы общесистемных задач,

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

Аспекты можно представить как обертки, охватывающие множест- во компонентов в приложении, как показано на рис. 1.3. Ядро прило- жения составляют модули, реализующие основную логику его работы. Применяя AOP, можно обернуть ядро приложения дополнительными функциональными слоями. Эти слои могут применяться декларатив- но повсюду в приложении, при этом ядро приложения может даже не подозревать об их существовании. Эта очень мощная концепция

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

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

Для демонстрации особенностей применения аспектов в Spring вернемся к предыдущему примеру и добавим в него простой аспект.

AOP в действии

Сведения о рыцарях дошли до наших времен только потому, что их подвиги были отмечены в песнях тогдашних певцов и сочини- телей баллад – менестрелей. Предположим, что вам потребовалось зафиксировать хронику приездов и отъездов вашего храброго рыца- ря BraveKnight с помощью услуг такого менестреля. В листинге 1.8 представлен класс Minstrel, который можно было бы использовать для этой цели.

Листинг 1.8. Класс Minstrel, фиксирующий средневековую историю в песнях

package  com.springinaction.knights;

public  class  Minstrel  {

public void singBeforeQuest() { // Вызывается перед выполнением задания System.out.println(ЭFa  la la; The  knight  is so  brave!Э);

}

public void singAfterQuest() {  // Вызывается после выполнения задания System.out.println(

ЭTee  hee  he;  The  brave  knight  did  embark  on  a  quest!Э);

}

}

Как видно из листинга 1.8, класс Minstrel содержит всего два ме- тода. Метод singBeforeQuest() вызывается перед отправкой рыцаря в поход, а метод singAfterQuest() – после его возвращения. Пока не видится никаких сложностей, мешающих работе этого кода, поэто- му внесем соответствующие изменения в класс BraveKnight, чтобы он мог использовать компонент Minstrel. В листинге 1.9 представлена первая попытка.

Листинг 1.9. Класс BraveKnight, который должен вызывать методы класса Minstrel

package  com.springinaction.knights;

public  class  BraveKnight  implements  Knight  {

private Quest quest; private  Minstrel  minstrel;

public  BraveKnight(Quest  quest,  Minstrel  minstrel)  { this.quest   =   quest;

this.minstrel   =   minstrel;

}

public void embarkOnQuest() throws QuestException { minstrel.singBeforeQuest();     // Должен ли рыцарь руководить quest.embark();                          // своим  менестрелем? minstrel.singAfterQuest();

}

}

Это должно сработать. Но что-то тут не так. Действительно ли рыцарь должен беспокоиться о руководстве своим менестрелем? Мне кажется, что менестрель должен просто делать свою работу, без напоминаний со стороны рыцаря. В конце концов, менестрель сам должен позаботиться о том, чтобы воспеть подвиги своего рыцаря. С какой стати рыцарь должен напоминать менестрелю, чтобы тот не забывал выполнять свою работу?

Кроме того, из-за того что рыцарь должен знать о существова- нии менестреля, мы вынуждены внедрять компонент Minstrel в ком- понент BraveKnight. Это не только усложняет реализацию класса BraveKnight, но также заставляет задаться вопросом: может ли ры- царь обойтись без менестреля? Что, если ссылка minstrel будет иметь значение null? Следует ли предусмотреть такой случай и проверять значение ссылки?

Простой класс BraveKnight начинает становиться все более слож- ным и мог бы стать еще сложнее, если бы в него пришлось добавить проверку ссылки minstrel на равенство значению null. Но благодаря использованию AOP можно просто объявить, что менестрель дол- жен воспевать подвиги рыцаря и освободить рыцаря от необходи- мости руководить действиями менестреля.

Чтобы превратить класс Minstrel в аспект, достаточно просто объ- явить его таковым в конфигурационном файле Spring. В листинге

1.10 представлена измененная версия файла knights.xml, объявляю- щая класс Minstrel  аспектом.

Листинг 1.10. Объявление класса Minstrel аспектом

<?xml  version="1.0"  encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

<bean id="knight" class="com.springinaction.knights.BraveKnight">

<constructor-arg ref="quest" />

</bean>

<bean id="quest" class="com.springinaction.knights.SlayDragonQuest"  />

<!– Объявление компонента Minstrel –>

<bean id="minstrel" class="com.springinaction.knights.Minstrel" />

<aop:config>

<aop:aspect  ref="minstrel">

<!– Объявление точки внедрения –>

<aop:pointcut  id="embark"

expression="execution(* *.embarkOnQuest(..))" />

<aop:before pointcut-ref="embark"

method="singBeforeQuest"/> <!– Операция, выполняемая до –>

<aop:after  pointcut-ref="embark"

method="singAfterQuest"/> <<!– Операция, выполняемая после –>

</aop:aspect>

</aop:config>

</beans>

Здесь, для объявления компонента Minstrel аспектом, использу- ется пространство имен aop конфигураций Spring. Во-первых, объ- ект Minstrel необходимо объявить компонентом. Затем сослаться на него в элементе <aop:aspect>. Определяя аспект далее, необходи- мо объявить (с помощью <aop:before>), что перед вызовом метода embarkOnQuest()  должен вызываться метод singBeforeQuest()  компо-

нента Minstrel. Это называется объявлением предварительной опе- рации (before advice). А затем объявить (с помощью <aop:after>), что после вызова метода embarkOnQuest() должен вызываться метод singAfterQuest(). Это называется объявлением  последующей  операции (after advice).

В обоих случаях атрибут pointcut-ref  должен ссылаться на точку

внедрения с именем embark. Эта точка внедрения определяется пред- шествующим элементом <pointcut>, атрибуту expression которого присваивается выражение, определяющее точки внедрения опера- ций. Синтаксис выражения определяется синтаксисом языка выра- жений AspectJ точек внедрения.

Не волнуйтесь, если вы незнакомы с языком AspectJ или с осо- бенностями записи выражений определения точек внедрения. Под- робнее тема аспектно-ориентированного программирования в Spring будет рассматриваться в главе 5. А пока достаточно знать, что этими объявлениями фреймворку Spring предлагается вызывать методы singBeforeQuest() и singAfterQuest() компонента Minstrel до и после вызова метода embarkOnQuest() компонента BraveKnight.

Вот и все! Добавив немного кода разметки XML, мы только что превратили компонент Minstrel в аспект Spring. Не беспокойтесь, если здесь что-то осталось непонятным – в главе 5 еще будет при- водиться множество примеров аспектно-ориентированного програм- мирования в Spring. А сейчас рассмотрим два важных следствия из этого примера.

Во-первых, компонент Minstrel так и остался простым Java-объек- том (POJO) – ничто в нем не указывает, что он должен использо- ваться как аспект. Вместо этого компонент Minstrel был превращен в аспект декларативно в контексте Spring.

Во-вторых, что особенно важно, компонент Minstrel  можно при- менить к компоненту BraveKnight вообще без его ведома. Фактиче- ски BraveKnight вообще не подозревает о существовании компонента Minstrel.

Следует также отметить, что прежде чем использовать волшеб- ство Spring по превращению объекта Minstrel в аспект, его снача- ла необходимо объявить компонентом Spring с помощью элемента

<bean>. Дело в том, что с аспектами Spring можно выполнять лю- бые операции, которые можно выполнять с другими компонентами Spring, такие как внедрение их в виде зависимостей.

С помощью аспектов воспевать подвиги рыцаря стало интерес- нее. Но поддержку аспектно-ориентированного программирования

в Spring можно использовать для решения более практичных задач. Как будет показано ниже, Spring AOP можно использовать для реа- лизации таких служб, как декларативные транзакции (глава 7) и безопасность (глава 10).

А пока познакомимся с еще одним приемом Spring, упрощающим разработку на языке Java.

Источник:   Уоллс К., Spring в действии. – М.: ДМК Пресс, 2013. – 752 с.: ил.

По теме:

  • Комментарии