Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

http://www.blogcn.com/User8/flier_lu/blog/6194820.html



    在上一节中曾经提到,因为 RealProxy 实现上的限制,所有需要被重定向的内部方法,都需要在一个 InternalClass 或 InternalObject 的子类中定义,以满足 MarshalByRefObject 的标记要求。同时这些方法必须以抽象方法方式定义,以便在不提供实现的情况下参与静态类型检查。
以下为引用:

public abstract class VariantClass : InternalClass
{
  public const int CV_I4 = 8;

  public static readonly string CLASS_NAME = "System.Variant";

  public abstract int GetCVTypeFromClass(Type ctype);
}

public abstract class VariantObject : InternalObject
{
  public abstract bool IsEmpty { get; }

  public abstract uint ToUInt32();
}


    而 Java 的基于接口的代理模型则比这种方式要简洁得多,使用者只需要把希望获得访问能力的方法,放入一个接口中定义好,然后建立代理即可,不受讨厌的 MarshalByRefObject 限制,也可以避免在单根结构的 Java/C# 中的一些额外麻烦。
    其实在 CLR 架构中完全可以模拟类似的语义,最终达到如下自动类型转换与封装的效果。无需显式注册内部类和内部对象的封装类,而是直接以接口方式任意定义组合希望访问的内部方法,由代理系统在后台自动完成类型封装和接口转换的工作。
以下为引用:

public interface IWindowsIdentityObject
{
  string[] GetRoles();
}

IWindowsIdentityObject identity = (IWindowsIdentityObject)WrapperManager.CreateWrapperOfObject(WindowsIdentity.GetCurrent());

foreach(string role in identity.GetRoles())
{
}


    这一魔术般效果的幕后英雄就是 IRemotingTypeInfo 接口。
以下为引用:

public interface IRemotingTypeInfo
{
  bool CanCastTo(Type fromType, object o);

  string TypeName { get; set; }
}


    实现了 IRemotingTypeInfo 接口的真实代理,其创建的透明代理会在进行类型转换时,将转换请求自动转发给 IRemotingTypeInfo.CanCastTo 方法,并根据返回值来模拟真实的转换效果。

    实现上,我们只需要让 ClassProxy 和 ObjectProxy 实现 IRemotingTypeInfo 接口,在其中进行是否能够转换的判断即可,最终方法调用会根据名称和参数,在每方法调用一级重新定位。
以下为引用:

internal class ObjectProxy : WrapperProxy, IRemotingTypeInfo, IInternalObject
{
  private readonly ClassProxy _classProxy;
  private readonly object _object;

public ObjectProxy(ClassProxy classProxy, Type wrapperClass, object objectToProxy) : base(wrapperClass)
{
    _classProxy = classProxy;
    _object = objectToProxy;
}

  #region IRemotingTypeInfo Members

  public bool CanCastTo(Type fromType, object o)
  {
    return fromType.IsAssignableFrom(_object.GetType()) || _classProxy.CanCastTo(fromType, true);
  }

  public string TypeName
  {
    get
    {
      return _object.GetType().FullName;
    }
    set
    {
      throw new NotSupportedException();
    }
  }

  #endregion
}


    如 ObjectProxy 在其 CanCastTo 方法中,首先判断被代理的对象是否直接实现了需转换的接口,如已经实现则直接可以允许转换,具体 Invoke 时自然能够定位到;如果被代理的对象没有实现需转换的接口,则有可能此接口可能是使用者自定义的用于访问内部方法的接口,如前面所定义的 IWindowsIdentityObject 接口。对此类自定义接口,ObjectProxy 不在对象一级进行判断,而是转交给全局唯一用于封装此内部类型的 ClassProxy 处理。
以下为引用:

internal class ClassProxy : WrapperProxy, IRemotingTypeInfo, IInternalClass
{
  #region IRemotingTypeInfo Members

  public bool CanCastTo(Type fromType, object o)
  {
    return CanCastTo(fromType, false);
  }

  public string TypeName
  {
    get
    {
      return _classToProxy.FullName;
    }
    set
    {
      throw new NotSupportedException();
    }
  }

  #endregion
}


    ClassProxy 则在实现 IRemotingTypeInfo.CanCastTo 方法时,直接进行自定义接口的判断,因为在类这个层面,无需支持预定义接口的转换,使用者如果需要可以直接通过 InternalClass.WrappedType 方法获得被包装类型。

    因此所有的自定义接口转换判断,实际上都最终落在 ClassProxy.CanCastTo 方法上。此方法将根据需转换接口的所有方法是否都在被封装类型中被实现,来判断是否允许转换到此自定义接口。而通过维护每个内部类型唯一的全局缓存,可以减少这种耗时的冗余检查操作。
以下为引用:

internal class ClassProxy : WrapperProxy, IRemotingTypeInfo, IInternalClass
{
  private IDictionary _clsIntfs= new Hashtable(),
                      _objIntfs = new Hashtable();

  internal bool CanCastTo(Type intf, bool instance)
  {
    IDictionary intfs = instance ? _objIntfs : _clsIntfs;

    lock(intf)
    {
      if(intfs.Contains(intf))
        return (Boolean)intfs[intf];

      bool canCast = true;

      foreach(MethodInfo method in intf.GetMethods())
      {
        if(getMethod(_classToProxy, method.Name, instance, getParamTypes(method.GetParameters())) == null)
        {
          canCast = false;
          break;
        }
      }

      intfs[intf] = canCast;

      return canCast;
    }
  }
}


    可以看到 ClassProxy 维护了内部类型唯一的 _clsIntfs 和 _objIntfs 两级缓存,分别用于缓存访问类静态方法和普通实例方法的自定义接口,而且凡是进行过转换判断的就缓存起来,以便再次转换时节省工作量。进行检测时,首先会检查缓存中是否保存了对此类型进行转换的信息,如果有则直接返回;否则会对需转换类型的每个方法,检查是否在被代理类型中存在;如果有任意一个方法不存在,则转换会失败;然后此转换的判断结果被保存到相应的缓存中,用于下次转换;最终转换结果被返回给 CLR。
    具体转换调用堆栈如下:
以下为引用:

ClassProxy.CanCastTo(System.Type intf = {"NSFocus.Util.Internal.IWindowsIdentityClass"}, bool instance = false)
  ClassProxy.CanCastTo(System.Type fromType = {"NSFocus.Util.Internal.IWindowsIdentityClass"}, System.Object o = {System.Runtime.Remoting.Proxies.__TransparentProxy})
    ...


    而通过对 InternalClass 和 InternalObject 的扩展,可以提供显式的类型转换判断机制:
以下为引用:

internal interface IInternalClass
{
  bool CanCastTo(Type intf);
}

public abstract class InternalClass : MarshalByRefObject, IInternalClass
{
  public abstract bool CanCastTo(Type intf);
}

internal class ClassProxy : WrapperProxy, IRemotingTypeInfo, IInternalClass
{
  public bool CanCastTo(Type intf)
  {
    return CanCastTo(intf, base.GetTransparentProxy());
  }
}

internal interface IInternalObject
{
  bool CanCastTo(Type intf);
}

public abstract class InternalObject : MarshalByRefObject, IInternalObject
{
  public abstract bool CanCastTo(Type intf);
}

internal class ObjectProxy : WrapperProxy, IRemotingTypeInfo, IInternalObject
{
  public bool CanCastTo(Type intf)
  {
    return CanCastTo(intf, base.GetTransparentProxy());
  }
}


    至此,自动类型转换与封装的原理与实现就大概清晰了,通过此模式,可以大大简化对内部类型访问前的薄记工作。

btw: 具体使用的单元测试代码如下:
以下为引用:

public interface IWindowsIdentityClass
{
  IntPtr _GetCurrentToken();
}

public interface IWindowsIdentityObject
{
  string[] GetRoles();
}

[Test]
public void testAutoBinding()
{
  InternalClass clsIdentity = WrapperManager.CreateWrapperOfType(typeof(WindowsIdentity));

  Assert.IsTrue(clsIdentity.CanCastTo(typeof(IWindowsIdentityClass)));
  Assert.IsFalse(clsIdentity.CanCastTo(typeof(IWindowsIdentityObject)));

  Assert.IsNotNull(((IWindowsIdentityClass)clsIdentity)._GetCurrentToken());

  InternalObject objIdentity = WrapperManager.CreateWrapperOfObject(WindowsIdentity.GetCurrent());

  Assert.IsFalse(objIdentity.CanCastTo(typeof(IWindowsIdentityClass)));
  Assert.IsTrue(objIdentity.CanCastTo(typeof(IWindowsIdentityObject)));

  Assert.IsTrue(((IIdentity)objIdentity).IsAuthenticated);
  Assert.IsTrue(((IWindowsIdentityObject)objIdentity).GetRoles().Length > 0);

  try
  {
    ((IWindowsIdentityObject)clsIdentity).GetRoles();;

    Assert.Fail();
  }
  catch(InvalidCastException)
  {
  }

  try
  {
    ((IWindowsIdentityClass)objIdentity)._GetCurrentToken();

    Assert.Fail();
  }
  catch(InvalidCastException)
  {
  }
}



to be continue...