2008年7月6日

Spring Framework 的理解以及可维护性是否得以改善的思考

Spring的特性:
1. 提供了一种管理对象的方法,可以把中间层对象有效地组织起来。一个完美的框架“黏合剂”。
2. 采用了分层结构,可以增量引入到项目中。
3. 有利于面向接口编程习惯的养成。
4. 目的之一是为了写出易于测试的代码。
5. 非侵入性,应用程序对Spring API的依赖可以减至最小限度。
6. 一致的数据访问介面。
6. 一个轻量级的架构解决方案。

对Spring的理解
Spring致力于使用POJOs来构建应用程序。由框架提供应用程序的基础设施,将只含有业务逻辑的POJOs作为组件来管理。从而在应用程序中形成两条相对独立发展的平行线,并且在各自的抽象层面上延长了各自的生命周期。

Spring的工作基础是Ioc。Ioc将创建对象的职责从应用程序代码剥离到了框架中,通常2中注入方式:setter 和 ctor参数。
每个Bean定义被当作一个POJO(通过类名和JavaBean的初始属性或构造方法参数两种方式定义的Bean)。
Spring的核心在org.springframework.beans,更高抽象层面是BeanFactory. BeanFactory是一个非常轻量级的容器。

关于可维护性的思考
Spring之类的技术确实带来了应用系统的可维护性的提高吗?
Ioc, AOP之类的技术,本质上都是将原本位于应用程序代码中"硬编码"逻辑,剥离出来放到了配置文件中(或者其他形式)。主流声音都是认为提高了应用程序的可维护性。

但如果从以下方面观察,结合项目实际经验,个人感觉这些技术的应用大大降低了应用程序的可维护性,尤其是面对一个陌生的系统,或者项目人员变动频繁的时候。
1. 中断了应用程序的逻辑,使代码变得不完整,不直观。此时单从Source无法完全把握应用的所有行为。
2. 将原本应该代码化的逻辑配置化,增加了出错的机会以及额外的负担。
3. 时光倒退,失去了IDE的支持。在目前IDE功能日益强大的时代,以往代码重构等让人头痛的举动越来越容易。而且IDE还提供了诸多强大的辅助功能,使得编程的门槛降低很多。通常来说,维护代码要比维护配置文件,或者配置文件+代码的混合体要容易的多。
4. 调试阶段不直观,后期的bug对应阶段,不容易判断问题所在。
5. 性能问题。虽说硬件性能日新月异,但是性能也是在不经意间一点一点地流失的。从汇编到高级语言,到面向对象,到虚拟机,一直处于这样的发展趋势。

posted @ 2008-07-06 10:21 bluoy 阅读(1421) | 评论 (3)编辑 收藏

2008年3月26日

项目中组员偶然写了一段垃圾的sql语句,不想却误打误撞的发现了一个jdbc的bug,包括Oracle 10g附带的版本。

详细描述可以参考如下代码:
   public static void testSetTimestampBug() throws Exception{
        Calendar calendar = new GregorianCalendar();
        Date d = calendar.getTime();
        
        String sql = "select 1+1 from dual where ?-sysdate<1";         //error sql
        String sql1 = "select ?-sysdate from dual";                          //no error sql
        String sql2 = "select 1+1 from dual where ?-1<sysdate";       //no error sql
        PreparedStatement pst = cn.prepareStatement(sql);
        //pst.setDate(1, new java.sql.Date(d.getTime()));                 //no  error
        pst.setTimestamp(1, new java.sql.Timestamp(d.getTime()));   //bug!!!, throw SQLException: ORA-00932
    }
三种sql的写法中,第一种写法在使用setTimestamp()时会出错,其他俩种却不会有问题。
即正常调用PreparedStatement.setTimestamp()方法,遇到某些特殊写法的sql语句却会出错。
本例中,抛出如下例外:
java.sql.SQLException: ORA-00932: inconsistent datatypes: expected NUMBER got INTERVAL.
然而,如果使用setDate()方法,则一切正常,三种写法都没有问题。

因为有这个问题,如果在持久层使用了其他的中间件,则这个问题可能变的更加隐蔽,比如iBatis中的处理是这样的:
java.util.Date ---> ibatis.DateTypeHandler----->PreparedStatement.setTimestamp() 
java.sql.Date ---> ibatis.SqlDateTypeHandler----->PreparedStatement.setDate()
如果不注意输入参数类型的话,就会遇到上述问题。我就因此费了不少周折。
对于iBatis的使用建议,保证入口参数类型始终为java.sql.Date即可。

posted @ 2008-03-26 17:17 bluoy 阅读(75) | 评论 (0)编辑 收藏

2008年3月24日

Web架构特性及REST架构风格(部分内容摘自网络)

良好的Web架构风格:
    1. 客户/服务器模式:  实现了UI与数据的分离。
    2. 服务端无状态性: 可见性,可靠性,可伸缩性等方面的改善。
     可见性-无状态性使得服务器不必要维护海量的上下文(Context)。
     可靠性-无状态性减少了服务器从局部错误中恢复的任务量。
     可伸缩性-无状态性使得服务器可以很容易的释放资源。
    3. 缓存: 减少服务端不必要的处理。
    4. 可伸缩性: 便于分布式和集群部署。
     上面的2,3点也是影响4的主要因素。而随着系统用户规模的指数上升,可伸缩性将变的至关重要。

现在大多数应用程序都忽略或者违反了上述2, 3的风格。当然也肯定失去了4带来的好处。
比如Java Servlet中HttpSession的应用,使服务器端保存了客户端的状态。
时下流行的动态页面的做法也使得资源缓存变得困难或者不可能。
这些都直接影响了应用的可伸缩性。

改善现状的思路是,把服务端的处理和状态前移,由客户端来实现。使服务端回归到无状态的特性。
以采用ajax技术的应用系统为例:因为不需要完全刷新就可以与服务器进行交互,使得有状态客户机成为可用选择。基于浏览器的应用程序代码可以在必要时获取新的服务器数据,并把这些数据织入当前页面。
将处理和状态前移到每个客户机上后,实现了无状态的服务端;同时缓存服务器可以缓存ajax引擎(比如dojo, prototype etc.),以及状态无关的数据。
个人理解,多种浏览器的plug-in技术(Sun的applet, MS的ActiveX等等),都应该是这种思路的不同技术实现。

经过以上分析整理,实际上已经涉及到了时下流行的一个概念-REST.

REST(Representational State Transfer)来源于Dr. Roy Thomas Fielding,  <Architectural Styles and the Design of Network-based Software Architectures>
当浏览器浏览访问一个url资源时,返回的页面即为该url资源的representation,这个representation给浏览器一个state,当
浏览器访问下一个url资源时,浏览器的state就transfer了。
REST其本身只是为分布式超媒体系统(distributed hypermedia systems)设计的一种架构风格,而不是某个标准,框架。

REST的设计准则
    1.网络上的所有事物都被抽象为资源(resource);
    2.每个资源对应一个唯一的资源标识符(resource identifier);
    3.通过通用的连接器接口(generic connector interface)对资源进行操作;
    4.对资源的各种操作不会改变资源标识符;
    5.所有的操作都是无状态的(stateless)。

REST中的资源所指的不是数据,而是数据和表现形式的组合。
REST是基于Http协议的,任何对资源的操作行为都是通过Http协议来实现。以往的Web开发大多数用的都是Http协议中的GET和POST方法,对其他方法很少使用,这实际上是因为对Http协议认识片面的理解造成的。Http不仅仅是一个简单的运载数据的协议,而是一个具有丰富内涵的网络软件的协议。他不仅仅能对互联网资源进行唯一定位,而且还能告诉我们如何对该资源进行操作。Http把对一个资源的操作限制在4个方法以内:GET, POST,PUT和DELETE,这正是对资源CRUD操作的实现。由于资源和URI是一一对应的,执行这些操作的时候URI是没有变化的,这和以往的 Web开发有很大的区别。正由于这一点,极大的简化了Web开发,也使得URI可以被设计成更为直观的反映资源的结构,这种URI的设计被称作 RESTful的URI。这位开发人员引入了一种新的思维方式:通过URL来设计系统结构。当然了,这种设计方式对一些特定情况也是不适用的,也就是说不是所有的URI都可以RESTful的。
REST 之所以可以提高系统的可伸缩性,就是因为它要求所有的操作都是无状态的。由于没有了上下文(Context)的约束,做分布式和集群的时候就更为简单,也可以让系统更为有效的利用缓冲池(Pool)。并且由于服务器端不需要记录客户端的一系列访问,也减少了服务器端的性能。

posted @ 2008-03-24 16:35 bluoy 阅读(86) | 评论 (0)编辑 收藏

2008年3月19日

       Java语言编程中更新XML文档的四种方法。第一种方法是直接读写XML文件。第二种方法是使用Apache Crimson的XmlDocument类,这种方法极为简单,使用方便,如果你选用Apache Crimson作为XML解析器,那么不妨使用这种方法,不过这种方法似乎效率不高(源于效率低下的Apache Crimson),另外,高版本的JAXP或者是Java XML Pack、JWSDP不直接支持Apache Crimson,亦即这种方法不通用。第三种方法是使用JAXP的XSLT引擎(Transformer类)来输出XML文档,这种方法也许是标准的方法 了,使用起来十分灵活,特别是可以自如控制输出格式,我们推荐采用这种方法。第四种方法是第三种方法的变种,采用了Xalan XML Serializer,引入了串行化操作,对于大量文档的修改/输出有优越性,可惜的是要重复设置XSLT引擎的属性和XML Serializer的输出属性,比较麻烦,而且依赖于Apache Xalan和Apache Xerces技术,通用性略显不足。除此之外,实际上应用别的API(比如dom4j、JDOM、Castor、XML4J、Oracle XML Parser V2)也有很多办法可以更新XML文档。

概念介绍
        Xerces/Crimson是XML解析器,Xalan是XSLT处理器,xml-apis.jar实际上是JAXP。
        Apache Crimson的前身是Sun Project X Parser, 至今Apache Crimson的很多代码都是从X Parser中直接移植过来的。早期的JAXP是和X Parser捆绑在一起的。后来的 JAXP和Apache Crimson捆绑在一起,比如JAXP 1.1。最新的JAXP 1.2 EA(Early Access)改弦更张,采用性能更好的Apache Xalan和Apache Xerces分别作为XSLT处理器和XML解析器,不能直接支持Apache Crimson了。
        dom4j(dom4j.jar)是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,dom4j无论在那个方面都是非常出色的。

posted @ 2008-03-19 10:27 bluoy 阅读(279) | 评论 (0)编辑 收藏

2008年3月13日

.NET垃圾收集器的过去、现在和未来(一)

Patrick Dussud介绍:
Patrick Dussud在微软工作了11年,曾经负责VBA、Jscript、MS Java等语言运行时的垃圾收集器(Garbage Collector)的设计,目前负责.NET CLR垃圾收集器的设计。他是.NET CLR的架构师,WinFX的首席架构师,Windows架构师组的成员。
在微软之前,Patrick是德州仪器(TI)Explorer工作站系统的主要设计人,Lucid公司Energize产品的首席架构师。

关键内容摘要

1. 微管理 / 内存的显式管理 ---  手动内存管理(new/delete)
        你必须保证在释放之前内存没有被别人使用,如果你把内存给了别人,往往你就不确定应该何时释放内存了。当你释放了内存,不知道别人正在使用这块内存时,就产生了程序崩溃的问题。所以,当你显式进行“new”和“delete”时,内存管理是一个复杂的问题,并且,此时你的代码不可组合。要么你必须确定对自己的内存有完全的控制,因此,要达到这种完全隔离的目的,你必须在将内存传递给别的模块时进行完全拷贝,这样,别的模块就只对这个完全拷贝的内存负责。要么你就得在某个地方形成对整个内存池的统一的管理,这就是自动化内存管理,这就是垃圾收集器的工作。

2. 对象终止器的调用时机由垃圾收集器决定,这些对象的析构函数被调用的先后顺序是无法预先确定的。提出了“关键终止化对象”的概念。当有一系列对象需要终止化时,关键终止化对象最后被终止化,直到上层对象干完工作前。

3.  工作机理: 垃圾收集器首先遍历所有的栈和静态变量,然后返回最初的树集。然后遍历树集对程序能够到达的每一个对象作标记。此时,我们就能逐个对象地检查内存,发现它被标记了,好的,留下。没有被标记?喔,我们有一个垃圾了。

4.  垃圾收集器的绝大部分速度和效率都来源于对回收策略的调整。通过保持内存紧凑,形成缓存本地化,页面本地化等等优势,很可能其效率甚至高于传统“new”和“delete” 操作,尤其是对于非常难以管理的服务器内存来说更是如此。

posted @ 2008-03-13 21:26 bluoy 阅读(72) | 评论 (0)编辑 收藏

2008年3月6日

如下代码:
class A{
    public void foo(){print("aaaaa");}
}

class B extends A{
    public void foo(){print("bbbbb");}
}

如果想通过B的实例化变量来调用被override的父类的方法foo():

B b = new B();

在C++中(VC 6)可以两种途径;
1.  ((A)b).foo();
2.  A a = B();

在java中类似做法则行不通,依然访问的是子类方法。
而且,在java中好像达不到这个目的。

posted @ 2008-03-06 10:34 bluoy 阅读(136) | 评论 (3)编辑 收藏

2008年1月11日

web常用的功能性测试方法

      
        1. 页面链接检查:每一个链接是否都有对应的页面,并且页面之间切换正确。

        2. 相关性检查:删除/增加一项会不会对其他项产生影响,如果产生影响,这些影响是否都正确。

        3. 检查按钮的功能是否正确:如update、cancel、delete、save等功能是否正确。

        4. 字符串长度检查:输入超出需求所说明的字符串长度的内容,看系统是否检查字符串长度,会不会出错。

        5. 字符类型检查:在应该输入指定类型的内容的地方输入其他类型的内容(如在应该输入整型的地方输入其他字符类型),看系统是否检查字符类型,会否报错。

        6. 标点符号检查:输入内容包括各种标点符号,特别是空格、各种引号、回车键。看系统处理是否正确。

        7. 中文字符处理:在可以输入中文的系统输入中文,看会否出现乱码或出错。

        8. 检查带出信息的完整性:在查看信息和update信息时,查看所填写的信息是不是全部带出,带出信息和添加的是否一致。

        9. 信息重复:在一些需要命名,且名字应该唯一的信息输入重复的名字或ID,看系统有没有处理,会否报错,重名包括是否区分大小写,以及在输入内容的前后输入空格,系统是否作出正确处理。

        10. 检查删除功能:在一些可以一次删除多个信息的地方,不选择任何信息,按”delete”,看系统如何处理,会否出错;然后选择一个和多个信息,进行删除,看是否正确处理。

        11. 检查添加和修改是否一致:检查添加和修改信息的要求是否一致,例如添加要求必填的项,修改也应该必填;添加规定为整型的项,修改也必须为整型。

        12. 检查修改重名:修改时把不能重名的项改为已存在的内容,看会否处理,报错。同时,也要注意,会不会报和自己重名的错。

        13. 重复提交表单:一条已经成功提交的纪录,back后再提交,看看系统是否做了处理。

        14. 检查多次使用back键的情况:在有back的地方,back,回到原来页面,再back,重复多次,看会否出错。

        15. search检查:在有search功能的地方输入系统存在和不存在的内容,看search结果是否正确。如果可以输入多个search条件,可以同时添加合理和不合理的条件,看系统处理是否正确。

        16. 输入信息位置:注意在光标停留的地方输入信息时,光标和所输入的信息会否跳到别的地方。

        17. 上传下载文件检查:上传下载文件的功能是否实现,上传文件是否能打开。对上传文件的格式有何规定,系统是否有解释信息,并检查系统是否能够做到。

        18. 必填项检查:应该填写的项没有填写时系统是否都做了处理,对必填项是否有提示信息,如在必填项前加* 

        19. 快捷键检查:是否支持常用快捷键,如Ctrl+C Ctrl+V Backspace等,对一些不允许输入信息的字段,如选人,选日期对快捷方式是否也做了限制。

        20. 回车键检查:在输入结束后直接按回车键,看系统处理如何,会否报错。

posted @ 2008-01-11 09:55 bluoy 阅读(50) | 评论 (0)编辑 收藏

2007年11月13日

调整完/usr的挂载点后,出现了一个奇怪的现象,原来正确的系统时钟现在出故障了,提前了8个小时。
鼓掌现象具体表现为:每次启动系统,ubuntu会用BIOS的时间+8后作为系统时钟,同时会把新的时间重新同步到BIOS中。所以每启动一次,时间就会快进8小时。很是怪异。
网上一通google才搞定。

原因是因为/usr的挂载时间被滞后了,由于/etc/localtime(时区信息)是连接到/usr/share/zoneinfo/下的某个设定好的时区文件。在系统获取时区信息时/usr尚未挂载,所以系统始终就错了。

修复办法很简单:
1.  rm /etc/localtime(取消原来的符号连接)
2. 把/usr/share/zoneinfo/下的某个设定好的时区文件copy到/etc/localtime.
3. 重新启动,故障消除。

相关概念:
UTC(Universal Time Coordinated)  = GMT  (Greenwich Mean Time)
hwclock :   query and set the hardware clock (RTC)
hwclock通过/etc/default/rcS的UTC(=yes/no)来认定BIOS时钟是UTC还是localtime。

参考文章:
http://blog.chinaz.com/u1/5830/archives/2006/36628.shtml

posted @ 2007-11-13 12:09 bluoy 阅读(106) | 评论 (0)编辑 收藏

昨天由于ubuntu的/分区空间紧张,决定把/usr挂载到别的分区。
挂载步骤(root权限执行):
1.   init  1  -- 切换到单用户模式。
2.   cp -ax /usr/*  /mnt/tmp  (tmp为新的/usr分区)。 -- 拷贝现在/usr下的内容到待切换的分区。
3.   ls -l /dev/disk/by-uuid    -- 查看分区的UUID。
4.   修改/etc/fstab中/usr的挂载方式,挂接到新的/usr分区。
5.   mv /usr /usr.old   -- 重命名现有的/usr为/usr.old, 为第6步做准备,重启动后可以删除之。
6.   mkdir /usr   -- 创建新的/usr挂载点,启动时自动挂载/usr分区到此处。
7.   restart

posted @ 2007-11-13 11:49 bluoy 阅读(155) | 评论 (3)编辑 收藏

2007年11月9日

/home 分区是最常移动的分区之一。某些时候,/home 中的全部空间都用完了,而且需要增加一个硬盘驱动器。另一些时候,/home 被设置为根分区的一部分,为了提高性能或便于备份,可能需要将它移动到别的地方。我会针对每种情况说明如何安全有效地移动 /home。

please visit the address:
http://www.ibm.com/developerworks/cn/linux/l-tip-prompt/tip05/index.html

posted @ 2007-11-09 15:52 bluoy 阅读(49) | 评论 (0)编辑 收藏

仅列出标题  下一页