状态对象:数据库的替代者

板桥里人 http://www.jdon.com 2006/1/2(转载请保留)

  这是一个实战中非常重要但是容易被忽视的概念,说它重要,是因为它比数据库重要;说它容易被忽视也是同样的原因,它经常被数据库概念替代。

  如果你经验和经历中没有状态这个概念,极端地说:可能你的Java系统经验还未积累到一定程度,状态是每个Java程序员深入Java系统后必然碰到的问题。

  本文我想试图表达的是:状态分两种:活动的状态对象和持久化的状态。而数据库中的数据只是状态的一种持久化结果,而Java系统 运行时,我们更多的可能是和一种活动的状态打交道,这种活动的状态存在内存中,而不是持久化到硬盘上,当然,需要时你可以通过数据库/文件持久化到硬盘上。

  但是,如果你以数据库数据替代状态,那么就可能导致数据库的频繁访问,而且 你的系统会变成一个非对象化的、紧耦合、到处是分散数据块的糟糕系统。这样的系统并不比传统的两层结构好到哪里!也不会比Jsp里嵌入Java代码伪三层系统高明到什么地方。

什么是状态?

  只要有对象就可能有状态,任何一个对象活动时,都有自己的状态属性,类的 字段属性极有可能成为状态,我们现在经常使用的Domain model其实就是一种 包含状态的对象,如果你对状态没有深入掌握,就不可能真正掌握对象系统特点,或者是Domain Model的执行情况。

  对于初学者,经常会疑问:我是将数据放在HttpSession中还是Request中,这里 其实已经开始接触状态,一旦你接触状态,你就要开始小心,因为你可能会将内存泄漏的恶魔导引进来。

  内存泄漏的恶魔爆发时刻取决于你状态的生存周期和系统并发访问量。

  状态的生存周期也就是包含这个状态的对象的生命周期,在简单系统中,我们只 需要通过new创建对象,然后它的消亡就会依靠JVM垃圾回收机制回收,但是事情会这么简单吗?

  状态的危险还会发生在多线程环境下,当多个线程对同一个内存中状态写操作时,这时怎么办?如果这个状态持久化在数据库中,我们会依赖数据库提供的强大事务机制防止这种并发死锁,但是如果是在内存中,你就很难办,因此,我们就尽量避免发生这种多线程同时访问一个状态的现象,而Singleton单例模式极容易发生这种现象,因此实践中,单例模式是J2EE开发中需要避免的,相关帖子讨论见:
http://www.jdon.com/jive/article.jsp?forum=91&thread=17578

  我们接触的Web容器或Jsp/Servlet本质就是一个多线程,这也是很多初学者不知道的, 因为多线程编程是复杂或困难的,因此才有jsp/Servlet这样的上层封装,但是我们使用他们
时,实际在进行多线程编程。

  生命周期和多线程并发使得我们简单的面向对象系统变得异常复杂和难以掌握起来。下面我从这个两个角度,给出两种模式思维解决之道。

生命周期(Scope)

  生命周期(Scope)就是指状态的活动周期,状态对象是什么时候被创建;然后什么时候被销毁,很显然,如果状态对象还没有被创建或已经被销毁,你再访问这个状态对象可能失败,而状态的生命周期控制是可能散落在运行程序的各个地方,如果不象状态模式那样进行统一控制,有可能整个系统是危机四伏的。

  状态的生命周期其实就是对象生命周期,更加细化地说:是Domain Model这个对象的生命周期。这在一个以领域模型为驱动的设计概念中不可回避的课题,而领域模型实战的复杂性就复杂在此。

  状态的生命周期在J2EE中目前有三种:Request/Session和Application,Request是每个客户端发出的一次请求,这是J2EE系统中最基本的事件激活单元, 当服务器端推出一个页面到客户端时,意味着这个Request的结束。那么如果我们的状态保存在Request中,意味着在request结束之前,这个请求经历的任何一个环节都可以对这个状态(对象)进行操作。(掌握这个原理,对于你学习Struts和JSF很有帮助)

  如果是Session,则一直和该客户端有关,只要是该客户端发出的每次request的任何环节都可以对这个状态(对象)进行操作。

  如果是Application,则意味着这个状态是当前Web项目的全局状态。

  这三种状态形式都是以将状态保存在内存中形式存在的,是和持久化状态相对的。是一种内存活动状态。

  生命周期的选取当然是越短越好,这样,这个状态对象就可以被自动销毁,从而避免了
大访问量下的内存泄漏,但是在大访问量下,对象频繁创建和销毁是耗费性能的。

  那么,我们可能经常使用HttpSession来保存状态,这时你极有可能造成内存泄漏,我经常在Jdon论坛上看到将很多数据库数据暂时保存在HttpSession中想法,这是相当危险的,因为一旦并发用户很多,相当多的HttpSession包含了状态,而状态中有可能有更多其他引用,因此内存很快会爆满,或者垃圾回收机制频繁启动,造成应用系统运行暂停或缓慢。

  当你将状态放入HttpSession时,难道没有考虑将其手工消除吗?你要知道所有Web容器(Tomcat/Weblogic等)都不会自动替你清除那些你可能不用的状态对象啊。如果每个人只管新增元素,不管重整或管理,这个系统能不变得混乱吗?代码上这种现象我们是通过Refactoring等结构/行为模式来解决,那么在运行时的状态管理呢?

  状态管理模式或者说对象管理模式正是解决这种问题的。

   按照该模式,你必须手工自己管理放在HttpSession的状态,比如你为每个HttpSession
设立一个状态容器最大尺寸,当超过这个尺寸时,你需要将不用的状态从容器去除, 但是如果这个客户端在Session失效期内又来访问这个状态怎么办?那么你可能需要先临时将状态序列化保存到硬盘上,等Session失效期到达后再真正删除。

  是不是觉得很麻烦?
  捷径是有:
  1. 尽量少使用HttpSession保存状态,这对集群环境也是有利的,见该贴讨论:
http://www.jdon.com/jive/article.jsp?forum=121&thread=22282
那么这些状态放在哪里?使用Application的缓存中,

  2. 使用状态管理中间件,目前有几个选择:EJB的有态Bean;NanoContainer之类状态相关的微容器。那么Spring可以吗?目前没有发现有该功能,甚至在Spring容器内无法直接使用Session性质的状态,只能通过线程级别的ThreadLocal来实现(对不起,你又要开始回到远古的汇编线程时代了);而Jdon框架则可以。

  下面我们谈谈Application的状态,在这个范围内,一个对象状态可以被多个用户反复访问,在这个级别,状态类似数据库中数据,因为可以使用数据库来替代这个级别的状态,所以将状态放入缓存这个深层次技术被大多数初学者忽视了,甚至产生了对数据库依赖心理。

缓存中的状态

  虽然我们将状态保存在Application中,但是我们不可避免还是遇到Session同样的状态管理问题,这个问题所幸的是有专门缓存中间件解决了,当然,在一个多服务器集群系统,如果一个客户端在一个服务器中存放了状态,那么能否在另外一个服务器的内存中访问到呢?回答是肯定的,前提是你必须使用分布式缓存系统。

  目前分布式缓存系统是靠EJB服务器完成,当JBoss 5在2006变成完全解耦、可肢解时,
我们就可以使用原本只支持EJB的JBoss分布式缓存系统来支持我们的普通JavaBeans了(POJO)。这其中目前可能会花费一些力气,因为还没有一个统一的POJO构件接口标准,我相信以后
可能会有。

  如果你不想花费力气,而且可能就只是一台服务器,可以通过双核芯片提升性能,那么单态缓存如果实现?很简单,使用一个缓存产品如OsCache等,将其设定保存在 Application中,或者在web.xml中进行一下简单的配置即可。

  但是,这时你可能碰到另外一个问题:状态的唯一标识,如何通过唯一标识从缓存中那么
多对象状态中取出你要的那一个呢?比较琐碎。

  有没有一个框架帮助你省却这些麻烦,当然推荐Jdon Framework,只要将包含状态的类(主要是Domain Model)继承特定的类或接口(接口在1.4版本实现)即可,这个类的对象运行时就会被缓存或从缓存中读取,再也无需你照料缓存了,就这么简单。

  当然,Jdon Framework的底层缓存器是可以被替代,使用你喜欢的缓存产品,因为jdon
Framework是基于Ioc设计,构件之间是完全解耦、可彻底肢解,能够通过配置替代和更换的。
如果你不明白这个道理,需要好好研究一下Ioc模式带给我们革命性的新变化。

  从以上也可以看出:java复杂性还在于我们需要在编码时,却要想象其运行时的情形。而这种翻译联想没有深厚的实践功底,是很难顺利完成的。

状态管理中间件

  自从J2EE开辟中间件时代以来,就有相当多的高级中间件提供与具体应用无关的通用功能,状态管理中间件很早就有之,EJB的有态Session Bean是一个代表。

  一个中间件不但要有良好的松耦合设计,我们暂时称为静态设计;更要有优秀的动态设计,例如状态管理就属于一种动态设计。

  当然,如果你比较谦虚,不但要选择一些静态设计很好的框架或中间件;而且还要依赖一些拥有良好的动态运行管理的中间件。

  EJB无论是EJB1.X/EJB2.X/EJB3.X.在状态管理上要更加优秀,当然EJB3.X又吸收了优秀的静态设计概念,但是因为需要有一个具体服务器实现过程,这个过程中存在一些陷阱,如In-Box问题等。

  Spring无疑是一个静态设计非常优秀框架,它一直在AOP上孜孜不倦,力图探索一条从AOP角度进行动态运行管理干预捷径,相信会有惊人结果,当然,这种细粒度的AOP需要实践检验,当然如果整入JDK 6.0就更好。

  而Jdon Framework则试图在目前两者之间寻求了一个平衡,既有Ioc/AOP优秀的静态设计,虽然在AOP上不及Spring前卫;但提供了切实Session和Cache状态管理;

  如果你不需要EJB的分布式多服务器集群功能;又不是AOP的超级粉丝,无疑使用Jdon Framework之类的框架无疑是简化方便的。

状态设计的难点

  最后,我不得不重申,并不是有了良好的状态管理框架就可以高枕无忧了,状态的设计其实是我们每个项目必须面临的可变课题,如果状态复杂了可以使用状态模式对付,可惜往往状态不够复杂。

  一个对象本身属性和状态是应该耦合在一起,还是进行分离,属性和状态没有明显的泾渭分明的界限,我们举一个例子:

  论坛Forum这个对象,它有一些字段属性,如论坛名称、论坛描述,还有其他一些相关属性:如该论坛的最新发帖;该论坛的发贴量,后两者好像也是论坛字段,但是他们可能经常变化的,应该属于状态,那么状态和Forum这个主体对象是什么关系?是将该论坛的最新发帖和该论坛的发贴量两个字段并入Forum这个Domain Model中,还是应该单独建立一个状态对象?如果进行分离,分离的依据是什么?

  当然,这里分离的依据是因为对象的生存周期不同。对于我们熟悉的课题,我们能够马上分辨出其中的生存周期,如果是不熟悉领域的建模呢?

  所以,大家已经明白:状态设计的难点是:如何粒度细化地创建模型对象;然后分辨出其中动态的状态性质。这是域建模实战中一个难点。

  很多人问我:你提倡的域建模、设计模式和框架是什么意思?为什么说他们是Java开发设计的三件宝呢?或者说三个典型知识点呢?我想通过本篇我已经通过状态这个概念稍微解释了域建模的一些特点。

  当前,MDA中的四色原型模式Archetype将帮助我们更好地分辨出类的属性、状态和行为,这是一场带来以后十年的软件革命,详情请看下面讨论

相关参考:

学生课程评分系统的设计讨论

数据库时代的终结

用j2ee架构金融平台的问题

DDD(Domain-Driven Design领域驱动设计)实战

更多关于evans ddd讨论

 

时间: 2024-10-14 22:23:11

状态对象:数据库的替代者的相关文章

用Java实现可保存状态的数据库生成XML树(1)-基于weblogic,包括一些基础问题,非常详细的。

web|xml|生成xml|数据|数据库|问题 用Java实现可保存状态的数据库生成XML树目录  0.    关键字词注释    11.    目的    12.    设计思想    13.    实现概要    14.    实现步骤    24.1.XML文档结构定义    24.2.数据表的结构定义    44.3.构造生成XML的servlet    44.4.构造显示树型结构的XSL模版    84.5.构造生成点击树型XMl显示详细节点内容的Servlet    124.6.Se

对象数据库 VS 关系数据库

对象|数据|数据库 对象数据库 VS 关系数据库我们将对象数据库管理系统(ODBMS)定义为一个集成了数据库能力与面向对象编程语言能力的数据库管理系统(DBMS),ODBMS使数据库对象看起来像是已有的一个或多个程序设计语言中的程序设计语言以象.--Rick Cattell,OMG-93委员会主席. ODBMS在多用户客户机/服务器环境中提供了持久性存储器.ODBMS可以处理对象的并行访问,提供锁定和事务保护,保护对象存储器免遭各种类型的威胁,照管像备份和恢复之类传统任务.ODBMS这所以与关系

建立对象数据库-内存映射范式,需要中间层容器的支持

对象|数据|数据库|中间层 在<设想使用XML和关系数据库形成一个对象数据库>一文中,已经阐述了关系数据库和对象数据库的主要区别:对外键的使用是通过直接记录,还是遍历外键子表来获得.而这里,是面对另一个重要的区别:如何释放对象的资源.对于对象数据库而言,它等同于是一批直接存于数据库中的串行化对象,它的实际存储形式到底是不是按关系结构存储并不是最重要的,重要的是调用程序读入内存的就是一个对象,而不是游标映射:这样,使用对象数据库就必须存在着一个如何清除用完的对象资源的问题:同时由于对象数据库必然

对象数据库与关系数据库利弊谈

在20世纪60年代后期引入的面向对象技术引起了一场革命.到20世纪80年代后,面向对象的技术已经成为了行业的主流,其原因多种多样:面向对象不仅简化了界面的开发,而且也提供了一种更加灵活.简单数据处理方法,这种方法从根本上改变了应用程序的构建方法.不再像关系型数据库一样用死板的二维表格来表示数据,对象技术使用类对数据进行描述.一个对象是一个类的实例,就像一颗特定的橡树是橡树类的实例一样. 对象技术使用继承方案,使得类是按等级设计的."橡树"类能够从更加普遍的类"树"继

关于对象数据库 DB4O 的一些BUG以及如何应对的方法

关于对象数据库 DB4O 的一些BUG以及如何应对的方法 1.objectmanager 6.0不能正常显示中文而是框框,是字体设置不正确的原因,因为没有源程序,所以无法定位在哪里出了问题. 2.objectmanager 6.0需要对应db4o 6.1的版本      objectmanager 1.8需要对应db4o 5.5的版本      objectmanager 1.7需要对应db4o 5.2的版本 版本不对应会造成无法打开数据库文件,怎么就这么不兼容呢?连高版本的管理工具都无法打开旧

机器学习成功检测糖尿病性视网膜病变,Realm为Node.js发布对象数据库

[12.01资讯速递]本次播报内容有:JetBrains Rider:一款全新的基于IntelliJ和ReSharper的.NET IDE:华为发布业界首个物联网建网方法论:英特尔人工智能论坛在京召开:首个基于Android 7.0的OxygenOS Beta版更新:Spark for Mac正式上架Mac App Store:交大教授训练机器看脸识罪犯:成功使用机器学习来检测糖尿病性视网膜病变:SpaceX计划构建太空互联网:人工智能vs人类智能:人机结合才是未来:Realm为Node.js发

《Python面向对象编程指南》——1.9 不带__init__()方法的无状态对象

1.9 不带__init__()方法的无状态对象 以下是一个不需要__init__()方法的类定义.对于策略模式的对象来说这是常见的设计.一个策略对象以插件的形式复合在主对象上来完成一种算法或逻辑.它或许依赖主对象中的数据,策略对象自身并不携带任何数据.通常策略类会和享元设计模式一起使用:在策略对象中避免内部存储.所有需要的值都从策略对象的方法参数传入.策略对象自身是无状态的,可以把它看作是一系列函数的集合. 这里定义了一个类给Player实例提供游戏模式的选择,以下这个策略包括了拿牌和下调投注

php面象对象数据库操作类实例_php技巧

本文实例讲述了php面象对象数据库操作类.分享给大家供大家参考. 具体实现代码如下: 复制代码 代码如下: //此处构造一个数据库操作类,封装所有数据库操作 //可以扩展便于后台管理程序的使用 Class MySQLDB  {     var $host;     var $user;     var $passwd;     var $database;    var $conn;       //利用构造函数实现变量初始化     //同时连接数据库操作    function MySQLD

《游戏编程模式》一7.5 状态对象应该放在哪里呢

7.5 状态对象应该放在哪里呢 我这里忽略了一些细节.为了修改一个状态,我们需要给state_指针赋值为一个新的状态,但是这个新的状态对象要从哪里来呢?我们之前的枚举方法是定义一些数字.但是,现在我们的状态是类,我们需要获取这些类的实例.通常来说,有两种实现方法. 7.5.1 静态状态 如果一个状态对象没有任何数据成员,那么它的唯一数据成员便是虚表指针了.那样的话,我们就没有必要创建此状态的多个实例了,因为它们的每一个实例都是相同的. 在那种情况下,我们可以定义一个静态实例.即使你有一系列的FS