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