单例模式效果可以用下面这张图表示,服务端的服务实例只有一个,任何一个客户端访问的服务端都是相同的服务实例。意味着服务端可以留下不同客户端的脚印。
使用也很简单,只需要将ServiceBehavior的上下文模式InstanceContextMode设置为Single即可。可以参照上一篇介绍 实例上下文模式:单调模式
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class Calculator : ICalculator,IDisposable { ...... }
上篇介绍实例上下文模式时有些概念没有讲清楚。这里来补一下。
ServiceHost有两个有参数的构造函数,分别是
public ServiceHost(object singletonInstance, params Uri[] baseAddresses); public ServiceHost(Type serviceType, params Uri[] baseAddresses);
第一个构造函数,singletonInstance是指服务实例,即new Calculator()创建的实例对象。
第二个构造函数是指服务实例的类型,即typeof(Calculator)获得。
baseAddresses均指终结点的基地址,这里需要注意,每一种Binding类型只能有一个基地址。例如下面寄宿代码将会报错,因为baseAddrs中含有两个http的基地址。
回到正题,通过观察ServiceHost的构造函数代码。会将服务实例放入singletoninstance,类型放入serviceType。 这两个字段在ServiceHost中都是私有的。Servciehost启动的时候先检查singletonInstance是否存在,若不存在则根据serviceType反射创建一个服务实例。
了解单例的机制需要知道一下几点:
1.当服务启动时候会调用服务行为(ServiceBehavior)初始化ServiceHost。而服务行为实现了IServiceBehavior接口,它定义了一个ApplyDispatchBehavior接口。
public interface IServiceBehavior { void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters); void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase); void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase); }
此接口的作用是将创建的实例上下文(InstanceContext)附加到终点分发器(EndpointDispatcher)的DispatchRuntime的SingletonInstance上。
2.实例上下文提供者实现了接口IInstanceContextProvider,实例上下文提供者附加在终点分发器(EndpointDispatcher)的DispatchRuntime的InstanceContextProvider上。
public interface IInstanceContextProvider { InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel); void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel); bool IsIdle(InstanceContext instanceContext); void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext); }
当服务端接受到一个请求消息时,首先会调用GetExistingInstanceContext获取InstanceContext,再从上下文中获取服务实例,调用实例的方法。流程处理完毕后调用IsIdle方法,如果返回为true则标识当前实例上下文生命周期结束,GC回收。
上一篇介绍单调时,直接将GetExistingInstanceContext返回null,WCF会创建一个新的实例上下文,使用完毕后再调用Isdle,直接返回true,则让GC回收。因此就有了单调模型。
实例模型只需要保证在GetExistingInstanceContext时返回一个相同的实例上下文,并且不能被GC回收,因此IsIdle返回false即可。而这个相同的实例上下文,需要到终结点分发器中取出来。
下面自定义一个SingleServiceProvider,如前面所述,IsIdle返回false,确保不让GC回收。GetExistingInstanceContext返回内部的runtime中的SingletonInstanceContext。而这个runtime是在构造函数中指定。
public class SingleServiceProvider : IInstanceContextProvider { private DispatchRuntime runtime; public SingleServiceProvider(DispatchRuntime runtime) { this.runtime = runtime; } public InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel) { return runtime.SingletonInstanceContext; } public void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel) {} public bool IsIdle(InstanceContext instanceContext) { return false; } public void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext) {} }
自定义SingleAttribute,遍历终结点分发器,若终ServiceHost中的服务实例SingletonInstance存在,则直接获取用来构造InstanceContext;若不存在则用serviceType反射形式创建一个服务实例,再构造InstanceContext。
public class SingleAttribute : Attribute, IServiceBehavior { public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) {} public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { foreach (ChannelDispatcher channel in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher endpoint in channel.Endpoints) { DispatchRuntime runtime = endpoint.DispatchRuntime; ServiceHost host = (ServiceHost)serviceHostBase; object Instance = null; if (null != host.SingletonInstance) { runtime.SingletonInstanceContext = new InstanceContext(serviceHostBase, host.SingletonInstance); Instance = host.SingletonInstance; } else { Instance = Activator.CreateInstance(serviceDescription.ServiceType); runtime.SingletonInstanceContext = new InstanceContext(serviceHostBase, Instance); } endpoint.DispatchRuntime.InstanceContextProvider = new SingleServiceProvider(runtime); if (serviceDescription.ServiceType.GetInterfaces().Contains(typeof(IDisposable))) { FieldInfo field = typeof(ServiceHost).GetField("disposableInstance", BindingFlags.Instance | BindingFlags.NonPublic); field.SetValue(host, Instance); } } } } public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {} }
当ServiceHost结束时会调用onClosed方法,里面会释放private的disposableInstance字段。所以这里也将创建的服务实例通过反射放到这里,这样ServiceHost关闭时也能让服务实例也能回收。
最后使用自定义的SingleAttribute,可以看到同样实现了单例效果,并没有针对每次调用都创建一个实例上下文。
[Single] public class Calculator : ICalculator,IDisposable { ...... }