在《这是EnterLib PIAB的BUG吗?》一文中我们讨论了PIAB关于抽象基类的BUG,今天又发现了一个新的问题。问题的起因源于《IoC+AOP的简单实现》这篇文章,因为文中给出的解决方案仅仅支持构造器注入,而不能支持属性注入和方法注入——这是由于EnterLib的PIAB设计本身就存在缺陷。
在《这是EnterLib PIAB的BUG吗?》一文中我们讨论了PIAB关于抽象基类的BUG,今天又发现了一个新的问题。问题的起因源于《IoC+AOP的简单实现》这篇文章,因为文中给出的解决方案仅仅支持构造器注入(Constructor Injection),而不能支持属性注入(Property Injection)和方法注入(Method Injection)。这是由于EnterLib的PIAB设计本身就存在缺陷。
对EnterLib 5.0有一定了解的人都应该知道,在新版本的EnterLib中,原来的ObjectBuild已经完全被Unity代替。PIAB本身也是通过UnityContainer的一个扩展实现的。我们今天介绍的这个问题(也许是BUG,也许不是?自己去分辨吧。不然我说是BUG,又有人不爽了)就是Unity本身的机制导致。
一、当引入属性注入(Property Injection)之后
我们现在用最少的代码来模拟这个问题。现在我们创建如下三个类型:Foo、IBar和Bar。Foo继承MarshalByRefObject,类型为IBar的属性上应用了DependencyAttribute,从而Foo对象在被UnityContainer进行创建的过程中,该属性能能够根据注册的类型匹配进行初始化,而Bar实现了接口IBar。
1: public class Foo : MarshalByRefObject
2: {
3: [Dependency]
4: public IBar Bar { get; set; }
5: }
6: public interface IBar{}
7: public class Bar : IBar
8: {}
现在,我们直接通过PolicyInjection的Create方法来创建Foo对象
1: static void Main(string[] args)
2: {
3: var foo = PolicyInjection.Create<Foo>();
4: }
当Create方法被执行的时候,系统会抛出如下图所示的ResolutionFailedException异常
下面是具体的出错信息:
1: Microsoft.Practices.Unity.ResolutionFailedException was unhandled
2: Message=Resolution of the dependency failed, type = "Bug4Piab.Foo", name = "(none)".
3: Exception occurred while: while resolving.
4: Exception is: InvalidOperationException - The current type, Bug4Piab.IBar, is an interface and cannot be constructed. Are you missing a type mapping?
5: -----------------------------------------------
6: At the time of the exception, the container was:
7:
8: Resolving Bug4Piab.Foo,(none)
9: Resolving value for property Foo.Bar
10: Resolving Bug4Piab.IBar,(none)
11:
12: Source=Microsoft.Practices.EnterpriseLibrary.Common
13: TypeRequested=Foo
14: StackTrace:
15: at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity.TransientPolicyBuildUpExtension.DoBuildUp(Type t, Object existing, String name, InjectionMember[] injectionMembers) in e:\Builds\EntLib\Latest\Source\Blocks\Common\Src\Configuration\Unity\TransientPolicyBuildUpExtension.cs:line 68
16: at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity.TransientPolicyBuildUpExtension.BuildUp(Type t, Object existing, String name, InjectionMember[] injectionMembers) in e:\Builds\EntLib\Latest\Source\Blocks\Common\Src\Configuration\Unity\TransientPolicyBuildUpExtension.cs:line 47
17: at Microsoft.Practices.EnterpriseLibrary.PolicyInjection.PolicyInjector.Wrap(Type typeToReturn, Object instance) in e:\Builds\EntLib\Latest\Source\Blocks\PolicyInjection\Src\PolicyInjection\PolicyInjector.cs:line 137
18: at Microsoft.Practices.EnterpriseLibrary.PolicyInjection.PolicyInjector.Create(Type typeToCreate, Type typeToReturn, Object[] args) in e:\Builds\EntLib\Latest\Source\Blocks\PolicyInjection\Src\PolicyInjection\PolicyInjector.cs:line 196
19: at Microsoft.Practices.EnterpriseLibrary.PolicyInjection.PolicyInjector.Create(Type typeToCreate, Object[] args) in e:\Builds\EntLib\Latest\Source\Blocks\PolicyInjection\Src\PolicyInjection\PolicyInjector.cs:line 180
20: at Microsoft.Practices.EnterpriseLibrary.PolicyInjection.PolicyInjector.Create[TObject](Object[] args) in e:\Builds\EntLib\Latest\Source\Blocks\PolicyInjection\Src\PolicyInjection\PolicyInjector.cs:line 154
21: at Microsoft.Practices.EnterpriseLibrary.PolicyInjection.PolicyInjection.Create[TObject](Object[] args) in e:\Builds\EntLib\Latest\Source\Blocks\PolicyInjection\Src\PolicyInjection\PolicyInjection.cs:line 37
22: at Bug4Piab.Program.Main(String[] args)
23: at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
24: at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
25: at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
26: at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
27: at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
28: at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
29: at System.Threading.ThreadHelper.ThreadStart()
30: InnerException: System.InvalidOperationException
31: Message=The current type, Bug4Piab.IBar, is an interface and cannot be constructed. Are you missing a type mapping?
32: Source=Microsoft.Practices.Unity
33: StackTrace:
34: at Microsoft.Practices.ObjectBuilder2.DynamicMethodConstructorStrategy.ThrowForAttemptingToConstructInterface(IBuilderContext context) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\DynamicMethod\Creation\DynamicMethodConstructorStrategy.cs:line 209
35: at BuildUp_Bug4Piab.IBar(IBuilderContext )
36: at Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\DynamicMethod\DynamicMethodBuildPlan.cs:line 37
37: at Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\BuildPlanStrategy.cs:line 43
38: at Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\StrategyChain.cs:line 110
39: at Microsoft.Practices.ObjectBuilder2.BuilderContext.NewBuildUp(NamedTypeBuildKey newBuildKey) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\BuilderContext.cs:line 220
40: at Microsoft.Practices.Unity.ObjectBuilder.NamedTypeDependencyResolverPolicy.Resolve(IBuilderContext context) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilderCustomization\NamedTypeDependencyResolverPolicy.cs:line 51
41: at BuildUp_Bug4Piab.Foo(IBuilderContext )
42: at Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\DynamicMethod\DynamicMethodBuildPlan.cs:line 37
43: at Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\BuildPlanStrategy.cs:line 43
44: at Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\StrategyChain.cs:line 110
45: at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity.TransientPolicyBuildUpExtension.DoBuildUp(Type t, Object existing, String name, InjectionMember[] injectionMembers) in e:\Builds\EntLib\Latest\Source\Blocks\Common\Src\Configuration\Unity\TransientPolicyBuildUpExtension.cs:line 64
46: InnerException:
二、这算是个BUG吗?
从错误信息我们不难看出,具体的异常发生在对属性Bar的初始化上面。因为上该属性上应用了DependencyAttribute特性,而PIAB内部就是采用了Unity的机制,所以当调用PolicyInejection的Create方法的时候会试图初始化Bar属性。但是该属性是接口,并且PolicyInection本身不具有基于该接口的匹配信息,毫无疑问,对属性Bar的注入将会失败。
但是这到底算不算BUG呢?我觉得这得两说。如果撇开PIAB的实现机制来说,PIAB本身是作为AOP框架,它的目的就是让被创建出来的对象具有被拦截(Interception)的能力。当我们调用这个对象的某个方法的时候,应用在该方法的一些以CallHandler体现横切关注点能够得以执行。至于DI或者IoC方面的实现(比如本例的属性注入)不应该是你干的事儿,就不要瞎掺和了。从这方面考虑,应用在属性Bar上的DependencyAttribute特性应该被忽略的。
但是,从另一方面讲,各种注入本应该在对象创建的时候进行。你现在通过PolicyInjection去初始化一个对象,属性注入机制自动生效,也不是什么坏事。但是如果PIAB硬是要兼职做IoC所做的事,它本身应该提供类型匹配的注册机制,但是没有。
三、PolicyInjection.Wrap同样有问题
由于各种注入都是在相应的对象被创建的时候进行的,所以当我们调用PolicyInjection.Create创建对象的时候,这些注入被默认地被执行,我们尚可以理解。但是,当我们已经成功构建一个对象,调用PolicyInjection.Wrap对其进行封装,使之具有能够被拦截的能力,PIAB在做“公鸡生蛋”的事貌似就不太适合了——因为很有可能在对象最初被构建的时候,各种注入工作就已经完成。
在下面的代码中,我们通过构建的UnityContainer创建对象Foo。由于Foo对象被创建之前,我们已经注册了IBar和Bar之间的匹配关系,Foo的属性Bar会正常地被初始化。
1: static void Main(string[] args)
2: {
3: var contianer = new UnityContainer();
4: contianer.RegisterType<IBar, Bar>();
5: var foo = contianer.Resolve<Foo>();
6: var proxy = PolicyInjection.Wrap<Foo>(foo);
7: }
但是,当我们调用PolicyInjection.Wrap方法对已经创建出来的Foo对象进行封装的时候,一样的异常被抛出。换句话说,PolicyInjection.Wrap还试图去完成各种注入工作,这就不只是“公鸡下蛋、牡鸡司晨”的问题,这纯属添乱——去完成一件已经被完成的事情。实际上,PolicyInjection.Create方法实现的本质上讲,它先做对象的创建,然后做对象的封装。这个问题的本质还是PIAB对创建出来的对象进行封装的时候,总是试图去完成各种注入工作而导致的。
四、采用接口可以解决这个问题
上面我们采用的对象类型为继承自MarshalByRefObject类型,但是经过我的实验,如果采用接口就不会出现这个问题。为此我们对上面的程序进行如下的改动,让Foo类实现一个新的接口IFoo,并基于该接口进行对象的封装。这样的话就不会出现上面的异常。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: var contianer = new UnityContainer();
6: contianer.RegisterType<IBar, Bar>();
7: contianer.RegisterType<IFoo, Foo>();
8: var foo = contianer.Resolve<IFoo>();
9: var proxy = PolicyInjection.Wrap<IFoo>(foo);
10: }
11: }
12: public interface IFoo { }
13: public class Foo : IFoo
14: {
15: [Dependency]
16: public IBar Bar { get; set; }
17: }
18: public interface IBar{}
19: public class Bar : IBar
20: {}
具体的原因,我还没有来得及深究。通过这一阵子对EnterLib以及Unity的深入应用,发现了很多问题。虽然我个人对EnterLib还算推崇——从很早的版本一直用过来,EnterLib从设计上确实在不断的完善。但是,每一个版本总是会出现很多细节方面的问题,很难让我满意——也许是我个人太较真了吧。
EnterLib PIAB又一个BUG?[续]——这是一个致命的BUG
作者:Artech