Специализированные атрибуты (Часть 1)
Авторизация нагружает
Существует множество мощных структур авторизации, но каждая из них требует много работы: добавление стандартного кода для каждого метода. А что, если в середине проекта вы решите изменить одну структуру на другую? Вам придется модифицировать все методы авторизации, а их может быть тысяча.
Авторизация нагружает, потому что, как и множество нефункциональных требований (безопасность, транзакции, кэширование …), она пересекается со всеми функциональными требованиями. Если у вас сотня бизнес-процессов, которые требуют авторизацию, безопасность и поддержку транзакций, то вы, скорее всего, будете иметь инструкции авторизации, безопасности и транзакций в каждом из данных процессов. Поэтому нам необходим лучший путь инкапсуляции, способ, который позволит нам модифицировать каждый метод, к которому он будет применим.
Специализированные атрибуты являются хорошим решением данной проблемы.
Тривиальный специализированный атрибут авторизации
То, что нам необходимо, очень просто: специализированный атрибут, который записывает сообщение до и после выполнения метода, к которому он будет применен. Мы также захотим указать категорию трассировки, используя конструктор специализированного атрибута.
Итак, в идеале нам хотелось бы использовать специализированный атрибут следующим образом:
[Trace("MyCategory")]
void SomeTracedMethod()
{
// Тело метода.
}
Пора это реализовать - мы можем начать с объявления специализированного атрибута стандартным образом. Все что нам нужно это поле, названное category и конструктор, инициализирующий данное поле:
public sealed class TraceAttribute : Attribute
{
private readonly string category;
public TraceAttribute( string category )
{
this.category = category;
}
public string Category { get { return category; } }
}
Затем мы можем добавить все вещи, которые позволяют данному атрибуту по-настоящему изменять методы, к которым он будет применен. Как уже оговаривалось во введении, мы будем использовать PostSharp для данной работы, поэтому давайте добавим его к проекту:
Все что нам необходимо выполнить, так это заставить наш специализированный атрибут наследовать PostSharp.Laos.OnMethodBoundaryAspect вместо System.Attribute. Этот класс определяет новые методы, которые будут вызваны во время выполнения целевых методов:
- OnEntry – до того как метод будет выполнен.
- OnSuccess – когда метод возвращается успешно (без исключений).
- OnException – когда метод выходит с исключением.
- OnExit – когда метод выходит, несмотря на успешность.
Мы только реализуем методы, которые нам необходимы: OnEntry и OnExit.
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
// Здесь необходимо добавить код трассировки.
}
public override void OnExit(MethodExecutionEventArgs eventArgs)
{
//Здесь необходимо добавить код трассировки.
}
Реализация данных методов должна вызывать System.Diagnostics.Trace.WriteLine. Но как мы можем узнать название метода, в котором мы находимся? Без проблем - вся необходимая информация содержится в объекте MethodExecutionEventArgs, который передается OnEntry и OnExit. Нам интересно свойство eventArgs.Method, но если мы также хотим записывать значения параметров, мы можем получить это, используя метод GetArguments().
Вот конечная реализация нашего трассирующего специализированного атрибута:
[Serializable]
public sealed class TraceAttribute : OnMethodBoundaryAspect
{
private readonly string category;
public TraceAttribute( string category )
{
this.category = category;
}
public string Category { get { return category; } }
public override void OnEntry( MethodExecutionEventArgs eventArgs )
{
Trace.WriteLine(
string.Format( "Entering {0}.{1}.",
eventArgs.Method.DeclaringType.Name,
eventArgs.Method.Name ),
this.category );
}
public override void OnExit( MethodExecutionEventArgs eventArgs )
{
Trace.WriteLine(
string.Format( "Leaving {0}.{1}.",
eventArgs.Method.DeclaringType.Name,
eventArgs.Method.Name ),
this.category );
}
}
Вы ничего не заметили? Класс оснащен специализированным атрибутом Serializable. Это необходимо для каждого атрибута, который составлен в PostSharp Laos.
Теперь давайте испробуем данный атрибут на примере:
internal static class Program
{
private static void Main()
{
Trace.Listeners.Add(new TextWriterTraceListener( Console.Out));
SayHello();
SayGoodBye();
}
[Trace( "MyCategory" )]
private static void SayHello()
{
Console.WriteLine("Hello, world." );
}
[Trace("MyCategory")]
private static void SayGoodBye()
{
Console.WriteLine("Good bye, world.");
}
}
Давайте выполним программу и увидим запись вызванных методов.
Как же все сработало? Если вы посмотрите на окно Output в Visual Studio, то вы увидите, что PostSharp был вызван во время процесса сборки.
PostSharp на самом деле изменил выходные результаты компилятора C# и улучшил сборку таким образом, что методы нашего трассирующего атрибута вызываются во время выполнения программы.
Изучив результирующую сборку при помощи Lutz Roeder's Reflector, мы можем увидеть много интересного:
Déjà vu?
Если вы думаете о том, что это очень похоже на Аспектно-ориентированное программирование (АОП), то вы правы - PostSharp Laos на самом деле представляет собой не что иное, как АОП-структуру.
Аспект определен как модуль или класс, реализующий сквозную функциональность. Аспект изменяет поведение остального кода, применяя совет в точках соединения, определённых некоторым срезом. В большинстве случаев в бизнес-приложениях, аспект является нефункциональным требованием, как авторизация, безопасность, управление транзакциями, обработка исключений или кэширование. Данное разделение является одним из основных принципов теории разработки программного обеспечения. Оно гласит о том, что фрагменты кода, реализующие одинаковое решение, должны быть сгруппированы в компоненты. Измерение качества разработки заключается в высоком уровне сцепления, но низком уровне связанности компонент.
АОП-структура позволяет инкапсулировать аспекты в модульные сущности - в PostSharp Laos, данные сущности являются специализированными атрибутами. Основным преимуществом данного подхода является простота: вы получаете АОП без типичной кривой обучаемости. В дополнение PostSharp Laos независим от языка, и его интеграция в Visual Studio (Intellisense, отладчик …) великолепна.
Другой функцией, которая отличает PostSharp, является то, что он оперирует на уровне MSIL после компиляции. Он не имеет ограничения решений на основе прокси - вы можете добавлять аспекты к закрытым методам (private) и вам не нужно наследовать классы от MarshalByRefObject и т.д.
Если вы заинтересованы в АОП, то вам стоит изучить данную область для вашей же пользы.
Шаг вперед: множественное применение специализированных атрибутов
Итак, у нас есть трассирующий специализированный атрибут. Но что, если мы хотим иметь сотни методов для трассировки? Необходимо ли нам добавлять данный специализированный атрибут к каждому из них? Конечно же - нет! Благодаря такому механизму, как множественное применение атрибутов, мы можем применить специализированный атрибут ко множеству методов в одной единой строке.
К примеру, добавление TraceAttribute к классу Program на самом деле применяет атрибут к каждому методу в классе:
[Trace( "MyCategory" )]
internal static class Program
{
…
И теперь если мы не хотим применить данный специализированный атрибут к методу Main, мы можем ограничить набор методов и названий методов, к которым он будет применен:
[Trace( "MyCategory", AttributeTargetMembers = "Say*")]
internal static class Program
{
…
В качестве альтернативы мы можем добавить специализированный атрибут к уровню сборки для трассировки всех методов, определенных в этой сборке:
[assembly: Trace("MyCategory")]
Тем не менее, необходимо избежать применения атрибута к самому классу TraceAttribute, потому что аспект не может аспектировать сам себя:
[Trace( null, AttributeExclude = true )]
[Serializable]
public sealed class TraceAttribute : OnMethodBoundaryAspect
{
…
Вывод
В данной статье мы продемонстрировали способ разработки специализированных атрибутов, которые действительно добавляют новое поведение вашим .NET программам. Для демонстрации мы использовали PostSharp Laos - аспектно-ориентированное решение от .NET Framework.
Три года спустя после создания PostSharp стал зрелым проектом. На момент написания статьи PostSharp был загружен множество раз, и на данный момент существует версия 1.0 и она находится на этапе выпуска. PostSharp, как сообщают, используется многими компаниями, как независимыми продавцами программных продуктов, так и интеграторами систем. Поставщик предоставляет поддержку, консультации и спонсированную разработку, тем самым заботясь о проекте.
Мы попытались показать в данной статье,что PostSharp на самом деле модифицирует MSIL-инструкции, тем самым дополнительные поведения вызываются во время выполнения. Вы можете увидеть, как все работает, при помощи рефлектора Lutz Roeder's Reflector. Данная статья представляла специализированные атрибуты, которые добавляют блок try-catch всем методам, но также вы можете перехватывать вызовы, выполненные в другой сборке для того, чтобы перехватывать доступ к полям или производить инъекции интерфейсов в типы.
В одной из следующих статей мы продемонстрируем способ разработки двух новых специализированных атрибутов: счетчика производительности и проверку полей.
Автор: Gael Fraiteur
Загрузить исходный код - 16.8 KBЗагрузить PostSharp (PostSharp - бесплатно доступен всем )