在《EnterLib PIAB又一个BUG?》这篇文章中我们谈到:当我们通过应用DependencyAttribute特性定义需要自动注入的属性的时候,当这个属性为接口、抽象类或者没有定义无参的构造函数,无论我们调用PolicyInjection的Create方法去创建一个新的对象,还是调用Wrap方法对现有对象进行封装,都会抛出一个ResolutionFailedException异常。之后根据园友韦恩卑鄙的评论,又进行了后续的验证。如果说在前文中,我们还对这是否是个BUG抱着“谨慎”的态度,那么在这篇文章中,可以肯定地告诉你:这是一个BUG,而且是一个“致命”的BUG。
一、前景回顾
我们在重新回顾一下在《EnterLib PIAB又一个BUG?》中描述的问题。如果我们定义如下几个类型,Foo继承于MarshalByRefObject,里面具有一个类型为IBar的Bar属性,上面应用了DependencyAttribute特性使之成为一个“注入属性”。
1: public class Foo : MarshalByRefObject
2: {
3: [Dependency]
4: public IBar Bar { get; set; }
5: }
6: public interface IBar { }
7: public class Bar : IBar { }
当我们调用PolicyInjection.Create静态方法构建Foo对象的时候,会抛出如下图所示的ResolutionFailedException异常,错误信息表明缺乏对接口IBar的类型匹配所致。
二 、如何“解决”这个问题?
要解决这个问题就得解决对接口IBar的类型注册问题,但是PolicyInjection没有什么提供任何的API共我们进行类型的注册。不过,我们可以看看PolicyInjection的Create或者Wrap具体的实现原理。
1: public static class PolicyInjection
2: {
3: // Methods
4: public static TInterface Create<TObject, TInterface>(params object[] args) where TObject: TInterface
5: {
6: using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
7: {
8: return policyInjector.Create<TObject, TInterface>(args);
9: }
10: }
11:
12: public static TObject Create<TObject>(params object[] args)
13: {
14: using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
15: {
16: return policyInjector.Create<TObject>(args);
17: }
18: }
19:
20: public static object Create(Type typeToCreate, params object[] args)
21: {
22: using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
23: {
24: return policyInjector.Create(typeToCreate, args);
25: }
26: }
27:
28: public static object Create(Type typeToCreate, Type typeToReturn, params object[] args)
29: {
30: using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
31: {
32: return policyInjector.Create(typeToCreate, typeToReturn, args);
33: }
34: }
35:
36: public static TInterface Wrap<TInterface>(object instance)
37: {
38: using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
39: {
40: return policyInjector.Wrap<TInterface>(instance);
41: }
42: }
43:
44: public static object Wrap(Type typeToReturn, object instance)
45: {
46: using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
47: {
48: return policyInjector.Wrap(typeToReturn, instance);
49: }
50: }
51: }
我们可以清楚地看到:最终完成对象的创建和封装的是通过一个叫做PolicyInjector的对象完成的。从下面的代码片断我们可以看出,PolicyInjector自己也定义了一系列Create和Wrap方法。
1: public class PolicyInjector : IDisposable
2: {
3: private IUnityContainer container;
4:
5: public PolicyInjector(IConfigurationSource configurationSource);
6: public PolicyInjector(IServiceLocator serviceLocator);
7: public TInterface Create<TObject, TInterface>(params object[] args) where TObject: TInterface;
8: public TObject Create<TObject>(params object[] args);
9: public object Create(Type typeToCreate, params object[] args);
10: public object Create(Type typeToCreate, Type typeToReturn, params object[] args);
11: public void Dispose();
12: public TInterface Wrap<TInterface>(object instance);
13: public object Wrap(Type typeToReturn, object instance);
14: }
我们说,PIAB完全Unity的机制进行对象的创建和封装,实际上体现在PolicyInjector通过一个UnityContainer对象来完成对象的创建和封装工作。如果我们直接通过PolicyInjector对象,而不是通过PolicyInjection这个外观类进行对象的封装,并且通过UnityContainer对象进行类型的注册,就可以避免上述异常的出现。具体实现如下:
1: static void Main(string[] args)
2: {
3: using (PolicyInjector injector = new PolicyInjector(ConfigurationSourceFactory.Create()))
4: {
5: var containerField = typeof(PolicyInjector).GetField("container", BindingFlags.Instance | BindingFlags.NonPublic);
6: IUnityContainer container = (IUnityContainer)containerField.GetValue(injector);
7: container.RegisterType<IBar, Bar>();
8: var foo = injector.Create<Foo>();
9: Console.WriteLine("foo.Bar.GetType().Name: {0}",foo.Bar.GetType().Name);
10: }
11: }
输出结果:
1: foo.Bar.GetType().Name: Bar
三、这真的是解决方案吗?
如果你足够仔细的话,在上面一节的标题中“解决”二字是加上引号的。我实际上在挖一个坑,诱使你往里跳:)。如果你真采用这个解决方案的话,一个“致命”的错误将会产生。
我们说过,PIAB的Create方法最终也是调用Wrap方法,而Wrap方法就会自作主张地去完成相应的注入工作——这本质上就是PIAB的BUG。我们说这个BUG足以致命,我可以通过一个简单的例子来说明这一点。假设我们具有如下的类型定义,Foo和IBar定义没有改变,现在我们定义两个具体的类Bar1和Bar2去实现IBar这个接口。
1: public class Foo : MarshalByRefObject
2: {
3: [Dependency]
4: public IBar Bar { get; set; }
5: }
6: public interface IBar { }
7: public class Bar1 : IBar { }
8: public class Bar2 : IBar { }
现在我们执行如下的代码:首先我们对IBar这个接口注册了匹配关系,让它直接映射到Bar1。然后我们创建一个Foo对象,并将Bar属性初始化成一个Bar2类型的对象,然后调用PolicyInjector的Wrap方法对Foo对象进行封装。通过输出结果,我们清楚地看到:Wrap方法的执行会按照我们注册的类型匹配关系重新设置了注入属性Bar的值,即类型为Bar1的对象。
1: static void Main(string[] args)
2: {
3: using (PolicyInjector injector = new PolicyInjector(ConfigurationSourceFactory.Create()))
4: {
5: var containerField = typeof(PolicyInjector).GetField("container", BindingFlags.Instance | BindingFlags.NonPublic);
6: IUnityContainer container = (IUnityContainer)containerField.GetValue(injector);
7: container.RegisterType<IBar, Bar1>();
8: var foo = new Foo();
9: foo.Bar = new Bar2();
10: Console.WriteLine("foo.Bar.GetType().Name: {0}", foo.Bar.GetType().Name);
11: foo = injector.Wrap<Foo>(foo);
12: Console.WriteLine("foo.Bar.GetType().Name: {0}", foo.Bar.GetType().Name);
13: }
14: }
输出结果:
1: foo.Bar.GetType().Name: Bar2
2: foo.Bar.GetType().Name: Bar1
四、方法注入一样有问题
依赖诸如具有三种典型的表现形式:构造注入、属性(设置)注入和方法(调用)注入。既然PolicyInjection的Wrap方法会自作主张地完成属性注入的工作,对于方法(调用)注入也不能幸免。对于这个问题,我们直接调用PolicyInjection的Wrap方法就可以模拟。
重新定义类型Foo,让它具有一个Int类型的属性Count,该属性通过一个应用了InjectionMethodAttribute特性的方法Initialize被初始化成-1。
1: public class Foo : MarshalByRefObject
2: {
3: public int Count { get; set; }
4: [InjectionMethod]
5: public void Initialize()
6: {
7: Count = -1;
8: }
9: }
现在,我们执行如下的代码,我们发现Wrap方法的调用篡改了我们实现设置的值(100-〉-1)。
1: static void Main(string[] args)
2: {
3: var foo = new Foo();
4: foo.Count = 100;
5: Console.WriteLine("Before Wrapping: foo.Count = {0}", foo.Count);
6: foo = PolicyInjection.Wrap<Foo>(foo);
7: Console.WriteLine("After Wrapping : foo.Count = {0}", foo.Count);
8: }
输出结果:
1: Before Wrapping: foo.Count = 100
2: After Wrapping : foo.Count = -1
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。