zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Enterprise Library深入解析与灵活应用(9):个人觉得比较严重的关于CachingCallHandler的Bug

BUG应用 解析 深入 关于 个人 灵活 library
2023-09-27 14:27:57 时间

微软EnterLib的Policy Injection Application Block(PIAB)是一个比较好用的轻量级的AOP框架,你可以通过创建自定义的CallHandler实现某些CrossCutting的逻辑,并以自定义特性或者配置的方式应用到目标方法上面。PIAB自身也提供了一系列的CallHandler,其中CachingCallHandler直接利用HttpRuntime的Cache实现了基于方法级别的缓存。但是,PIAB发布到现在,CachingCallHandler就一直存着一个问题:如果目标方法具有Out参数并且返回类型不是void,会抛出IndexOutOfRangeException,如果返回类型为void,out参数也不会被缓存。不知道微软对此作何考虑,反正我觉得这是一个不可原谅的Bug。(Source Code从这里下载)

一、问题重现

这个问题还还重现,为了比较我们先来看看正常情况下CachingCallHandler的表现。下面我定义了一个简单的接口:IMembershipService, 包含一个方法GetUserName根据传入的User ID返回User Name。MembershipService实现了该接口,为了方便大家确定方法执行的结果是否被缓存,我让每次执行都返回一个GUID。CachingCallHandler直接以自定义特性的方式应用到GetUserName方法上。

 1: using System;

现在,在Main方法中,编写如下的代码:通过PolicyInjection的Create TType, TInterface 创建能够被PIAB截获的Proxy对象,并在一个无限循环中传入相同的参数调用GetUserName方法。从输出结果我们看到,返回的UserName都是相同的,从而证明了第一次执行的结果被成功缓存。


 10: IMembershipService svc = PolicyInjection.Create MembershipService, IMembershipService 

现在我们修改我们的程序:将GetUserName改成TryGetUserName,将UserName以输出参数的形式反悔,Bool类型的返回值表示UserId是否存在,相信大家都会认为这是一个很常见的API定义方式。


 IMembershipService svc = PolicyInjection.Create MembershipService, IMembershipService 

at System.Runtime.Remoting.Proxies.RealProxy.PropagateOutParameters(IMessage msg, Object[] outArgs, Object returnValue)

at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)

at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData msgData, Int32 type)

at CachingCallHandler4OutParam.IMembershipService.TryGetUserName(String userId, String userName)

at CachingCallHandler4OutParam.Program.Main(String[] args) in e:\EnterLib\CachingCallHandler4OutParam\CachingCallHandler4OutParam\Program.cs:line 15

at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)

at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

二、是什么导致异常的抛出?

我们现在通过CachingCallHandler的Invoke方法的实现,可以看出一些问题:该CallHander仅仅会缓存方法的返回值(this.AddToCache(key, return2.ReturnValue);),而不是缓存输出参数;由于仅仅只有返回值被缓存,所以最终创建的IMethodReturn不包含输出参数,从而导致返回的消息与参数列表不一致,导致异常的发生。


 1: public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)

 12: string key = this.keyGenerator.CreateCacheKey(input.MethodBase, inputs);

 23: return input.CreateMethodReturn(objArray2[0], new object[] { input.Arguments });

三、问题如何解决?

现在我们来Fix这个Bug,让它支持输出参数并对输出参数和返回值一并缓存。为此,我首先创建了如下一个OutputParamter类表示输出参数,属性Value和Index分别表示参数值和在方法参数列表中的位置:


然后将需要进行缓存的方法返回值和输出参数封装在一个单独的类中,我将它起名为InvocationResult. 两个属性ReturnValue和Outputs分别表示返回值和输出参数。StreamlineArguments方法结合传入的所以参数列表返回一个方法参数值的数组,该数组的元素顺序需要与方法的参数列表相匹配。


 23: public bool TryGetParameterValue(int index, out object parameterValue)

然后在现有CachingCallHandler的基础上,添加如下两个辅助方法:AddToCache和GetInviocationResult,分别用于将InvocationResult对象加入缓存,以及根据IMethodInvocation和IMethodReturn对象创建InvocationResult对象。最后将类名改成FixedCachingCallHandler以示区别。


 6: HttpRuntime.Cache.Insert(key, result, null, Cache.NoAbsoluteExpiration, this.expirationTime, CacheItemPriority.Normal, null);

 10: private InvocationResult GetInvocationResult(IMethodInvocation input, IMethodReturn methodReturn)

 24: return new InvocationResult(methodReturn.ReturnValue, outParms.ToArray());

最后我们重写Invoke方法, 去处对返回类型void的过滤,并实现对基于InvocationResult对象的缓存和获取:


 4: public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)

 11: string key = this.keyGenerator.CreateCacheKey(input.MethodBase, inputs);

 12: InvocationResult result = (InvocationResult)HttpRuntime.Cache.Get(key);

 22: return input.CreateMethodReturn(result.ReturnValue, result.StreamlineArguments(input.Arguments));

 27: private InvocationResult GetInvocationResult(IMethodInvocation input, IMethodReturn methodReturn)

 41: return new InvocationResult(methodReturn.ReturnValue, outParms.ToArray());

微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 原文链接

Java Flight Recorder & Mission Control 一个高效的性能分析工具 立即下载