閱讀725 返回首頁    go 技術社區[雲棲]


Enterprise Library深入解析與靈活應用(7):再談PIAB與Unity之間的集成

在EnteLib中,PIAB(Policy Injection Application Block)和Unity的定位是輕量級的AOP框架和IoC容器(Container)。通過PIAB,我們可以將一些業務無關的crosscutting concern定義於相應的CallHandler中,通過Attribute聲明或者配置應用到承載業務邏輯的目標方法上。而通過Unity提供的IoC容器(或者DI容器),即UnityContainer,很好地實現了依賴的動態注入,從而實現了組件之間、模塊之間或者服務之間的鬆耦合。

Unity完全建立在ObjectBuilder2之上,顧名思義,這是一個用於創建對象的基礎組件。ObjectBuilder2提供了一種具有高可擴展性的、基於策略(Strategy Based)的對象創建框架,它不僅僅是Unity的基礎組件,也是整個EnterLib和Software Factory的基石。而PIAB通過方法調用劫持(Method Call Interception)的機製實現了策略注入(Policy Injection)。PIAB提供了不同的方法劫持機製,最為典型的就是基於TransparentProxy(可以參考我的PIAB係列文章)和代碼生成(比如動態生成一個繼承自目標類型的子類,通過Override掉相應的Virtual方法實現策略注入;或者動態生成一個實現了目標接口的類型,實現相應的方法實現策略注入)。PIAB需要通過特殊的機製創建可被劫持(Interceptable)對象,而UnityContainer本質上是一個創建對象的容器,如果能夠使UnityContainer按照PIAB的要求創建可被劫持(Interceptable)對象,那麼就能實現兩者之間的集成。(Source Code從這裏下載)

一、Unity 1.2和EnterLib 4.1如何實現兩者的集成

我在本係列的第一篇文章就談過PIAB和Unity之間的集成問題,當時我們是采用了一個自定以UnityContainerExtension實現的,當時針對的版本是Unity 1.1和EnterLib 3.1。到了Unity 1.2和EnterLib 4.1,Unity已經被廣泛地使用到了整個EnterLib內部,微軟甚至通過Unity對PIAB進行了徹底的改造。所以,最新的Unity和PIAB中,已經提供了兩者的原生集成。

Unity和PIAB兩者之間的集成是通過一個特殊的UnityContainerExtension——Microsoft.Practices.Unity.InterceptionExtension.Interception實現的。為了演示Interception的使用,我們創建一個簡單的例子。該例子中定義了一服務SyncTimeProvisionService用於實現同步時間的提供,SyncTimeProvisionService實現了接口ISyncTimeProvision。SyncTimeProvisionService本身並不提供具體實現,而是通過另一個組件SyncTimeProvider實現具體的同步時間的返回,SyncTimeProvider實現接口ISyncTimeProvider。

你可以將SyncTimeProvisionService和SyncTimeProvider看成是一個應用中具有依賴關係的兩個模塊,為了實現兩個模塊之間的解耦,采用基於接口的依賴是推薦的設計模式。所以,SyncTimeProvisionService並不之間依賴於SyncTimeProvider,而是依賴於SyncTimeProvider的接口ISyncTimeProvider。我們通過Constructor注入實現依賴注入。為了讓讀者對Unity和PIAB集成的效果具有一個直觀的印象,我在SyncTimeProvider 上應用了一個CachingCallHandlerAttribute,如果該CallHandler生效,方法執行的結果將會被緩存,在緩存過期之前,得到的時間將是一樣的。相應的代碼如下所示:

using System;
namespace Artech.InterceptableUnity
{

public interface ISyncTimeServiceProvision
{
DateTime GetCurrentTime();
}

public class SyncTimeServiceProvisionService : ISyncTimeServiceProvision
{
public ISyncTimeProvider SyncTimeProvider
{ get; private set; }

public SyncTimeServiceProvisionService([Dependency]ISyncTimeServiceProvider syncTimeServiceProvider)
{
this.SyncTimeServiceProvider = syncTimeServiceProvider;
}

#region ISyncTimeServiceProvision Members

public DateTime GetCurrentTime()
{
return this.SyncTimeProvider.GetCurrentTime();
}

#endregion } public interface ISyncTimeProvider
{
DateTime GetCurrentTime();
}

[CachingCallHandler]
public class SyncTimeProvider : ISyncTimeProvider
{
#region ISyncTimeServiceProvider Members

public DateTime GetCurrentTime()
{
return DateTime.Now;
}

#endregion } }
那麼我們就可以通過下麵的方式,利用UnityContainer采用基於接口(ISyncTimeServiceProvision)的方式創建SyncTimeServiceProvisionService ,並調用GetCurrentTime方法:
 
using System;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Artech.InterceptableUnity
{
class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<ISyncTimeServiceProvision, SyncTimeServiceProvisionService>();
container.RegisterType<ISyncTimeProvider, SyncTimeProvider>();

container.AddNewExtension<Interception>();
container.Configure<Interception>().SetDefaultInterceptorFor(typeof(ISyncTimeServiceProvider), new TransparentProxyInterceptor());
var syncTimeServiceProvision = container.Resolve<ISyncTimeServiceProvision>();
for (int i = 0; i < 5; i++)
{
Console.WriteLine(syncTimeServiceProvision.GetCurrentTime());
Thread.Sleep(1000);
}

}
}
}

通過下麵的輸出,我們看出輸出的時間都是相同的,從而證實了CachingCallHandlerAttribute的有效性,進而正式了UnityContainer和PIAB的集成:

  

通過Microsoft.Practices.Unity.InterceptionExtension.Interception對Unity和PIAB兩者之間的集成,需要我們借助Interception為每一個需要被劫持(Interception)的類型注冊相應的Interceptor(實現接口Microsoft.Practices.Unity.InterceptionExtension.IInterceptor),如下麵的代碼片斷所示。

container.Configure<Interception>().SetDefaultInterceptorFor(typeof(ISyncTimeServiceProvider), new TransparentProxyInterceptor()); 

但是在每多情況下,我們不可能預先確定需要注冊哪些對象,或者這樣的類型很多,手工注冊的方式將不具有可操作性。比如,在一個N-Layer的應用中,上層的對象通過UnityContainer創建下層對象,並且通過PIAB的方式將不同的Crosscutting Concern應用於相應的層次,我們不可能對每一個應用了PAIB CallHandler相關的類型進行Interceptor的注冊。

為此,我對Interception進行了擴展,實現了Interceptor的動態注冊。Unity采用兩種不同的InterceptionStrategy:InstanceInterceptionStrategy和TypeInterceptionStrategy,它們分別采用基於InstanceInterceptor和TypeInterceptor實現方法調用劫持。我繼承了InstanceInterceptionStrategy和TypeInterceptionStrategy,將Inteceptor的動態注冊定義在PreBuildUp方法中。繼承自Interception,在Initialize方法中將兩個擴展的InstanceInterceptionStrategy和TypeInterceptionStrategy——ExtendedInstanceInterceptionStrategy和ExtendedTypeInterceptionStrategy添加到UnityContainer的BuildStrategy列表中。在這個擴展的Inteception——ExtendedInterception中,被用於動態注冊的Interceptor定義在ExtendedInterception中,默認為TransparentProxyInteceptor。下麵是ExtendedInterception、ExtendedInstanceInterceptionStrategy和ExtendedTypeInterceptionStrategy的定義:

ExtendedInterception:

using Microsoft.Practices.Unity.InterceptionExtension;
using Microsoft.Practices.Unity.ObjectBuilder;
namespace Artech.InterceptableUnity
{
public class ExtendedInterception : Interception
{
public IInterceptor Interceptor
{ get; internal set; }

public ExtendedInterception()
{
this.Interceptor = new TransparentProxyInterceptor();
}

protected override void Initialize()
{
this.Context.Strategies.Add(new ExtendedInstanceInterceptionStrategy(this), UnityBuildStage.Setup);
this.Context.Strategies.Add(new ExtendedTypeInterceptionStrategy(this), UnityBuildStage.PreCreation);
this.Context.Container.RegisterInstance<InjectionPolicy>(typeof(AttributeDrivenPolicy).AssemblyQualifiedName, new AttributeDrivenPolicy());
}
}
}

ExtendedInstanceInterceptionStrategy:

using System;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;

namespace Artech.InterceptableUnity
{
public class ExtendedInstanceInterceptionStrategy : InstanceInterceptionStrategy
{
public ExtendedInterception Interception
{ get; private set; }

public ExtendedInstanceInterceptionStrategy(ExtendedInterception interception)
{
if (null == interception)
{
throw new ArgumentNullException("interception");
}

this.Interception = interception;
}

private static IInstanceInterceptionPolicy FindInterceptorPolicy(IBuilderContext context)
{
Type buildKey = BuildKey.GetType(context.BuildKey);
Type type = BuildKey.GetType(context.OriginalBuildKey);
IInstanceInterceptionPolicy policy = context.Policies.Get<IInstanceInterceptionPolicy>(context.BuildKey, false) ?? context.Policies.Get<IInstanceInterceptionPolicy>(buildKey, false);
if (policy != null)
{
return policy;
}
policy = context.Policies.Get<IInstanceInterceptionPolicy>(context.OriginalBuildKey, false) ?? context.Policies.Get<IInstanceInterceptionPolicy>(type, false);
return policy;
}

public override void PreBuildUp(IBuilderContext context)
{
if (BuildKey.GetType(context.BuildKey) == typeof(IUnityContainer))
{
return;
}

IInstanceInterceptionPolicy policy = FindInterceptorPolicy(context);
if (null != policy)
{
if (policy.Interceptor.CanIntercept(BuildKey.GetType(context.BuildKey)))
{
this.Interception.SetDefaultInterceptorFor(BuildKey.GetType(context.BuildKey), policy.Interceptor);
}
}
else { if (this.Interception.Interceptor.CanIntercept(BuildKey.GetType(context.BuildKey)) && this.Interception.Interceptor is IInstanceInterceptor)
{
this.Interception.SetDefaultInterceptorFor(BuildKey.GetType(context.BuildKey), (IInstanceInterceptor)this.Interception.Interceptor);
}
}
base.PreBuildUp(context);
}
}
}

 

ExtendedTypeInterceptionStrategy:

using System;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity.InterceptionExtension;

namespace Artech.InterceptableUnity
{
public class ExtendedTypeInterceptionStrategy : TypeInterceptionStrategy
{

public ExtendedInterception Interception
{ get; private set; }

public ExtendedTypeInterceptionStrategy(ExtendedInterception interception)
{
if (null == interception)
{
throw new ArgumentNullException("interception");
}

this.Interception = interception;
}

private static ITypeInterceptionPolicy GetInterceptionPolicy(IBuilderContext context)
{
ITypeInterceptionPolicy policy = context.Policies.Get<ITypeInterceptionPolicy>(context.BuildKey, false);
if (policy == null)
{
policy = context.Policies.Get<ITypeInterceptionPolicy>(BuildKey.GetType(context.BuildKey), false);
}
return policy;
}

public override void PreBuildUp(IBuilderContext context)
{
var policy = GetInterceptionPolicy(context);
if (null != policy)
{
if (policy.Interceptor.CanIntercept(BuildKey.GetType(context.BuildKey)))
{
this.Interception.SetInterceptorFor(BuildKey.GetType(context.BuildKey), policy.Interceptor);
}
}
else { if (this.Interception.Interceptor.CanIntercept(BuildKey.GetType(context.BuildKey)) && this.Interception.Interceptor is ITypeInterceptor)
{
this.Interception.SetDefaultInterceptorFor(BuildKey.GetType(context.BuildKey), (ITypeInterceptor)this.Interception.Interceptor);
}
}
base.PreBuildUp(context);
}
}
}

那麼使用的時候,動態注冊Interceptor的操作將不再需要,如下麵代碼片斷所示:

using System;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Artech.InterceptableUnity
{
class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<ISyncTimeServiceProvision, SyncTimeServiceProvisionService>();
container.RegisterType<ISyncTimeServiceProvider, SyncTimeServiceProvider>();

ExtendedInterception interception = new ExtendedInterception();
interception.Interceptor = new TransparentProxyInterceptor();
container.AddExtension(interception);
var syncTimeServiceProvision = container.Resolve<ISyncTimeServiceProvision>();
for (int i = 0; i < 5; i++)
{
Console.WriteLine(syncTimeServiceProvision.GetCurrentTime());
Thread.Sleep(1000);
}
}
}

 

為了通過配置的方式應用ExtendedInterception,我們需要為之定義相應的配置類型,一個繼承自Microsoft.Practices.Unity.Configuration.UnityContainerExtensionConfigurationElement得類型。為此,我定義了下麵一個ExtendedInterceptionElement類型,配置屬性為默認的Inteceptor的類型。

using System;
using System.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Artech.InterceptableUnity
{
public class ExtendedInterceptionElement : UnityContainerExtensionConfigurationElement
{
[ConfigurationProperty("interceptor", IsRequired = false, DefaultValue = "")]
public string Interceptor
{
get
{
return (string)this["interceptor"];
}
}

public override void Configure(IUnityContainer container)
{
base.Configure(container);
ExtendedInterception interception = new ExtendedInterception();
if (!string.IsNullOrEmpty(this.Interceptor))
{
var type = System.Type.GetType(this.Interceptor);
if (null == type)
{
throw new ConfigurationErrorsException(string.Format("The {0} is not a valid Interceptor.", this.Interceptor));
}

if (!typeof(IInterceptor).IsAssignableFrom(type))
{
throw new ConfigurationErrorsException(string.Format("The {0} is not a valid Interceptor.", this.Interceptor));
}
interception.Interceptor = (IInterceptor)Activator.CreateInstance(type);
}

container.AddExtension(interception);
}
}
}

那麼對於上麵的例子,我麼可以將Type Mapping和ExtendedInterception擴展定義在如下一個配置文件中:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration, Version=1.2.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"
/> </configSections> <unity> <typeAliases> <typeAlias alias="ISyncTimeServiceProvision" type="Artech.InterceptableUnity.ISyncTimeServiceProvision,Artech.InterceptableUnity" /> <typeAlias alias="SyncTimeServiceProvisionService" type="Artech.InterceptableUnity.SyncTimeServiceProvisionService,Artech.InterceptableUnity" /> <typeAlias alias="ISyncTimeProvider" type="Artech.InterceptableUnity.ISyncTimeProvider,Artech.InterceptableUnity" /> <typeAlias alias="SyncTimeProvider" type="Artech.InterceptableUnity.SyncTimeProvider,Artech.InterceptableUnity" /> </typeAliases> <containers> <container> <types> <type type="ISyncTimeServiceProvision" mapTo="SyncTimeServiceProvisionService" /> <type type="ISyncTimeProvider" mapTo="SyncTimeProvider" /> </types> <extensionConfig> <add name="extendedInterception" type="Artech.InterceptableUnity.ExtendedInterceptionElement,Artech.InterceptableUnity" interceptor="Microsoft.Practices.Unity.InterceptionExtension.TransparentProxyInterceptor,Microsoft.Practices.Unity.Interception, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </extensionConfig> </container> </containers> </unity> </configuration>

那麼我們的代碼將會變得異常簡潔:

using System;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using Microsoft.Practices.Unity.Configuration;
using System.Configuration;
namespace Artech.InterceptableUnity
{
class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection("unity") ;
configuration.Containers.Default.Configure(container);
var syncTimeServiceProvision = container.Resolve<ISyncTimeServiceProvision>();
for (int i = 0; i < 5; i++)
{
Console.WriteLine(syncTimeServiceProvision.GetCurrentTime());
Thread.Sleep(1000);
}
}
}
}


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 14:34:06

  上一篇:go  機器視覺技術背後的行業趨勢
  下一篇:go  監管收緊,互聯網還能顛覆線下嗎?