Monday, September 22, 2008

What's wrong with Spring.Net Interceptors?

When I began using of Spring.Net interceptors part of my code stopped working! For example such code:


public class POBlo
{
    private IDataProvider _dataProvider = null;
    // Yes, I know about auto property :)
    protected virtual IDataProvider DataProvider
    {
        get { return _dataProvider; }
        set { _dataProvider = value; }
    }
 
    public virtual void Save (Entity entity)
    {
        // Here we have NullReferenceException :(
        DataProvider.Save (entity);
    }
}

Before I added interceptor all were OK. I could save entity and no exception was occurred.

After some investigation I've found the problem: intercepted target object has right value of this property but proxy object does not expose it!
Finally I opened Spring.Net sources and found the Spring.Proxy.AbstractProxyTypeBuilder's InheritType method the proxies my class:



protected virtual void InheritType(TypeBuilder typeBuilder,
            IProxyMethodBuilder proxyMethodBuilder, Type type, bool declaredMembersOnly)
{
    IDictionary methodMap = new Hashtable();
    IList finalMethods = new ArrayList();
 
    BindingFlags bindingFlags = BindingFlags.Public  BindingFlags.Instance;
    if (declaredMembersOnly)
    {
        bindingFlags = BindingFlags.DeclaredOnly;
    }
 
    // override virtual methods
    MethodInfo[] methods = type.GetMethods(bindingFlags);
    foreach (MethodInfo method in methods)
    {
        if (method.IsVirtual && !method.IsFinal)
        {
            MethodBuilder methodBuilder = proxyMethodBuilder.BuildProxyMethod(method, null);
            ApplyMethodAttributes(methodBuilder, method);
            methodMap[method.Name] = methodBuilder;
        }
    }
    // override virtual properties
    foreach (PropertyInfo property in type.GetProperties(bindingFlags))
    {
        ImplementProperty(typeBuilder, type, property, methodMap);
    }
    // override virtual events
    foreach (EventInfo evt in type.GetEvents(bindingFlags))
    {
        ImplementEvent(typeBuilder, type, evt, methodMap);
    }
}

As you can see, Spring's default implementation creates proxy just for publicly published interfaces of the class. So first obvious solution is to mark all properties "public" instead of "protected". But this is unacceptable because of different reasons: huge reworking and (what is more important) encapsulation violation.

So I've decided to just reimplement this solution. This is quite easy with DI and Spring.Net. Simply follow the steps:
1. Create custom ProxyTypeBuilder.
2. Create custom ProxyFactory
3. Register ProxyFactory in configs.




Create custom ProxyTypeBuilder

Create custom proxy type builder where we define necessary binding flags.



 
using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using Spring.Aop;
using Spring.Aop.Framework;
using Spring.Aop.Framework.DynamicProxy;
using Spring.Proxy;

namespace Qulix.Spring.Aop
{
    /// 
    /// Implements proxy type builder that uses inheritance and exposes not only public methods
    /// 
    [CLSCompliant(false)]
    public class CustomInheritanceProxyTypeBuilder : DecoratorAopProxyTypeBuilder
    {
        #region IProxyTypeBuilder Members
        [CLSCompliant(false)]
        protected override void InheritType(TypeBuilder typeBuilder,
                                            IProxyMethodBuilder proxyMethodBuilder, Type type, bool declaredMembersOnly)
        {
            IDictionary methodMap = new Hashtable();
            BindingFlags bindingFlags = BindingFlags.NonPublic  BindingFlags.Public  BindingFlags.Instance;
            if (declaredMembersOnly)
            {
                bindingFlags = BindingFlags.DeclaredOnly;
            }
            // override virtual methods
            MethodInfo[] methods = type.GetMethods(bindingFlags);
            foreach (MethodInfo method in methods)
            {
                if (method.IsVirtual && !method.IsFinal)
                {
                    MethodBuilder methodBuilder = proxyMethodBuilder.BuildProxyMethod(method, null);
                    ApplyMethodAttributes(methodBuilder, method);
                    methodMap[method.Name] = methodBuilder;
                }
            }
            // override virtual properties
            foreach (PropertyInfo property in type.GetProperties(bindingFlags))
            {
                ImplementProperty(typeBuilder, type, property, methodMap);
            }
            // override virtual events
            foreach (EventInfo evt in type.GetEvents(bindingFlags))
            {
                ImplementEvent(typeBuilder, type, evt, methodMap);
            }
        }
        #endregion
    }
}


As you can see I've added extra BindingFlags. This will allow to find and proxy not only public properties and methods.


Create custom proxy factory


Just inherit from one of the implemented Proxy factories and reimplement CreateAopProxy method:



[Serializable]
[CLSCompliant(false)]
public class InheritanceAopProxyFactory : DefaultAopProxyFactory
{
    [CLSCompliant(false)]
    public override IAopProxy CreateAopProxy(AdvisedSupport advisedSupport)
    {
        if (advisedSupport == null)
        {
            throw new AopConfigException("Cannot create IAopProxy with null ProxyConfig");
        }
        if (advisedSupport.Advisors.Length == 0 && advisedSupport.TargetSource == EmptyTargetSource.Empty)
        {
            throw new AopConfigException("Cannot create IAopProxy with no advisors and no target source");
        }
 
        IProxyTypeBuilder typeBuilder = new CustomInheritanceProxyTypeBuilder(advisedSupport);
        Type aopProxy = BuildProxyType(typeBuilder);
        ConstructorInfo constructor = aopProxy.GetConstructor(new Type[] { typeof(IAdvised) });
        return (IAopProxy)constructor.Invoke(new object[] { advisedSupport });
    }
}

Here we just replace creation of ProxyTypeBuilder with our own CustomInheritanceProxyTypeBuilder. Of course, you can parametrize you ProxyFaxtory to accept type of the ProxyTypeBuilder. I do not implement this functionality for simplicity.


Register ProxyFactory in cofig


As last step we need to define what IAopProxyFactory implementation will be used. For this purpose write in the configuration file the following:


 
<object id="IAopProxyFactory" type="Qulix.Spring.Aop.InheritanceAopProxyFactory, Qulix.Spring" />
 

That's it. Now we just need to define the reference to our ProxyFactory implementation. For example:



<object id="POBloProxier" type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop">
    <property name="AopProxyFactory" ref="IAopProxyFactory" />
...

I hope this will help you. And you will help me to find easier and more clearer solution.

kick it on DotNetKicks.com
Share this Post:

Facebook
Digg DelIcioUs

No comments: