Unity 2.0 предоставляет нам три вида перехвата:
- InterfaceInterceptor для перехвата методов интерфейса
- VirtualMethodInterceptor для перехвата виртуальных методов
- TransparentProxyInterceptor для перехвата методов у объектов типа MarshalByRefObject
Для простоты продемонстрирую это на примере: предположим нам необходимо реализовать транзакционный метод (метод помеченный атрибутом [TransactionMethod] будет автоматически выполняться в транзакции, см атрибут [Transaction] в Spring.NET).
Рассмотрим интерфейс и его имплементацию. В интерфейсе у нас имеется метод, декларативно(!) помеченный как транзакционный. Имплементация по сути не важна.
public interface IAccountService
{
// Хотим чтобы метод Withdraw выполнялся под транзакцией,
// поэтому пометим его специальным атрибутом.
[TransactionMethod()]
void Withdraw(decimal amount);
}
public class AccountService : IAccountService
{
public void Withdraw(decimal amount)
{
if (amount < 0 || amount > 1000)
throw new ArgumentOutOfRangeException("amount");
}
}
Собственно атрибут. Видно, что он наследуется от HandlerAttribute, именно атрибуты такого типа по-умолчанию и обрабатываются перехватчиком.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class TransactionMethodAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
// Перехватчик для каждого атрибута HandlerAttribute,
// вызывает метод CreateHandler, который возвращает
// конкретный обработчик ICallHandler.
return new TransactionMethodCallHandler();
}
}
Вся логика сосредоточена в обработчике вызова ICallHandler.Invoke:
public class TransactionMethodCallHandler : ICallHandler
{
// Главный метод обработчика ICallHandler.
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
IMethodReturn result;
// открываем транзакцию
using (TransactionScope transaction = new TransactionScope())
{
result = getNext()(input, getNext);
if (result.Exception == null)
{
// исключений не возникло, завершим транзакцию
transaction.Complete();
}
}
return result;
}
public int Order { get; set; }
}
Продемонстрируем наш пример:
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
// Настроим контейнер.
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Configure(container);
// Запросим у контейнера IAccountService.
IAccountService service = container.Resolve<IAccountService>();
// Полученный service - прокси-класс делегирующий AccountService.
// Если мы попробуем узнать тип объекта service, то мы получим нечто вида:
// DynamicModule.ns.Wrapped_IAccountService_af75f3474a9d4de2b1fe6aa1d26a8e36
service.Withdraw(33); // транзакция пройдет успешно
service.Withdraw(-6); // транзакция откатится
}
Чтобы все это заработало, осталось настроить Unity контейнер через config файл. Укажем правило разрешения типа (mapping) и тип перехвата InterfaceInterceptor:
<?xml version="1.0" encoding="utf-8" ?>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"></sectionExtension>
<!-- Заведем псевдонимы для известных типов -->
<alias alias="IAccountService" type="UnityTest.IAccountService, UnityTest" />
<alias alias="AccountService" type="UnityTest.AccountService, UnityTest" />
<container>
<extension type="Interception"/>
<register type="IAccountService" mapTo="AccountService">
<interceptor type="InterfaceInterceptor"/>
<policyInjection/>
</register>
</container>
</unity>
PS: Пример можно доработать, добавив дополнительные параметры настройки транзакции, например:
[TransactionMethod(TransactionScopeOption.Suppress)]
Ссылки:
Спасибо за статью. У меня есть вопрос с перфомансом, насколько он ниже, если сранвивать с тем же PostSharp-ом?
ОтветитьУдалитьТак как я понял, Unity генерит прокси на лету, в отличии от PostSharp-а, который делает вставки кода в момент компиляции.
@Sergey Litvinov
ОтветитьУдалитьPerformance я не сравнивал, так как не пользуюсь PostSharp (он вроде бы стал платным). Вероятно PostSharp будет быстрее, это все-таки заранее сгенерированный код, хотя возможно Unity делает предварительною генерацию проксей перед пуском приложения, но я придерживаюсь реализации AOP на Unity из-за то, что мы вдобавок к AOP получаем еще и DI/IoC контейнер.
Кстати вот еще хорошая ссылка:
http://ayende.com/Blog/archive/2007/07/02/7-Approaches-for-AOP-in-.Net.aspx
Заглянул в исходники Unity, и выяснил что на время выполнения, Unity сохраняет сгенерированные прокси во временную сборку Unity_ILEmit_InterfaceProxies используя System.Reflection.Emit (http://unity.codeplex.com/SourceControl/changeset/view/63122#427069)
ОтветитьУдалитьhttp://gandjustas.blogspot.com/2011/04/unity-20-interception.html
ОтветитьУдалить@gandjustas
ОтветитьУдалитьКстати, если мы настроим перехватчик в xml-файле, а не в коде, то мы без модификации последнего сможем подключать/отключать его, это хорошо распространяется, например, на диагностические перехватчики не влияющие на логику системы.
Да, зачастую такие вещи как логирование и Performance Counters лучше устанавливать в конфиге, а то что касается БЛ (транзакционность, кеширование, протаскивание контекста) иметь в коде.
ОтветитьУдалить"В Лиспе, если охота аспектно-ориентированного программирования, нужно лишь настругать немного макрокоманд, и готово. В Java, нужен Грегор Кичалес, создающий новую фирму, и месяцы и годы попыток заставить всё работать." П.Норвиг
ОтветитьУдалитьВижу, в C# -- то же мракобесие, что и у нас на Java. Кажется, мы когда-то выбрали синюю пилюлю вместо красной...
@Dmitry
ОтветитьУдалитьAOP'ом должен управлять framework, в .NET он пока отсутствует, поэтому приходиться заполнить пробел подручными средствами, например Unity. Но .NET же не стоит на месте.
Да, бесспорно, если нет ничего лучше, то приходится юзать то, что есть. На работе я с java мучаюсь точно так же, как и ты с C#-ом, из-за бедных средств обобщенного программирования в языке. Что C#, что Java -- языки слишком низкоуровневые и невыразительные. Если бы у них были средства нормального метапрограммирования, то не пришлось бы юзать AOP-фреймворки.
ОтветитьУдалитьВоспоминание Кенни Тилтона:
Я помню как Грегор Кичалес на ILC 2003 [Международная конференция по Лиспу], рассказывая об AspectJ безмолвной толпе, остановился, затем жалобно добавил «Когда я показал это Java-программистам они встали и заапплодировали».
а не подскажете чем можно перехватывать запросы в unity web player
ОтветитьУдалитьК сожалению я не знаком с Unity (которая game engine)
ОтветитьУдалить