本文出现的源头还得从DbHelper说起,先来说说这个DbHelper的演化(产生)过程:
(一)、说起DbHelper大家都非常的熟悉了,就是一个数据库操作帮助类,如果说简单的话,几个静态的方法:
1)、public static DataSet ExecNomQuery(parameters....);
2)、public static DataTable ExecNomQuery(parameters....);
3)、public static bool ExecCommand(parameters....);
4)、public static object ExecScalar(parameters....);
5)...
(二)、如果没有特殊要求的话上面4个方法基本上就够用了,但是如果涉及到事务处理等操作,上面这个数据库帮助类就会显得有点力不从心了。也是正是由于如此,自己把原来的静态数据库帮助类进行了修改,全部采用实例成员,采用new的方式来创建DbHelper,用完后采用using方式将对象Dispose掉。程序运行效果也还可以,如果不是非常高负荷的应用,这样的DbHelper基本上都可以应付。
(三)、为了不至于到处出现new DbHelper()的操作,于是就产生了创建DbHelper对象的简单工厂类。
(四)、在不断应用的过程中,需要对数据库帮助类进行扩展,于是原本只支持MSSqlServer数据库的DbHelper又开始不够用了,为了不改变太多原来DbHelper的代码,于是直接从DbHelper中抽象出一个IDbHelper接口出来,将原来的DbHelper改名为MSSqlDbHelper,同时创建了一个支持Oracle数据库的OracleDbHelper,这样就可以根据通过工厂类创建自己需要的数据库帮助类了(MSSqlServer、OracleDbHelper,如果需要的话,可以自己扩展,只需要继承IDbHelper接口即可)。
(五)、为了消除数据库帮助类中的if;else if;分支的个数,于是产生了抽象工厂类IDbHelperFactory,先通过反射创建IDbHelperFactory,然后通过工厂类创建IDbHelper。(注:抽象工厂类彻底的消除简单工厂类的if-else if分支)
(六)、后来有一段时间一头钻入Ioc的漩涡中,于是产生了一个想法,想自己写一个Ioc容器,。。。哗啦哗啦。。。经过几天的折腾,一个自己打造的精简版的Ioc容器诞生了,通过构造函数/或者setter方式注入创建IDbHelperFactory,然后创建IDbHelper对象。
(七)、上面的IDbHelper用了一段时间,感觉还不错,但是不知不觉的使用过程中,DbHelper中增加了一个又一个的字段、属性、方法(都是实例成员),导致DbHelper对象不断膨胀,从而使得DbHelper实例化的时候,对内存的开销相比最原始的静态数据库帮助类大了许多,想到自己的Ioc容器创建对象的生命周期中有一种是Pooled类型的,即组件池模式。对于这个组件池模式,在这里好好解释一下(对Ioc有一定研究的人可以飘过),组件池模式,即容器在讲对象创建完以后,同时保存一份对象的引用在自己的Pool中,这样调用方在对象调用完毕之后,GC不会将对象回收掉,因为Pool中还有一个该对象的引用。这样在下一个创建该类对象的调用中,Ioc容器就可以先在Pool中寻找是否有可用的对象,如果有的话,就直接返回Pool中的对象,否则重新创建对象。好,问题产生了:
1)、Ioc容器如何判断哪些对象已经不在使用中;
2)、调用方在什么时候释放掉从容器中获取到的对象实例。
基于以上两点,我们可以参考微软自带的数据库连接池功能,即使用的时候,将对象标记为使用中(Status=Open),使用完毕后,将数据库连接对象Close掉,以便其他地方调用。于是一个简单的接口产生了,命名为“可重用对象”:
该接口只有一个bool类型的状态属性:IsOnUsing,用于标记该对象是否处于使用中状态。
下面给出从对象池中获取对象的代码构架:
写完上述方法后心中窃喜了一阵子,于是迫不及待的创建了30(只要大于组件池的容量即可)个线程来测,结果还没等我反应过来,就发现代码出现了死循环程序提示内存溢出了。。。。
着实让我悲剧了一阵子。。。。。。。。。。。。实在不知道为什么会出现这样的问题,难道是:
【先声明:我测试的对象就是上面的IDbHelper,DbHelper继承了IDispose和IReusedObject两个接口,并且在Dispose方法中加了“IsOnUsing = false;",所以通过using(IDbhelper dbHelper = ....){.....}的方式调用DbHelper对象使用完毕后,对象都会被对象池重用。】
1)、组件池中的所有的对象都一直被占用在。。。。。
2)、还是什么其他原因。。。。
答案已经给出:就在代码中红色标识处。
经过大家的激烈讨论,发现有一个地方可以优化的:那就是如果调用方忘记经从Ioc容器中获取的对象Dispose()掉的话,那么这个对象就是个死角,永远也不会被释放掉,直到程序退出为止。所以下一步会针对这个Ioc容器进行优化,为组件池中的组件增加对象超时时间(即对象空闲一段时间就将该对象从组件池中释放掉)。
下面贴出优化后的代码,欢迎大家指正,谢谢!
ASP.NET开发技术交流群: 67511751(人员招募中...)