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已经有了一定的了解。
一、创建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 。
1: namespace Artech.PolicyInjectionIntegratedInUnity
2: {
3: public class PolicyInjectionStrategy : EnterpriseLibraryBuilderStrategy
4: {
5: public override void PreBuildUp(IBuilderContext context)
6: {
7: base.PreBuildUp(context);
8: if (context.Policies.Get<IPolicyInjectionPolicy>(context.BuildKey) == null)
9: {
10: context.Policies.Set<IPolicyInjectionPolicy>(new PolicyInjectionPolicy(true), context.BuildKey);
11: }
12: }
13:
14: public override void PostBuildUp(IBuilderContext context)
15: {
16: base.PostBuildUp(context);
17: IPolicyInjectionPolicy policy = context.Policies.Get<IPolicyInjectionPolicy>(context.BuildKey);
18: if ((policy != null) && policy.ApplyPolicies)
19: {
20: policy.SetPolicyConfigurationSource(EnterpriseLibraryBuilderStrategy.GetConfigurationSource(context));
21: context.Existing = policy.ApplyProxy(context.Existing, BuildKey.GetType(context.OriginalBuildKey));
22: }
23: }
24: }
25: }
26:
27:
上面就是整个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方法。
二、创建PolicyInjectionExtension
有了PolicyInjectionStrategy,我们自定义UnityContainerExtension就显得很简单了,需要做的仅仅是override Initialize方法,创建PolicyInjectionStrategy对象,并将其加入到当前BuilderContext 的Strategie列表中即可。
1: namespace Artech.PolicyInjectionIntegratedInUnity
2: {
3: public class PolicyInjectionExtension : UnityContainerExtension
4: {
5: protected override void Initialize()
6: {
7: base.Context.Strategies.AddNew<PolicyInjectionStrategy>(UnityBuildStage.Initialization);
8: }
9: }
10: }
11:
三、通过coding的方式应用PolicyInjectionExtension
我们现在将我们定义的PolicyInjectionExtension使用到实际的场景中,看看它能够像我们希望的那样,调用通过Unity container创建的对象上的方法,其对应的Policy Injection CallHandler能够被正常地执行。我们仍然使用CachingCallHandler和TimeService来做试验。为此,我定义了一个interface(ITimeService) 和他的实现(TimeService ),并通过cusotom attribute的方式在class上应用了CachingCallHandler。
1: public interface ITimeService
2: {
3: DateTime GetSystemTime();
4: }
5:
6: [CachingCallHandler]
7: public class TimeService : ITimeService
8: {
9: #region IContract Members
10:
11: public DateTime GetSystemTime()
12: {
13: return DateTime.Now;
14: }
15:
16: #endregion
17: }
18: }
19:
在Console application的Main()中,先创建UnityContainer对象,然后通过AddExtension将我们定义的PolicyInjectionExtension添加到该UnityContainer种,然后进行interface和concrete type匹配关系的注册。最后通过Resolve方法创建ITimeService 对象,并在for循环中以一定的时间间隔(1s)调用GetSystemTime方法。
1: namespace Artech.PolicyInjectionIntegratedInUnity
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: IUnityContainer container = new UnityContainer();
8: container.AddExtension(new PolicyInjectionExtension());
9: container.RegisterType<ITimeService, TimeService>();
10: ITimeService instance = container.Resolve<ITimeService>();
11: for (int i = 0; i < 10; i++)
12: {
13: Console.WriteLine(instance.GetSystemTime());
14: Thread.Sleep(1000);
15: }
16: }
17: }
18: }
19:
因为我们在TimeService 使用了CachingCallHandler,GetSystemTime方法返回的结果将会被缓存。所以输出的时间都是相同的,如下图所示:
四、通过configuration的方式应用PolicyInjectionExtension
Unity的主要的目的是实现了DI(dependency injection). DI的目的说白了还是实现松耦合。而送耦合实现的一个主要的途径还是将编译时的依赖转换成运行时的依赖。所以在很多情况下只用通过配置的方式才能真正意义上实现解耦。而Unity的大多数使用场景还是基于configuration方式的。为了实现上一节中通过coding一样的功能,我们定义了如下的Unity配置:
1: version="1.0" encoding="utf-8"
2: <configuration>
3: <configSections>
4: <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
5: </configSections>
6: <unity>
7: <containers>
8: <container>
9: <types>
10: <type type="Artech.PolicyInjectionIntegratedInUnity.ITimeService,Artech.PolicyInjectionIntegratedInUnity" mapTo="Artech.PolicyInjectionIntegratedInUnity.TimeService,Artech.PolicyInjectionIntegratedInUnity"/>
11: </types>
12: <extensions>
13: <add type="Artech.PolicyInjectionIntegratedInUnity.PolicyInjectionExtension,Artech.PolicyInjectionIntegratedInUnity" />
14: </extensions>
15: </container>
16: </containers>
17: </unity>
18: </configuration>
19:
我们通过配置的方式实现了type的注册和extension的添加。有了上面的配置,我们的code进行相应的改动使用unity的配置:
1: namespace Artech.PolicyInjectionIntegratedInUnity
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: IUnityContainer container = new UnityContainer();
8: UnityConfigurationSection unityConfigSection = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
9: unityConfigSection.Containers.Default.Configure(container);
10: ITimeService instance = container.Resolve<ITimeService>();
11: for (int i = 0; i < 10; i++)
12: {
13: Console.WriteLine(instance.GetSystemTime());
14: Thread.Sleep(1000);
15: }
16: }
17: }
18: }
19:
运行上面的代码,我们一样可以得到相同的输出: