前面有一篇博客谈到了如何实现AOP,也就是如何拦截所有方法调用的技术。只是很浅显的讲到了如何去做才能实现方法拦截,对其中的奥秘真是不甚了解。这次就再深入一点,虽然还是不甚了解,但比上一篇,又前进了一小步。
这次要说的就是WCF等一些技术用到的:动态创建一个对象,该对象可以转换为一个指定的接口,并且当在转换为接口的对象上调用接口的方法时,拦截所有调用,去做相应的事情。对于WCF来说,相应的事情就是:发起一个网络连接,访问远端服务,并将返回的数据转换为当前接口方法的返回值类型。
下面演示的是如何创建一个客户端代理来访问WCF服务。首先我们假设服务端提供了如下服务:
[ServiceContract]
public interface IOrderService
{
[OperationContract]
string GetOrder(int id);
}
客户端使用如下代码创建代理:
IOrderService orderServiceProxy = ChannelFactory<IOrderService>.CreateChannel(binding, address);
string order = orderServiceProxy.GetOrder(1);
可以看到,客户端并不知道这个接口的实现类,那么它是如何创建一个对象并能转换为IOrderService接口的呢?这正是今天要说的透明代理。
什么是透明代理?MSDN上对此没有详细的描述,只有零零散散的一点注释,那就先看看这些注释吧!
使用任何穿越远程边界的对象实际上都是在使用透明代理,透明代理会让你觉得远程对象好像就在客户端空间里。它会把所有调用通过远程调用框架转发给一个真实对象。透明代理对象寄宿在一个类型为RealProxy的托管类型实例内,RealProxy实现了转发透明代理传递过来的调用的功能。RealProxy对象继承了托管对象的语义,例如垃圾回收,支持成员和方法,也可以扩展为一个新类。所以RealProxy具有双重特性;一方面它具有跟远程对象一样的行为,另一方面它本身也是对象。最后,代理对象不必一定要用在远程调用里,它可以跟被代理对象在同一个AppDomain里。
通过这段话,基本上已经了解了一些透明代理和真实代理(RealProxy)的概念。那么既然透明代理表现的和被代理对象行为几乎一模一样, 如何来判断一个对象是本身还是一个透明代理对象呢,使用RemotingServices.IsTransparentProxy方法可以帮助你进行判断。
好吧,回归到我们当初设定的目标上来:创建一个透明代理,该代理可以被转换为一个指定的接口。因为代理的实际功能都是在RealProxy内部实现的,那么我需要看一看RealProxy应该如何使用,根据MSDN的描述,我们应该从RealProxy类继承,并必须重写Invoke方法。下面我就创建一个类InterfaceProxy,并让它从RealProxy继承。
internal class InterfaceProxy : RealProxy
{
public InterfaceProxy(Type classToProxy)
: base(classToProxy)
{ }
public override IMessage Invoke(IMessage msg)
{
return msg;
}
}
于是我们可以从该真实代理获取一个透明代理,如下:
InterfaceProxy proxy = new InterfaceProxy(typeof(IOrderService));
object tp = proxy.GetTransparentProxy();
通过查看本地变量调试窗口,可以发现tp的类型为System.Runtime.Remoting.Proxies.__TransparentProxy,看上去我们获取了一个透明代理,那么下一步我们要把它转换为我们想要的接口并调用该接口方法“GetOrder”
InterfaceProxy proxy = new InterfaceProxy(typeof(IOrderService));
object tp = proxy.GetTransparentProxy();
IOrderService serviceProxy = tp as IOrderService;
serviceProxy.GetOrder(1);
记住,先把断点打在InterfaceProxy的Invoke方法体中,运行程序,发现调用进入了Invoke,如果继续运行,就会发现在serviceProxy.GetOrder(1)处出现了异常。这是由于我们没有对该方法调用返回正确的结构,也就是说,我们需要在Invoke方法中返回有效的IMessage来保证这个返回结果支持GetOrder的返回值类型。
对于上面的代码,有一点需要解释的是:透明代理对象是一种非常特殊的对象,如果真实代理代理的是接口类型(真实代理的构造函数的实参就是被代理类型),那么从该真实代理获取的透明代理就可以转换被代理类型,并把相应的接口调用转发给真实代理,真实代理通过Invoke方法处理所有的接口调用,因为在Invoke的参数中包含的当前接口方法的元数据信息以及当前调用的参数信息。这里需要注意的一点是,甚至在透明代理上调用Object类定义的方法都会被转发到真实代理,例如调用tp.GetType(),该调用会被转发至真实代理,如果你没有正确的实现这个调用,该方法甚至会返回null,或者报错。可以看到,如果不了解真实代理的特性,很难想象GetType方法会返回null。
好了,我想我们已经完成所需的工作,达到了和ChannelFactory<T>相同的使用方式,至于在Invoke方法里面需要做什么,完全由需求决定,对于WCF是启动一个网络访问,访问远程服务,对于企业库里面的策略注入,所做的就是调用包装对象的方法之前做一些通用的操作(见前一篇博客);总之拦截了调用之后,你可以做你任何想做的事情。
但是这并不是关于透明代理的全部,还有一些特性需要我们去了解,从而给我们解决问题提供更广的思路。
· 真实代理能够代理任何类型吗?
答案是否定的,真实代理只能代理接口类型和从MarshalByRefObject类型派生的类型。传递其它类型将会抛出异常。
· 除了能转换为被代理类型,透明代理能转换为任何类型吗?
答案是否定。不过虽然不能转换为任何类型,但是可以转换为任何接口类型和任何从MarshalByRefObject类型派生的类型。不过需要添加额外的接口支持,也就是说,RealProxy的派生类还需要实现IRemotingTypeInfo接口,如果你实现了这个接口,CLR就会在类型转换时调用接口实现的方法CanCastTo来向你询问是否支持该类型转换。在这里你可以实现任何你想要支持的类型转换规则,或者简单返回一个true,表示你支持所有可能的转换。该接口还有一个属性定义:TypeName,不过我还没有发现它在整个流程中的作用。
可以看到,透明代理只支持接口类型和MarshalByRefObject机器派生类型,但是WCF的ChannelFactory<T>却没有对T做任何类型的限制,这并不是说它支持所有的类型,而是泛型约束里没有对接口类型的约束,也不支持对两个泛型约束的或操作。所以只能在运行时判断被代理类型是否符合要求,不信的话,可以使用ChannelFactory<int>试一下。
此外,对于IMessage接口来说,又有很多话题可以讨论。简单来说它把我们平时的方法调用表示为消息调用,例如当使用new来创建被代理对象的时候(参见上一篇文章),Inovke的参数IMessage实际为ConstructionCall对象,但是不确定当前的IMessage参数一定为ContructionCall类型的对象,但是一定是实现了IConstructionCallMessage类型的对象。IMessage有相当大的继承层次,仔细研究就能找到对应的方法调用的合适的消息对象。可以在此转发调用给真实对象并做进一步处理。