Санкт-Петербургский государственный университет
Опубликован: 01.07.2013 | Доступ: свободный | Студентов: 448 / 118 | Длительность: 16:00:00
Лекция 14:

Новая версия Windows Azure и аспектно-ориентированное программирование (АОП)

< Лекция 13 || Лекция 14: 1234 || Лекция 15 >

Принципы применения АОП и Aspect.NET для новой версии Azure на примере: Web-роль, на странице которой тестируется Logging Application Block

Для практического ознакомления с EL Integration Pack for Windows Azure компания Microsoft предлагает серию лабораторных работ (Hands-on Labs). Программисту предоставляется исходный проект и методические указания по его пошаговому изменению. Полученный результат можно сравнить с эталонным конечным проектом, в котором теперь задействуется тот или иной функциональный блок EL Integration Pack for Windows Azure. Если на основе этих методических указаний составить аспект и применить его с помощью Aspect.NET к начальному проекту, то результирующая сборка будет обладать функциональностью соответствующего эталонного проекта, но без модификации кода исходного проекта. Однако стоит учитывать, что изменения в конфигурационные файлы исходного проекта необходимо все же вносить вручную, поскольку АОП позволяет удалять сквозную функциональность лишь из программного кода.

Рассмотрим упражнение "Hands-on Lab 1: Using the Logging Application Block with Windows Azure Storage", где путем добавления ссылок на сборки EL производится подключение функционального блока логгирования к исходному проекту, а затем вызов его метода для передачи сообщения в облачное хранилище диагностической информации WAD (листинг 1). Это дает возможность настраивать параметры сбора и хранения отладочных сообщений через графический интерфейс Logging Application Block, либо через его конфигурационные файлы.

//Веб-роль, на странице которой тестируется Logging Application Block
public partial class Default : System.Web.UI.Page {
//Сообщение отсылается в обработчике щелчка мыши по кнопке страницы
protected void LogButton_Click(object sender, EventArgs e) {
Microsoft.Practices.EnterpriseLibrary.Logging.
     Logger.Write("Message from the Logging Application Block");
     }
}

Итак, наша задача заключается в том, чтобы перенести все зависимости от EL и вызовы методов протоколирования в отдельный проект с аспектом. Применив затем с помощью Aspect.NET данный аспект к исходному проекту, мы получим его бесшовную интеграцию с Logging Application Block.

Традиционно, в Aspect.NET подобные задачи решаются размещением кода протоколирования в действии аспекта и вставкой его перед, после, или вместо вызова целевого метода в исходном проекте. В нашем случае, целевой метод – это обработчик события щелчка мыши LogButton_Click() класса веб-страницы Default, причем созданием объекта этого класса и отправкой ему событий занимается среда ASP.NET и сервер IIS. Это означает, что код вызова нашего целевого метода располагается вне сборки исходного проекта и недоступен Aspect.NET.

По мнению авторов, перехват вызовов методов, которые реагируют на внешние события, может быть осуществлен через наследование классов. Если в аспектном проекте создать класс, который наследует от целевого класса, а затем подменить им свой базовый класс в сборке исходного проекта, то требуемый перехват можно осуществить в переопределенном виртуальном методе (см. листинг 2). Специальный пользовательский атрибут [ReplaceBaseClass] предписывает компоновщику Aspect.NET заменить целевой класс своим аспектным наследником:

  • Заменить в исходной сборке все вызовы методов базового целевого класса (в том числе и конструкторы) на вызовы методов его наследника в аспектной сборке.
  • Принудительно объявить виртуальными те методы целевого класса, которые переопределены в замещающем его наследнике. Если они закрыты (private), то сделать их защищенными (protected).
  • Если вызов этих методов в исходной сборке производится с помощью MSIL-инструкции call или ldftn, заменить их на callvirt и ldvirtftn соответственно.
  • Объединить с помощью инструмента ILRepack (из проекта Mono.Cecil) сборки с аспектом и исходную.
  • Присвоить какое-нибудь служебное имя базовому целевому классу, а его первоначальное имя – замещающему наследнику из аспекта.

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

//Проект с замещающим аспектным наследником
[AspectDotNet.ReplaceBaseClass]
public class AspectClass : Default {
protected void LogButton_Click(object sender, EventArgs e) {           
Microsoft.Practices.EnterpriseLibrary.Logging.
     Logger.Write("Message from the Logging Application Block");
            base.LogButton_Click(sender, e);
      }
}
 
//Исходный проект, после отделения зависимости от Logging Application Block
public partial class Default : System.Web.UI.Page {
protected void LogButton_Click(object sender, EventArgs e) {}
}

Второй пример применения АОП для новой версии Azure

Рассмотрим теперь "Hands-on Lab 6: Implementing Throttling Behavior", где иллюстрируется ограничение функциональности под нагрузкой с использованием сервисов функционального блока Autoscaling Application Block. Отдельный компонент Autoscaler занимается мониторингом диагностической информации и, в зависимости от текущей нагрузки на облако, устанавливает свойство ThrottlingMode в файле конфигурации исходного проекта. В зависимости от значения этого свойства какие-то из методов класса веб-страницы могут изменять свое поведение (листинг ниже).

//Веб-роль, на странице которой тестируется Autoscaling Application Block
public partial class Default : System.Web.UI.Page {
override void OnPreRenderComplete(EventArgs e) {
            base.OnPreRenderComplete(e);
            string throttlingMode = 
                RoleEnvironment.GetConfigurationSettingValue("ThrottlingMode");
            switch (throttlingMode)
            {
                case "HighActivity":
                    this.ThrottlingLabel.Text = "Работа при высокой активности…";
                    break;
                default:
                    this.ThrottlingLabel.Text = "Работа при обычной  активности…";
       this.DoSomeUsualWork();
                    break;
            }
      }
 
 private void DoSomeUsualWork() {/*…*/}
}

Данный метод можно перенести в аспект и тогда целевой класс будет сконцентрирован только на решении своей задачи, в то время как компонент Autoscaler и бесшовная интеграция с аспектом обеспечит реакцию на повышенную нагрузку. Задачу можно было бы решить аналогично предыдущему примеру, но здесь есть препятствие в виде вызова закрытого в целевом классе метода DoSomeUsualWork(). Для того, чтобы он стал доступным замещающему наследнику, компоновщик аспектов мог бы принудительно сделать этот метод защищенным. Однако это нарушит инкапсуляцию целевого класса, и единственный способ сохранить ее – использовать рефлексию .NET. Закрытые члены целевого класса становятся полями его аспектного наследника, которые инициализируются в конструкторе. Также предположим, что в замещающем наследнике целевого класса нам понадобится вызвать метод OnPreRenderComplete следующего по иерархии базового класса System.Web.UI.Page. Защищенные и открытые методы целевого класса используются в его замещающем аспектном наследнике без ограничений. Итоговый аспект представлен в листинге ниже.

using System.Reflection;
[AspectDotNet.ReplaceBaseClass]
public class AspectClass : _Default {
MethodInfo DoSomeUsualWork;
 
      public AspectClass() {
      BaseType = this.GetType().BaseType;
    //Получение ссылки на закрытый метод целевого класса _Default
            DoSomeUsualWork = BaseType.GetMethod("DoSomeUsualWork", 
BindingFlags.NonPublic | BindingFlags.Instance);
    //Ссылка на метод базового класса System.Web.UI.Page
PageOnPreRenderComplete = base.GetType().BaseType.
GetMethod("OnPreRenderComplete", BindingFlags.NonPublic | BindingFlags.Instance);
      }
        
      protected override void OnPreRenderComplete(EventArgs e) {
    //Вызываем метод базового класса System.Web.UI.Page
         PageOnPreRenderComplete.Invoke(this, new object[] { e });
         string throttlingMode = 
                   RoleEnvironment.GetConfigurationSettingValue("ThrottlingMode");
              switch (throttlingMode) {
                case "HighActivity":
   //Использование в аспекте члена целевого класса _Default
                    this.ThrottlingLabel.Text = "Работа при высокой    активности…";
                    break;
                default:
                    this.ThrottlingLabel.Text = "Работа при обычной активности…";
     //Вызов закрытого члена целевого класса _Default
  DoSomeUsualWork.Invoke(this, null);
                    break;
         }
      }
}
< Лекция 13 || Лекция 14: 1234 || Лекция 15 >