Добавить комментарий - CodeHelper

Добавить комментарий

Это вторая часть из моей серии статей о внутреннем устройства Spring.NET AOP. В первой статье я описал первый шаг в разделении поведения и то, как мы можем использовать шаблон "Декоратор", чтобы сделать наш код более легким в сопровождении. Однако мы до сих пор имеем несколько важных проблем:

  • Изменение интерфейса нашего сервиса требует изменение всех декораторов
  • Невозможность использования данных декораторов для других сервисов

Более гибкий подход: перехватчик

Как было сказано ранее, мы можем улучшить нашу реализацию. Представьте, что мы имеем другие сервисы в нашем приложении. И мы не хотим реализовывать кеширование или операцию повтора каждый раз для каждого метода. Дублирование кода - это дорога в ад. Таким образом нам необходимо улучшить наше решение. Во-первых, давайте сделаем рефакторинг нашего кода операции повтора в отдельный класс, который может быть использован для оборачивания вызова любого метода. Мы введем новый специальный интерфейс для добавления нужного поведения и назовем его перехватчиком (от англ. interceptor):

public interface IMethodInterceptor
{
 object InvokeMethod(Func<object> invokeNext);
}

Реализация перехватчика принимает делегат в качестве параметра. Данный делегат может быть как обычным методом, так и другим перехватчиком. Метод, который мы оборачиваем дополнительным поведением, обычно называют целевым методом (от англ. target method). В том случае, если делегат - это другой перехватчик, то это называется цепочкой перехватчиков (от англ. interceptor chain). В таком случае каждый перехватчик добавляет свое собственное поведение перед вызовом целевого метода. Наш Retry-interceptor будет выглядить следующим образом:

public class RetryInterceptor : IMethodInterceptor
{
 public object InvokeMethod(Func<object> invokeNext)
 {
   int retries = 0;
   while (true)
   {
     try
     {
       return invokeNext();
     }
     catch (Exception ex)
     {
       retries++;
       if (retries >= 3)
       {
         throw; // retry threshold exceeded, giving up
       }
       Thread.Sleep(1000); // wait a second
     }
   }
 }
}

Отметьте, что нашему RetryInterceptor абсолютно неважен вид целевого метода или его аргументы. Он всего лишь принимает делегат, который может быть следующим перехватчиком в цепочке или же вызовом реального метода.

Теперь мы можем реализовать нашу цепочку перехватчиков еще более гибким способом. Мы создадим класс, который назовем CalculatorProxy, и передадим ему реальный метод и список необходимых перехватчиков. Вызов метода в данном случае несколько неочевиден, но с этим вполне можно справиться:

public class CalculatorProxy : ICalculator
{
 private ICalculator target;
 private IMethodInterceptor[] interceptors;

 public CalculatorProxy(ICalculator target, IMethodInterceptor[] interceptors)
 {
   this.target = target;
   this.interceptors = interceptors;
 }

 public int Add(int x, int y)
 {
   return (int) InvokeInterceptorAtIndex(0, ()=>target.Add(x, y));
 }

 private object InvokeInterceptorAtIndex(int interceptorIndex, Func<object> targetMethod)
 {
   if (interceptorIndex >= interceptors.Length)
   {
     return targetMethod();
   }
   return interceptors[interceptorIndex].InvokeMethod(
           () => InvokeInterceptorAtIndex(interceptorIndex + 1, targetMethod)
       );
 }
}

Прокси принимает все внешние вызовы ICalculator.Add() и отдает их на выполнение первому перехватчику в цепочке. Прокси инициализируется следующим образом:

ICalculator calc = new CalculatorProxy(
                       new CalculatorWebService(),
                       new IMethodInterceptor[] { new RetryInterceptor() });
int sum = calc.Add(2, 5);

Каждый перехватчик будет вызывать следующего перехватчика в цепочке или целевой метод, если цепочка закончилась:

alt text

Генерация прокси - Spring's ProxyFactory

Конечно на данный момент мы реализовали наш механизм перехватывания только для одного метода. Теперь представим, что мы хотим расширить функциональность нашего калькулятора и добавить к нему метод Divide(). Взгляните на то, как будет выглядеть реализация этого метода в proxy:

public double Divide(double dividend, double divisor)
{
 return (int)InvokeNext(0, () => target.Divide(dividend, divisor));
}

Сравнив это с реализацией метода Add(), мы можем заметить очевидное сходство. Оба метода передают контроль первому перехватчику в цепочке, а так же передают ему вызов реального метода. Независимо от того, сколько методов мы добавим, код всегда будет выглядеть одинаково. Давайте выделим общий код вызова цепочки перехватчиков в в базовый класс AbstractProxy:

public abstract class AbstractProxy
{
 private IMethodInterceptor[] interceptors;

 public AbstractProxy(IMethodInterceptor[] interceptors)
 {
   this.interceptors = interceptors;
 }

 protected object InvokeInterceptorAtIndex(int interceptorIndex, Func<object> targetMethod)
 {
   if (interceptorIndex >= interceptors.Length)
   {
     return targetMethod();
   }
   return interceptors[interceptorIndex].InvokeMethod(
       () => InvokeInterceptorAtIndex(interceptorIndex + 1, targetMethod)
     );
 }
}

Тогда наш CalculatorProxy примет вид:

public class CalculatorProxy : AbstractProxy, ICalculator
{
 private readonly ICalculator target;

 public CalculatorProxy(ICalculator target, IMethodInterceptor[] interceptors)
   : base(interceptors)
 {
   this.target = target;
 }

 public int Add(int x, int y)
 {
   return (int)InvokeInterceptorAtIndex(0, () => target.Add(x, y));
 }

 public double Divide(double dividend, double divisor)
 {
   return (int)InvokeInterceptorAtIndex(0, () => target.Divide(dividend, divisor));
 }
}

Даже, если мы реализуем другие интерфейсы, эти строки не изменятся. К счастью, Castle.DynamicProxy, LinFu и конечно Spring.NET уже реализуют возможность генерации этого кода в run-time. Далее я покажу, как вы можете использовать спринговский класс ProxyFactory для этой цели.

Далее

Внимание! Вы собираетесь отправить информацию от имени анонимного пользователя.
v1.7.123.556
© 2009—2010 CodeHelper FAQ | О сайте | Обратная связь | История изменений | Статьи
Creative Commons LicenseМатериалы сайта распространяются под лицензией Creative Commons Attribution-Share Alike 3.0 Unported.