Новая версия Windows Azure и аспектно-ориентированное программирование (АОП)
Принципы применения АОП и 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; } } }