Enterprise Library深入解析與靈活應用(1):通過Unity Extension實現和Policy Injection Application Block的集成
Enterprise Library是微軟P&P部門開發的眾多Open source框架中的一個,最新的版本已經出到了4.0。由於接觸Enterprise Library 已經有很長的一段時間,在實際的項目中使用的頻率也很高。對此有了一些積累,希望通過這個新的系列和廣大網友一起分享和交流。本系列 假設讀者已經對Enterprise Library有一定的了解,故而不會對各個Application Block的基本原理和編程模型進行介紹,而把側重點放在 Enterprise Library深層次的實現原理、設計模式的應用、有效擴展和最佳實踐上。
今天我們討論的內容是如何通過自定義 UnityContainerExtension實現Unity和PIAB的集成,我們假設讀者已經對Unity Application Block和Policy Injection Application Block已 經有了一定的了解。
1、創建PolicyInjectionStrategy
我們知道Policy Injection Application Block是基於Remoting的原理 通過Method Interception的方式實現了AOP(而另一種常見的方式是基於IL Injection)。要使應用在目標對象的CallHandler發揮作用,需用 通過PolicyInjecctor(默認為Remoting PolicyInjection)進行對象的創建。而實現Unity和PIAB集成的途徑就是讓Unity Container使用進行 對象的創建。
Unity是建立在ObjectBuilder之上的,而ObjectBuilder是整個Enterprise Library以及P&P其他開源框架(比如 Smart Client Software Factory)的基石。ObjectBuilder,顧名思義,就是進行對象創建的組件。而ObjectBuilder進行對象創建的方式是基 於策略的(Strategy based object creation),他通過將不同的策略運用到對象創建(或釋放回收)的不同的階段,而從提供了一個功能強 大的、極具擴展的對象創建的框架。而要實現我們的目標,首先需要創建自定義的BuilderStrategy:PolicyInjectionStrategy 。
namespace Artech.PolicyInjectionIntegratedInUnity
{
public class PolicyInjectionStrategy : EnterpriseLibraryBuilderStrategy
{
public override void PreBuildUp(IBuilderContext context)
{
base.PreBuildUp(context);
if (context.Policies.Get<IPolicyInjectionPolicy> (context.BuildKey) == null)
{
context.Policies.Set<IPolicyInjectionPolicy>(new PolicyInjectionPolicy(true), context.BuildKey);
}
}
public override void PostBuildUp(IBuilderContext context)
{
base.PostBuildUp(context);
IPolicyInjectionPolicy policy = context.Policies.Get<IPolicyInjectionPolicy>(context.BuildKey);
if ((policy != null) && policy.ApplyPolicies)
{
policy.SetPolicyConfigurationSource(EnterpriseLibraryBuilderStrategy.GetConfigurationSource(context));
context.Existing = policy.ApplyProxy(context.Existing, BuildKey.GetType(context.OriginalBuildKey));
}
}
}
}
上面就是整個PolicyInjectionStrategy 的定義。通過PreBuildUp在對象創建之前將 PolicyInjectionPolicy添加到BuilderContext 的Policy列表中(BuilderContext 為整個對象的創建和生命周期的管理提供context信息)。 在PostBuildUp中,將PolicyInjectionPolicy從BuilderContext 中取出,調用ApplyProxy方法將創建的對象通過PolicyInjecctor進行封裝, 那麼調用被封裝過的對象,我們的Policy Injection CallHandler就可以發揮其作用了。
注:PolicyInjectionPolicy定了在 Microsoft.Practices.EnterpriseLibrary.PolicyInjection dll中,實現了 Microsoft.Practices.EnterpriseLibrary.PolicyInjection.ObjectBuilder.IPolicyInjectionPolicy和 Microsoft.Practices.ObjectBuilder2.IBuilderPolicy。其ApplyProxy方法實際上就是調用了PolicyInjecctor的Wrap方法。
2、創建 PolicyInjectionExtension
有了PolicyInjectionStrategy,我們自定義UnityContainerExtension就顯得很簡單了,需要做的僅僅是 override Initialize方法,創建PolicyInjectionStrategy對象,並將其加入到當前BuilderContext 的Strategie列表中即可。
namespace Artech.PolicyInjectionIntegratedInUnity
{
public class PolicyInjectionExtension : UnityContainerExtension
{
protected override void Initialize()
{
base.Context.Strategies.AddNew<PolicyInjectionStrategy>(UnityBuildStage.Initialization);
}
}
}
3、通過coding的方式應用PolicyInjectionExtension
我們現在將我們定義的PolicyInjectionExtension使用到實 際的場景中,看看它能夠像我們希望的那樣,調用通過Unity container創建的對象上的方法,其對應的Policy Injection CallHandler能夠被 正常地執行。我們仍然使用CachingCallHandler和TimeService來做試驗。為此,我定義了一個interface(ITimeService) 和他的實現 (TimeService ),並通過cusotom attribute的方式在class上應用了CachingCallHandler。
public interface ITimeService
{
DateTime GetSystemTime();
}
[CachingCallHandler]
public class TimeService : ITimeService
{
#region IContract Members
public DateTime GetSystemTime()
{
return DateTime.Now;
}
#endregion
}
}
在Console application的Main()中,先創建UnityContainer對象,然後通過AddExtension將我們定義的PolicyInjectionExtension添加到該 UnityContainer種,然後進行interface和concrete type匹配關系的注冊。最後通過Resolve方法創建ITimeService 對象,並在for循環中以一 定的時間間隔(1s)調用GetSystemTime方法。
namespace Artech.PolicyInjectionIntegratedInUnity
{
class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.AddExtension(new PolicyInjectionExtension());
container.RegisterType<ITimeService, TimeService>();
ITimeService instance = container.Resolve<ITimeService>();
for (int i = 0; i < 10; i++)
{
Console.WriteLine(instance.GetSystemTime());
Thread.Sleep(1000);
}
}
}
}
因為我們在TimeService 使用了CachingCallHandler, GetSystemTime方法返回的結果將會被緩存。所以輸出的時間都是相同的,如下圖所示:
4、通過configuration的方式應用 PolicyInjectionExtension
Unity的主要的目的是實現了DI(dependency injection). DI的目的說白了還是實現松耦合。而送耦合實 現的一個主要的途徑還是將編譯時的依賴轉換成運行時的依賴。所以在很多情況下只用通過配置的方式才能真正意義上實現解耦。而Unity的大 多數使用場景還是基於configuration方式的。為了實現上一節中通過coding一樣的功能,我們定義了如下的Unity配置:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
<unity>
<containers>
<container>
<types>
<type type="Artech.PolicyInjectionIntegratedInUnity.ITimeService,Artech.PolicyInjectionIntegratedInUnity" mapTo="Artech.PolicyInjectionIntegratedInUnity.TimeService,Artech.PolicyInjectionIntegratedInUnity"/>
</types>
<extensions>
<add type="Artech.PolicyInjectionIntegratedInUnity.PolicyInjectionExtension,Artech.PolicyInjectionIntegratedInUnity" />
</extensions>
</container>
</containers>
</unity>
</configuration>
我們通過配置的方式實現了type的注冊和extension的添加。有了上面的配置, 我們的code進行相應的改動使用unity的配置:
namespace Artech.PolicyInjectionIntegratedInUnity
{
class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
UnityConfigurationSection unityConfigSection = ConfigurationManager.GetSection ("unity") as UnityConfigurationSection;
unityConfigSection.Containers.Default.Configure (container);
ITimeService instance = container.Resolve<ITimeService>();
for (int i = 0; i < 10; i++)
{
Console.WriteLine(instance.GetSystemTime());
Thread.Sleep(1000);
}
}
}
}
運行上面的代碼,我們一樣可以得到相同的輸 出:
本文配套源碼