Автоматическая реализация INotifyPropertyChanged на Spring.NET

Feb 04, 2012 04:34


Запись опубликована Техно-логи. You can comment here or there.

В статье ранее я писал как сделать автоматическую реализацию INotifyPropertyChanged на основе расширений библиотеки NInject. К сожалению, моя жизнь с этой библиотекой не сложилась, NInject был заменен на Spring.NET. При этом схему автореализации надо было как-то перенести без особенных изменений прикладного кода. Объясню, что именно я сделал.


Для начала нам нужен интерфейс, который будет реализовываться всеми потенциальными автонотифицируемыми классами:

public interface IAutoNotifyPropertyChanged : INotifyPropertyChanged { void OnPropertyChanged(string propertyName); }
Удобства ради можно использовать базовый класс для всех авто-реализаций:

public class ViewModelBase : IAutoNotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } }
Кроме этого, нам понадобятся атрибуты, которыми мы будет включать/отключать авто-нотификацию:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class NotifyOfChangesAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class DoNotNotifyOfChangesAttribute : Attribute { }
Теперь пришла очередь Pointcut, которые будет определять setter-ы целевых свойств:

public class AutoNotifyPointcut : StaticMethodMatcherPointcut { public override bool Matches(MethodInfo method, Type targetType) { if (!typeof(IAutoNotifyPropertyChanged).IsAssignableFrom(targetType)) return false; // non-setters ignored if (!method.Name.StartsWith("set_")) return false; object[] attributes = targetType.GetCustomAttributes(typeof(NotifyOfChangesAttribute), true); if (attributes.Length > 0) { object[] disables = method.GetCustomAttributes(typeof (DoNotNotifyOfChangesAttribute), true); if (disables.Length > 0) return false; return true; } attributes = method.GetCustomAttributes(typeof (NotifyOfChangesAttribute), true); return attributes.Length > 0; } }
Затем нам нужен advice, в котором и будет происходить вся работа:

public class AfterPropertySetAdvice : IMethodInterceptor { private static readonly Logger logger = LogManager.GetCurrentClassLogger(); public object Invoke(IMethodInvocation invocation) { object proxy = invocation.Proxy; string name = null; IAutoNotifyPropertyChanged model = proxy as IAutoNotifyPropertyChanged; if (model == null) { logger.Error("Advice is called on non-IAutoNotifyPropertyChanged class"); } else { name = invocation.Method.Name; if (!name.StartsWith("set_")) { logger.Error("Cannot notify on non-setter"); } name = name.Substring(4); } object rval = invocation.Proceed(); if (name != null) { model.OnPropertyChanged(name); } return rval; } }
Тут для вящего удобсва отладки используется NLog. Его использование можно вовсе удалить.

Так, классы созданы, надо как-то их применить к объектам. Сразу скажу, что из-за особенностей Spring AOP для оборачивания класса в advice надо, чтобы экземпляр класса создавался Spring-ом. Код подключения выглядит примерно так:

Можно заметить, что оборачиванию будут подвергнуты объекты, id которых заканчивается на «Model». Анонимные объекты будет проигнорированы.

Кажется, что ограничение по имени какое-то странное. Также кажется, что использование InheritanceBasedAopConfigurer выглядит странно в контексте наличия тега aop:config в Spring.NET. Поясню, почему именно так.

В рамках Spring.NET существует 3 способо создания proxy объектов для вызова advice:
  1. Aggregation based - для объекта-обертки генерируется DynamicProxy со своим интерфейсом. Такой объект нельзя привести к оригинальному типу target объекта. Реальный объект в этом случае будет аггрегирован внутри proxy. Нам не подходит из-за несовместимого интерфейса.
  2. Inheritance based - объект-прокси будет наследником класса target объекта, при этом обернуты будут только virtual методы и свойства оригинала. Оригинальный объект будет аггрегирован внутри proxy. В нашем случае это дает интересный спец-эффект: поскольку события тоже проксируются, то добавление обработчика события на объект-обертку не влияет на оригинальный объект. Значит, если внутри advice вызвать событие, то вызов будет превращен в вызов объекта-оригинала, а не обертки. А на внутреннем объекте обработчиков как небыло так и нет. Значит, тоже не подходит
  3. InheritanceBasedAopConfigurer - новый способ генерации вызовов, добавленный в Spring 1.2. Идея состоит в изменении кода оригинального объекта. Вот это нам подходит, но дает указанные выше ограничения на создание объектов

Пример создания объекта:

development, c#, spring.net

Previous post Next post
Up