Java Votary

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  48 随笔 :: 1 文章 :: 80 评论 :: 0 Trackbacks

2005年12月16日 #

ThreadLocal是什么

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

线程局部变量并不是Java的新发明,很多语言(如IBM IBM XL FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供在语言级支持,而是变相地通过ThreadLocal的类提供支持。

所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。

ThreadLocal的接口方法

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

  • void set(Object value)

设置当前线程的线程局部变量的值。

  • public Object get()

该方法返回当前线程所对应的线程局部变量。

  • public void remove()

将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

  • protected Object initialValue()

返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法 也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:

代码清单1 SimpleThreadLocal

public class SimpleThreadLocal {

private Map valueMap = Collections.synchronizedMap(new HashMap());

public void set(Object newValue) {

valueMap.put(Thread.currentThread(), newValue);①键为线程对象,值为本线程的变量副本

}

public Object get() {

Thread currentThread = Thread.currentThread();

Object o = valueMap.get(currentThread);②返回本线程对应的变量

if (o == null && !valueMap.containsKey(currentThread)) {③如果在Map中不存在,放到Map

中保存起来。

o = initialValue();

valueMap.put(currentThread, o);

}

return o;

}

public void remove() {

valueMap.remove(Thread.currentThread());

}

public Object initialValue() {

return null;

}

}

虽然代码清单9‑3这个ThreadLocal实现版本显得比较幼稚,但它和JDK所提供的ThreadLocal类在实现思路上是相近的。

一个TheadLocal实例

下面,我们通过一个具体的实例了解一下ThreadLocal的具体使用方法。

代码清单2 SequenceNumber

package com.baobaotao.basic;

public class SequenceNumber {

通过匿名内部类覆盖ThreadLocalinitialValue()方法,指定初始值

private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){

public Integer initialValue(){

return 0;

}

};

获取下一个序列值

public int getNextNum(){

seqNum.set(seqNum.get()+1);

return seqNum.get();

}

public static void main(String[] args)

{

SequenceNumber sn = new SequenceNumber();

3个线程共享sn,各自产生序列号

TestClient t1 = new TestClient(sn);

TestClient t2 = new TestClient(sn);

TestClient t3 = new TestClient(sn);

t1.start();

t2.start();

t3.start();

}

private static class TestClient extends Thread

{

private SequenceNumber sn;

public TestClient(SequenceNumber sn) {

this.sn = sn;

}

public void run()

{

for (int i = 0; i < 3; i++) {④每个线程打出3个序列值

System.out.println("thread["+Thread.currentThread().getName()+

"] sn["+sn.getNextNum()+"]");

}

}

}

}

 

通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号, 在③处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:

thread[Thread-2] sn[1]

thread[Thread-0] sn[1]

thread[Thread-1] sn[1]

thread[Thread-2] sn[2]

thread[Thread-0] sn[2]

thread[Thread-1] sn[2]

thread[Thread-2] sn[3]

thread[Thread-0] sn[3]

thread[Thread-1] sn[3]

考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

Thread同步机制的比较

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线 程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编 写多线程代码时,可以把不安全的变量封装进ThreadLocal。

由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

Spring使用ThreadLocal解决线程安全问题

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用 域。就是因为Spring对一些Bean(如RequestContextHolder、 TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用 ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9‑2所示:

通通透透理解ThreadLocal

1同一线程贯通三层

这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

下面的实例能够体现Spring对有状态Bean的改造思路:

代码清单3 TopicDao:非线程安全

public class TopicDao {

private Connection conn;一个非线程安全的变量

public void addTopic(){

Statement stat = conn.createStatement();引用非线程安全变量

}

}

由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:

代码清单4 TopicDao:线程安全

import java.sql.Connection;

import java.sql.Statement;

public class TopicDao {

①使用ThreadLocal保存Connection变量

private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();

public static Connection getConnection(){

②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,

并将其保存到线程本地变量中。

if (connThreadLocal.get() == null) {

Connection conn = ConnectionManager.getConnection();

connThreadLocal.set(conn);

return conn;

}else{

return connThreadLocal.get();③直接返回线程本地变量

}

}

public void addTopic() {

④从ThreadLocal中获取线程对应的Connection

Statement stat = getConnection().createStatement();

}

}

不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对 应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了 Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的 Connection。因此,这个TopicDao就可以做到singleton共享了。

当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时 不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类 使用ThreadLocal保存Connection。

小结

ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况 下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

posted @ 2009-07-01 12:15 Dion 阅读(335) | 评论 (0)编辑 收藏

     摘要: 题记世界是事实的总体,而不是事物的总体。世界为诸事实所规定,为它们既是全部事实所规定。——路德维希·维特根斯坦,《逻辑哲学论》The answer, please?"请回答我..."The stern voice startles you. 一个严厉的声音把你吓个半死.You were dozing in Mrs. Rosencrantz's high school math class agai...  阅读全文
posted @ 2006-03-11 10:02 Dion 阅读(2096) | 评论 (0)编辑 收藏

前几天跟着写了一个简单的例子.
觉得Drools的配置也没有什么.
今天在运行house的例子的时候, 无论怎么样, 总是异常: 没有定义的SMF.
显然没有找到我定义的drools.config文件.
官方网站上是这样写地:
String droolsConfigProp = System.getProperty( "drools.conf" );

if ( droolsConfigProp != null )
{
    loadConfig( droolsConfigProp );
}

ClassLoader cl = Thread.currentThread( ).getContextClassLoader( ); if ( cl == null )
{
    cl = getClass( ).getClassLoader( );
}

Enumeration configUrls = cl.getResources( "META-INF/drools.conf" );

if ( !configUrls.hasMoreElements( ) )
{
    cl = getClass( ).getClassLoader( );
    configUrls = cl.getResources( "META-INF/drools.conf" );
}

if ( !configUrls.hasMoreElements( ) )
{
    cl = ClassLoader.getSystemClassLoader( );
    configUrls = cl.getResources( "META-INF/drools.conf" );
}

this.classLoader = cl;
while ( configUrls.hasMoreElements( ) )
{
    URL configUrl = (URL) configUrls.nextElement( );
    loadConfig( configUrl );
}

好像每一个旮旯里面都找了, 为什么没有找到我的呢?
System.getProperty指向的位置并不一定和loadFromUrl位置一样.呵呵.
posted @ 2006-03-11 10:00 Dion 阅读(1631) | 评论 (0)编辑 收藏

内容提要
       在本文的第一部分,我将讨论规则引擎如何帮助你从软件的应用逻辑中分离出商业规则逻辑,以实现商业应用的灵活性。另外,我还将介绍JSR-94规则引擎 API,及其开源实现Drools项目,它是这一新技术的先驱。在第二部分,我们将介绍一个规则引擎例子,并深入地研究Drools引擎及其JSR-94 扩展的复杂性。

为什么使用规则引擎
       商业世界充满了关于变化的陈词滥调,如任何事物都会改变,唯一不变的是变化等等。而在技术领域里,情况正好相反。我们仍然在试图解决30年前软件业中同样 的一堆问题--也许比30年前还要多的问题。在过去的十年,IT从业人员淹没在软件方法学的大量文献中,如快速软件开发,极限编程,敏捷软件开发等,它们 无一例外地强调灵活和变化的重要性。
       但商业通常比开发团队所依赖的软件过程和技术改变得更加迅速。当商业策划人员试图重整IT部门,以支持新的业务转型时,仍然觉得很费劲。

Lost in Translation
       虽然IT团队反应迅速,但他们通常带来"电话效应"――IT给商业计划的执行带来的阻力和它带来的利益一样多。不幸的是,在开发团队完全理解商业决策规则 并实现之前,规则已经改变了。在软件进入市场前,它已经过时了,需要进行重构以满足新的业务需求。如果你是一个开发人员,你会知道我在说什么。再也没有比 在需求变动的情况下构造软件让开发人员更沮丧的事情了。作为软件开发人员,你必须比业务人员更了解业务,有时还要了解更多。
       试想一下你是一位商业决策者。假如公司的成功依赖于你对于市场趋势敏锐的洞察力,它常常帮助你领先于竞争者利用变化的市场环境获利。每天你都会得到更多更 好的市场信息,但并不要紧。完成新产品开发可能需要6-9个月,在此期间,对于市场大胆和敏锐的洞察和信息优势可能已经浪费了。而且,当产品发布时,有这 样几种可能:产品没有什么吸引人的特性,预算超支,过了产品的最佳发布期限,或三者兼而有之。
       情况可能还会更糟,在完成产品开发时,市场环境和规划产品开发时相比,已经发生了根本变化。现在你必须要遵守新的规则,你已经丧失了你的边际优势,而且设 计软件的五人中的三人已经离开了公司。你必须给接手的新人重新讲解复杂的业务。如果事情不顺利,你可能发现自己要对付一个缺少文档,并且你完全不了解的遗 留应用。
       你的战略在哪出现了问题?你在哪里应该可以做到更好?最近的轻量级软件过程,如极限编程,敏捷软件开发等都在强调自动单元测试和软件功能优先级的重要性。 除此之外,还有其他的原则,你的开发团队可能也很熟悉,这些原则可以帮助他们对需求的变动作出迅速反应并缩短项目的开发周期。这些原则的大多数,如系统分 解,多年前就已经出现,并得到了Java平台的支持(如JMX等),还有如面向对象和角色建模,已经内建在Java语言中。
       但Java仍然是一门相当年轻的语言,而且Java平台远远还没有完备。当前在Java社区,一个引人注目的新技术是,分离商业决策者的商业决策逻辑和应 用开发者的技术决策,并把这些商业决策放在中心数据库,让它们能在运行时(即商务时间)可以动态地管理和修改。这是一个你值得考虑的策略。
       为什么你的开发团队不得不象商业经理人一样,在代码中包含复杂微妙的商业决策逻辑呢?你怎样才能向他们解释决策推理的微妙之处呢?你这样做是否谨慎呢?可 能不是。象bottom line一样,某些东西在解释的过程中丢失了。为什么要冒这样的风险,让应用代码或测试代码错误地表达你的商业决策逻辑呢?如果这样做的话,你怎样检查它 们的正确性呢――难道你自己想学习如何编程和编写测试代码,或者你的客户会为你测试软件?你一方面要应付市场,一方面要应付软件代码,这实在太困难了。
       如果能将这些商业决策规则集中地放在一个地方,以一种你可以理解的格式定义,让你可以直接管理,而不是散落在代码的各个角落,那该有多好。如果你能把商业 决策规则独立于你的软件代码,让开发团队作出技术决策,你将会获得更多好处。你的项目开发周期会更短,软件对于变动的需求更灵活。

规则引擎标准Java API
       2003年11月,Java社区通过了Java Rule Engine API规范(JSR-94)的最后草案。这个新的API让开发人员在运行时访问和执行规则有了统一的标准方式。随着新规范产品实现的成熟和推向市场,开发 团队将可以从应用代码中抽取出商业决策逻辑。
       这就需要新一代的管理工具,帮助商务经理人可以定义和细化软件系统的行为。不必通过开发过程来修改应用,并假定可以得到正确的结果,经理人将可以随时根据需要修改决策规则,并进行测试。
       但这将需要开发人员在设计系统时作出某些改变,并可以得到合适的开发工具。

分离商务和技术的关注点
       这是一个非常简单的例子,从经理人的角度,说明如何分离商务和技术的关注点。
       你管理着一个反向投资基金。你公司计算机系统的一部分用于分析股票价格,收益和每股净资产,并在需要时向你提出预警。这个计算机系统的工作是,识别出PE比率比市场平均值低的股票,并标记出来以便进一步的检查。
       你的IT部门拥有一大堆数据,并开发了一系列你可以在规则中引用的简单数据对象。现在,为简单起见,假设你是一名受过良好教育的,了解技术的管理人,你了解XML的基本知识,可以让你编写和修改简单的XML规则文件。
       你的第一个规则是,给道琼斯所有的股票估值,并剔除P/E比率大于10的股票(这有点过分简化,但这里只作为一个例子)。保留下来的股票用来生产一系列报表。对于这个简单的例子,你的规则文件看起来如下(我们将会过头来讨论这个文件的结构):

<stock:overvalued>
    <stock:index> DJIA </stock:index>
    <stock:pe> over 10.0 </stock:pe>
</stock:overvalued>

       一个月后,你接到一家巴西分析师公司的电话,雇佣你的公司生成一系列巴西股市的报表,但他们有更严格的标准。而目前在巴西,P/E比率市场平均值是个位 数,因此你用来评估被市场低股票的阈值需要改变。除了较低的P/E比率,你的新客户还要求以Price-to-Book比率作为参考标准。
       你启动规则编辑器,并修改规则以匹配新的评估条件。现在,规则引擎剔除巴西股市中P/E比率大于6.5,以及Price to Book 比率小于等于1的股票。完成规则文件修改后,看起来如下:

<stock:overvalued>
    <stock:index> Brazil </stock:index>
    <stock:pe> over 6.5 </stock:pe>
    <stock:pb> over 1.0 </stock:pb>
</stock:overvalued>

       你无需为此向开发团队作任何解释。你无需等待他们开发或测试程序。如果你的规则引擎的语义足够强大,让你描述工作数据,你可以随时按需修改商业规则。
       如果限制因素是规则的定义语言和数据模型,你可以确信这两者将会标准化,并出现先进的编辑器和工具,以简化规则的定义,保存和维护。
       现在,我希望你已经清楚以下的原则:在这个例子中,哪只股票是否被选择是一个商务决策,而不是技术决策。决定将哪只股票交给你的分析师是经理人的逻辑 ――"logic of the bottom line"。经理人作出这些决策,并可以按需定制应用。这些规则因此变成了一种控制界面,一种新的商业系统用户界面。

使用Rule开发
       如果在这个应用场景中,你是一个开发人员,你的工作会稍微轻松一些。一旦你拥有了一种用于分析股票的规则语言,你可以取出数据对象并交给规则引擎执行。我们将会到规则语言的讨论,但现在我们继续刚才的例子。
       你的系统将一系列的stock bean输入规则引擎。当规则执行后,你可以选出符合条件的股票并可以对它们作进一步处理。也许是把它们输入报表生成系统。分析师使用这些报表帮助他们分 析股市。同时,老板也可能让你使用新的技术分析工具,并用Dow理论预测股市的底部和顶部。
       规则引擎可以让你的系统变得更简单,因为你无需在代码中编写商务逻辑,如怎样选择股票,选择股票过程中奇怪的条件组合等。这些逻辑不再进入你的代码。你将可以专注于数据模型。
       现在可以这么认为,通过从应用代码中剥离出易变的商业逻辑,你的效率会更高。但凡是总有例外――简单应用可能并不能从规则系统中获益。但如果你开发一个大型系统,有很多易变的商业逻辑,你可以考虑在应用中集成规则引擎。
       除了从应用代码中剥离出商业决策逻辑外,规则引擎还有其他用处。有时候你需要应用成百上千的规则进行决策,并且有上千个对象和这些规则一起使用。很难想象 有什么先进的人工智能引擎可以处理这种情况。遇到这种情况,你需要一个极快的决策算法或是大型机。大型机并不便宜,但你可以非常便宜的得到效率和可伸缩性 最好的算法。

Bob McWhirter的Drools项目
       现在,我要介绍Drools项目,Charles Forgy Rete算法的一个增强的Java语言实现。Drools是一个Bob McWhirter开发的开源项目,放在The Codehaus上。在我写这篇文章时,Drools发表了2.0-beata-14版。在CVS中,已完整地实现了JSR94 Rule Engine API并提供了单元测试代码。
       Rete算法是Charles Forgy在1979年发明的,是目前用于生产系统的效率最高的算法(除了私有的Rete II)。Rete是唯一的,效率与执行规则数目无关的决策支持算法。For the uninitiated, that means it can scale to incorporate and execute hundreds of thousands of rules in a manner which is an order of magnitude more efficient then the next best algorithm。Rete应用于生产系统已经有很多年了,但在Java开源软件中并没有得到广泛应用(讨论Rete算法的文档参见http://herzberg.ca.sandia.gov/jess/docs/61/rete.html。)。
       除了应用了Rete核心算法,开源软件License和100%的Java实现之外,Drools还提供了很多有用的特性。其中包括实现了JSR94 API和创新的规则语义系统,这个语义系统可用来编写描述规则的语言。目前,Drools提供了三种语义模块――Python模块,Java模块和 Groovy模块。本文余下部分集中讨论JSR94 API,我将在第二篇文章中讨论语义系统。
       作为使用javax.rules API的开发人员,你的目标是构造一个RuleExecutionSet对象,并在运行时通过它获得一个RuleSession对象。为了简化这个过程, 我编写了一个规则引擎API的fa?ade,可以用来解释代表Drools的DRL文件的InputStream,并构造一个 RuleExecutionSet对象。
       在上面提到了Drools的三种语义模块,我接下来使用它们重新编写上面的例子XML规则文件。这个例子中我选择Java模块。使用Java模块重新编写的规则文件如下:

<rule-set name="StockFlagger"
      xmlns="http://drools.org/rules"
      xmlns:java="http://drools.org/semantics/java">
  <rule name="FlagAsUndervalued">
    <parameter identifier="stock">
      <java:class>org.codehaus.drools.example.Stock</java:class>
    </parameter>
    <java:condition>stock.getIndexName().equals("DJIA");</java:condition>
    <java:condition>stock.getPE() > 10 </java:condition>
    <java:consequence>
      removeObject(stock);   ( 译注:应该是retractObject(stock) )
    </java:consequence>
  </rule>
</rule-set>

       现在的规则文件并没有上面的简洁明了。别担心,我们将在下一篇文章讨论语义模块。现在,请注意观察XML文件的结构。其中一个rule-set元素包含了 一个或多个rule元素,rule元素又包含了parameter,condition和consequence元素。Condition和 consequence元素包含的内容和Java很象。注意,在这些元素中,有些事你可以做,有些事你不能做。目前,Drools使用 BeanShell2.0b1作为它的Java解释器。我在这里并不想详细的讨论DRL文件和Java语义模块的语法。我们的目标是解释如何使用 Drools的JSR94 API。
       在Drools项目CVS的drools-jsr94模块中,单元测试代码包含了一个ExampleRuleEngineFacade对象,它基于 Brian Topping的Dentaku项目。这个fa?ade对象通过javax.rules API,创建了供RuleExecutionSet和RuleSession使用的一系列对象。它并没有完全包括了Drools引擎API的所有特性和细 微差别,但可以作为新手使用API的一个简单例子。
      下面的代码片断显示如何使用规则引擎的facade构造一个RuleExecutionSet对象,并通过它获得一个RuleSession对象。
 
import java.io.InputStream;
import javax.rules.*;
import org.drools.jsr94.rules.ExampleRuleEngineFacade;
public class Example {
    private ExampleRuleEngineFacade engine;
    private StatelessRuleSession statelessSession;
    /* place the rule file in the same package as this class */
    private String bindUri = "myRuleFile.drl"
    public Example() {
        /* get your engine facade */
        engine = new ExampleRuleEngineFacade();
        /* get your input stream */
        InputStream inputStream =
                Example.class.getResourceAsStream(bindUri);
        /* build a RuleExecutionSet to the engine */
        engine.addRuleExecutionSet(bindUri, inputStream);
        /* don't forget to close your InputStream! */
        inputStream.close();
        /* get your runtime session */
        this.statelessSession = engine.getStatelessRuleSession(bindUri);
    }
    ...
}

       在以上的例子代码中,你需要处理InputStream的IOException例外,这里为了简单起见省略了。你要做的只是构建InputStream 对象,并把它输入ExampleRuleEngineFacade,用来创建一个RuleExecutionSet对象。然后,你可以得到一个 StatelessRuleSession,并用它来执行所有的规则。使用StatelessRuleSession相对简单。我们可以给上面的类添加一 个方法,用来对一个对象列表执行规则:

public List getUndervalued(List stocks) {
    return statelessSession.executeRules(stocks);
}

       该方法输入一个stock对象列表给规则引擎,然后使用规则评估输入的股票对象,并剔除那些不符合价值低估标准的股票。它是个简单的例子,但足以说明问题。
       在ExampleRuleEngineFacade类中,代码会稍微有些复杂。ExampleRuleEngineFacade类创建了一个 RuleServiceProvider对象,并用它创建RuleAdministrator,RuleExecutionSetProvider和 RuleRuntime对象。RuleExecutionSetProvider负责解释InputStream,并创建一个 RuleExecutionSet对象。RuleRuntime对象用来得到一个session,RuleAdministrator用来管理所有的对 象。在往下是Drools核心API,它的核心是Rete算法实现。我在这里不打算详细讨论,但你可以看看 ExampleRuleEngineFacade的代码。
       现在你已经看到了在商业和科研方面使用规则引擎的一些例子,并对Drools项目有了基本的了解。在下一篇文章里,我将讨论DRL文件的结构和Java语 义模块,让你可以编写自己的DRL文件。还将向你解释如何编写你自己的语义模块,讨论salience和working memory的概念。

资源
· Drools Project
· JSR-94 Specification
 
作者
      N. Alex Rupp is a freelance software architect and developer from Minneapolis, and the current JSR94 Lead for the Drools project.
posted @ 2006-03-11 10:00 Dion 阅读(1723) | 评论 (0)编辑 收藏

一般情况下, 只显式引用:

  • drools-all-2.0.jar
  • antlr-2.7.5.jar
  • xercesImpl-2.6.2.jar

就可以了.当然ClassPath下也要用一些其他的jar.
下载位置: http://dist.codehaus.org/drools/distributions/drools-2.0-bin-withdeps.zip

如果, 在DRL文件中定义了Java Function, 这时候就要显式的引用:

  • janino-2.3.2.jar

这时候, 引擎是需要janino把DRL中的java function描述转换成可执行的二进制代码(?)的.

posted @ 2006-03-11 09:59 Dion 阅读(952) | 评论 (0)编辑 收藏

Drools and Mandarax

两个项目做了两件不同的事情: 一个是Forward Chaining,另一个是 backward chaining. Drools 是forward chaining的,  意味着 它对assert的对象反应, 事件驱动的. Mandarax 是 backward chaining的, 像 prologue一样, 你问它问题, 它试图给你它知道的答案. 举例来说, 在使用Drools的时候, 你可能会先assert 给它今天的日期, 如果它发现有匹配的规则的手,它会用事件的方式通知你"今天是你的生日". 在 backward chaining 的系统, 你可能先问 "今天是我的生日嘛?" 系统会搜索它知道的, 然后告诉你答案.
For an excellent explanation of forward and backward chaining read Charles Forgey's recent articles at http://rulespower.com/ - Forward and Backward Chaining:
Parts 1, 2 and 3.
posted @ 2006-03-11 09:58 Dion 阅读(1162) | 评论 (0)编辑 收藏

Open Source Rule Engines Written In Java

  • Drools The drools engine uses a modified form of the Rete algorithm called the Rete-OO algorithm. Internally it operates using the same concepts and methods as Forgy's original but adds some node types required for seemless integration with an object-oriented language.
  • OFBiz Rule Engine Backward chaining is supported. Original code base from "Building Parsers in Java" by Steven John Metsker.
  • Mandarax Based on backward reasoning. The easy integration of all kinds of data sources. E.g., database records can be easily integrated as sets of facts and reflection is used in order to integrate functionality available in the object model.
  • Algernon Efficient and concise KB traversal and retrieval. Straightforward access to ontology classes and instances. Supports both forward and backward chaining.
  • TyRuBa TyRuBa supports higher order logic programming: variables and compound terms are allowed everywhere in queries and rules, also in the position of a functor- or predicate-name. TyRuBa speeds up execution by making specialized copies of the rule-base for each query in the program. It does so incrementally while executing a logic program and builds an index for fast access to rules and facts in the rule base, tuned to the program that is running. The indexing techniques works also for higher-order logic. TyRuBa does 'tabling' of query results.
  • JTP Java Theorem Prover is based on a very simple and general reasoning architecture. The modular character of the architecture makes it easy to extend the system by adding new reasoning modules (reasoners), or by customizing or rearranging existing ones.
  • JEOPS JEOPS adds forward chaining, first-order production rules to Java through a set of classes designed to provide this language with some kind of declarative programming.
  • InfoSapient Semantics of business rules expressed using fuzzy logic.
  • JShop Simple Hierarchical Ordered Planner (SHOP) written in Java.
  • RDFExpert RDF-driven expert system shell. The RDFExpert software uses Brian McBride's JENA API and parser. A simple expert system shell that uses RDF for all of its input: knowledge base, inference rules and elements of the resolution strategy employed. It supports forward and backward chaining.
  • Jena 2 - Jena is a Java framework for writing Semantic Web applications. Jena2 has a reasoner subsystem which includes a generic rule based inference engine together with configured rule sets for RDFS and for the OWL/Lite subset of OWL Full. These reasoners can be used to construct inference models which show the RDF statements entailed by the data being reasoned over. The subsystem is designed to be extensible so that it should be possible to plug a range of external reasoners into Jena, though worked examples of doing so are left to a future release.
  • JLisa - JLisa is a powerful framework for building business rules accessible to Java and it is compatible with JSR-94. JLisa is more powerful than Clips because it has the expanded benefit of having all the features from common lisp available. These features are essential for multi-paradigm software development
  • Euler - Euler is a backward-chaining reasoner enhanced with Euler path detection and will tell you whether a given set of facts and rules supports a given conclusion. Things are described in N3.
  • JLog - JLog is an implementation of a Prolog interpreter, written in Java. It includes built-in source editor, query panels, online help, animation primitives, and a GUI debugger.
  • Pellet OWL Reasoner - Pellet is an open-source Java based OWL DL reasoner. It can be used in conjunction with either Jena or OWL API libraries. Pellet API provides functionalities to see the species validation, check consistency of ontologies, classify the taxonomy, check entailments and answer a subset of RDQL queries (known as ABox queries in DL terminology). Pellet is an OWL DL reasoner based on the tableaux algorithms developed for expressive Description Logics.
  • Prova - Prova is derived from Mandarax Java-based inference system developed by Jens Dietrich. Prova extends Mandarax by providing a proper language syntax, native syntax integration with Java, and agent messaging and reaction rules. The development of this language was supported by the grant provided within the EU project GeneStream. In the project, the language is used as a rules-based backbone for distributed web applications in biomedical data integration.
posted @ 2006-03-11 09:58 Dion 阅读(1331) | 评论 (0)编辑 收藏

始终会用上的Common BeanUtils

Beanutils用了魔术般的反射技术,实现了很多夸张有用的功能,都是C/C++时代不敢想的。无论谁的项目,始终一天都会用得上它。我算是后知后觉了,第一回看到它的时候居然错过。

1.属性的动态getter、setter

在这框架满天飞的年代,不能事事都保证执行getter,setter函数了,有时候属性是要根据名字动态取得的,就像这样:  
BeanUtils.getProperty(myBean,"code");
而Common BeanUtils的更强功能在于可以直接访问内嵌对象的属性,只要使用点号分隔。
BeanUtils.getProperty(orderBean, "address.city");
相比之下其他类库的BeanUtils通常都很简单,不能访问内嵌的对象,所以有时要用Commons BeanUtils来替换它们。

BeanUtils还支持List和Map类型的属性,如下面的语法即可取得Order的顾客列表中第一个顾客的名字
BeanUtils.getProperty(orderBean, "customers[1].name");
其中BeanUtils会使用ConvertUtils类把字符串转为Bean属性的真正类型,方便从HttpServletRequest等对象中提取bean,或者把bean输出到页面。
而PropertyUtils就会原色的保留Bean原来的类型。

2.BeanCompartor 动态排序

还是通过反射,动态设定Bean按照哪个属性来排序,而不再需要在实现bean的Compare接口进行复杂的条件判断。
List peoples = ...; // Person对象的列表
Collections.sort(peoples, new BeanComparator("age"));

如果要支持多个属性的复合排序,如"Order By lastName,firstName"

ArrayList sortFields = new ArrayList();
sortFields.add(new BeanComparator("lastName"));
sortFields.add(new BeanComparator("firstName"));
ComparatorChain multiSort = new ComparatorChain(sortFields);
Collections.sort(rows,multiSort);

其中ComparatorChain属于jakata commons-collections包。
如果age属性不是普通类型,构造函数需要再传入一个comparator对象为age变量排序。
另外, BeanCompartor本身的ComparebleComparator, 遇到属性为null就会抛出异常, 也不能设定升序还是降序。这个时候又要借助commons-collections包的ComparatorUtils.

   Comparator mycmp = ComparableComparator.getInstance();
   mycmp = ComparatorUtils.nullLowComparator(mycmp);  //允许null
   mycmp = ComparatorUtils.reversedComparator(mycmp); //逆序
   Comparator cmp = new BeanComparator(sortColumn, mycmp);
3.Converter 把Request或ResultSet中的字符串绑定到对象的属性

   经常要从request,resultSet等对象取出值来赋入bean中,如果不用MVC框架的绑定功能的话,下面的代码谁都写腻了。

   String a = request.getParameter("a");
bean.setA(a);
String b = ....
bean.setB(b);
......

不妨写一个Binder自动绑定所有属性:

    MyBean bean = ...;
HashMap map = new HashMap();
Enumeration names = request.getParameterNames();
while (names.hasMoreElements())
{
String name = (String) names.nextElement();
map.put(name, request.getParameterValues(name));
}
BeanUtils.populate(bean, map);

    其中BeanUtils的populate方法或者getProperty,setProperty方法其实都会调用convert进行转换。
     但Converter只支持一些基本的类型,甚至连java.util.Date类型也不支持。而且它比较笨的一个地方是当遇到不认识的类型时,居然会抛 出异常来。 对于Date类型,我参考它的sqldate类型实现了一个Converter,而且添加了一个设置日期格式的函数。
要把这个Converter注册,需要如下语句:

    ConvertUtilsBean convertUtils = new ConvertUtilsBean();
   DateConverter dateConverter = new DateConverter();
   convertUtils.register(dateConverter,Date.class);



//因为要注册converter,所以不能再使用BeanUtils的静态方法了,必须创建BeanUtilsBean实例
BeanUtilsBean beanUtils = new BeanUtilsBean(convertUtils,new PropertyUtilsBean());
beanUtils.setProperty(bean, name, value);
4 其他功能
4.1 ConstructorUtils,动态创建对象
     public static Object invokeConstructor(Class klass, Object arg)
4.2 MethodUtils,动态调用方法
    MethodUtils.invokeMethod(bean, methodName, parameter);

4.3 PropertyUtils,当属性为Collection,Map时的动态读取:
Collection: 提供index
   BeanUtils.getIndexedProperty(orderBean,"items",1);
或者
  BeanUtils.getIndexedProperty(orderBean,"items[1]");
Map: 提供Key Value
  BeanUtils.getMappedProperty(orderBean, "items","111");//key-value goods_no=111 
或者
  BeanUtils.getMappedProperty(orderBean, "items(111)") 

4.4 PropertyUtils,直接获取属性的Class类型
     public static Class getPropertyType(Object bean, String name)
4.5 动态Bean 用DynaBean减除不必要的VO和FormBean 
posted @ 2006-01-15 20:20 Dion 阅读(5081) | 评论 (2)编辑 收藏

     摘要: Migrate apps from Internet Explorer to MozillaHow to make Internet Explorer-specific Web applications work in Mozilla-based browsersDocument options Print this page'); //--> Print this page E-ma...  阅读全文
posted @ 2006-01-04 19:18 Dion 阅读(1481) | 评论 (1)编辑 收藏

以下以 IE 代替 Internet Explorer,以 MF 代替 Mozzila Firefox

1. document.form.item 问题
    (1)现有问题:
        现有代码中存在许多 document.formName.item("itemName") 这样的语句,不能在 MF 下运行
    (2)解决方法:
        改用 document.formName.elements["elementName"]
    (3)其它
        参见 2

2. 集合类对象问题
    (1)现有问题:
        现有代码中许多集合类对象取用时使用 (),IE 能接受,MF 不能。
    (2)解决方法:
        改用 [] 作为下标运算。如:document.forms("formName") 改为 document.forms["formName"]。
        又如:document.getElementsByName("inputName")(1) 改为 document.getElementsByName("inputName")[1]
    (3)其它

3. window.event
    (1)现有问题:
        使用 window.event 无法在 MF 上运行
    (2)解决方法:
        MF 的 event 只能在事件发生的现场使用,此问题暂无法解决。可以这样变通:
        原代码(可在IE中运行):
            <input type="button" name="someButton" value="提交" onclick="javascript:gotoSubmit()"/>
            ...
            <script language="javascript">
                function gotoSubmit() {
                    ...
                    alert(window.event);    // use window.event
                    ...
                }
            </script>

        新代码(可在IE和MF中运行):
            <input type="button" name="someButton" value="提交" onclick="javascript:gotoSubmit(event)"/>
            ...
            <script language="javascript">
                function gotoSubmit(evt) {
                    evt = evt ? evt : (window.event ? window.event : null);
                    ...
                    alert(evt);             // use evt
                    ...
                }
            </script>
        此外,如果新代码中第一行不改,与老代码一样的话(即 gotoSubmit 调用没有给参数),则仍然只能在IE中运行,但不会出错。所以,这种方案 tpl 部分仍与老代码兼容。

4. HTML 对象的 id 作为对象名的问题
    (1)现有问题
        在 IE 中,HTML 对象的 ID 可以作为 document 的下属对象变量名直接使用。在 MF 中不能。
    (2)解决方法
        用 getElementById("idName") 代替 idName 作为对象变量使用。

5. 用idName字符串取得对象的问题
    (1)现有问题
        在IE中,利用 eval(idName) 可以取得 id 为 idName 的 HTML 对象,在MF 中不能。
    (2)解决方法
        用 getElementById(idName) 代替 eval(idName)。

6. 变量名与某 HTML 对象 id 相同的问题
    (1)现有问题
        在 MF 中,因为对象 id 不作为 HTML 对象的名称,所以可以使用与 HTML 对象 id 相同的变量名,IE 中不能。
    (2)解决方法
        在声明变量时,一律加上 var ,以避免歧义,这样在 IE 中亦可正常运行。
        此外,最好不要取与 HTML 对象 id 相同的变量名,以减少错误。
    (3)其它
        参见 问题4

7. event.x 与 event.y 问题
    (1)现有问题
        在IE 中,event 对象有 x, y 属性,MF中没有。
    (2)解决方法
        在MF中,与event.x 等效的是 event.pageX。但event.pageX IE中没有。
        故采用 event.clientX 代替 event.x。在IE 中也有这个变量。
        event.clientX 与 event.pageX 有微妙的差别(当整个页面有滚动条的时候),不过大多数时候是等效的。

        如果要完全一样,可以稍麻烦些:
        mX = event.x ? event.x : event.pageX;
        然后用 mX 代替 event.x
    (3)其它
        event.layerX 在 IE 与 MF 中都有,具体意义有无差别尚未试验。


8. 关于frame
   (1)现有问题
         在 IE中 可以用window.testFrame取得该frame,mf中不行
   (2)解决方法
         在frame的使用方面mf和ie的最主要的区别是:
如果在frame标签中书写了以下属性:
<frame src="xx.htm" id="frameId" name="frameName" />
那么ie可以通过id或者name访问这个frame对应的window对象
而mf只可以通过name来访问这个frame对应的window对象
例如如果上述frame标签写在最上层的window里面的htm里面,那么可以这样访问
ie: window.top.frameId或者window.top.frameName来访问这个window对象
mf: 只能这样window.top.frameName来访问这个window对象

另外,在mf和ie中都可以使用window.top.document.getElementById("frameId")来访问frame标签
并且可以通过window.top.document.getElementById("testFrame").src = 'xx.htm'来切换frame的内容
也都可以通过window.top.frameName.location = 'xx.htm'来切换frame的内容
关于frame和window的描述可以参见bbs的‘window与frame’文章
以及/test/js/test_frame/目录下面的测试
----adun 2004.12.09修改

9. 在mf中,自己定义的属性必须getAttribute()取得
10.在mf中没有  parentElement parement.children  而用
               parentNode parentNode.childNodes
   childNodes的下标的含义在IE和MF中不同,MF使用DOM规范,childNodes中会插入空白文本节点。
  一般可以通过node.getElementsByTagName()来回避这个问题。
   当html中节点缺失时,IE和MF对parentNode的解释不同,例如
   <form>
   <table>
        <input/>
   </table>
   </form>
   MF中input.parentNode的值为form, 而IE中input.parentNode的值为空节点

  MF中节点没有removeNode方法,必须使用如下方法 node.parentNode.removeChild(node)

11.const 问题
  (1)现有问题:
     在 IE 中不能使用 const 关键字。如 const constVar = 32; 在IE中这是语法错误。
  (2)解决方法:
     不使用 const ,以 var 代替。

12. body 对象
   MF的body在body标签没有被浏览器完全读入之前就存在,而IE则必须在body完全被读入之后才存在

13. url encoding
在js中如果书写url就直接写&不要写&amp;例如var url = 'xx.jsp?objectName=xx&amp;objectEvent=xxx';
frm.action = url那么很有可能url不会被正常显示以至于参数没有正确的传到服务器
一般会服务器报错参数没有找到
当然如果是在tpl中例外,因为tpl中符合xml规范,要求&书写为&amp;
一般MF无法识别js中的&amp;


14. nodeName 和 tagName 问题
  (1)现有问题:
     在MF中,所有节点均有 nodeName 值,但 textNode 没有 tagName 值。在 IE 中,nodeName 的使用好象
     有问题(具体情况没有测试,但我的IE已经死了好几次)。
  (2)解决方法:
     使用 tagName,但应检测其是否为空。

15. 元素属性
   IE下 input.type属性为只读,但是MF下可以修改


16. document.getElementsByName() 和 document.all[name] 的问题
  (1)现有问题:
     在 IE 中,getElementsByName()、document.all[name] 均不能用来取得 div 元素(是否还有其它不能取的元素还不知道)。
posted @ 2005-12-31 17:55 Dion 阅读(926) | 评论 (1)编辑 收藏

这里说说我的经历吧。大学前以及大学前面三年的经历就不说了,因为大学前的高中就是好好学习,大学前三年就是混过来的。

    我上的学校还算可以,虽然不是北大清华这样第一流名牌大学,但至少也算中国的第二流名牌大学了。大学中前面三年都陪伴着游戏过去,所学到的只是些计算机基 础知识。到大四后我突然发现就业的问题就在眼前,而自己似乎什么也不会,于是开始看书。最一开始重点看的是C++,可是后来自从看了一本J2ME的书以后 被Java所吸引。当时虽然学校上过Java课程,但是自己也只是学了很少的皮毛,也就只会写写Hello World和什么加减法之类很简单的程序,连API都知道没有几个,比如说字符串长度的API我都不知道。所以刚开始自己学J2ME的时候屡屡受挫,自己 也明白自己的缺点,决定从J2SE开始好好补上。

    刚开始为了熟悉Java开发环境,买了本JBuilder开发的教程,并且在自己的本本上安装了JBuilder进行演练。当时的我连JavaDoc都不 知道,每次究竟什么API能做什么事情一点头绪都没有,还不知道哪里去查,后来同学告诉我有个JavaDoc这个东西,我还兴奋不已,觉得自己被从黑暗中 拉回来了。一开始使用JBuilder的时候,马上为之所吸引,有两个原因,第一是因为它自动标出语法错误,边写代码边提示你什么地方语法出错,记得以前 使用VC++的时候,每次程序写好后先编译,然后再Build,再运行,这其中每个步骤都会出不少错误。特别是在编译的时候,写个200多行的程序一次编 译下来就有100多个错误,结果每次花在这上面的工夫都要好长时间。而JBuilder使用了即时语法分析,所以基本上程序写完,就可以省略调试语法错误 的步骤了。第二个原因是可以自动提示代码,这个功能可以让你迅速熟悉API,免得每次去查帮助文档那么麻烦,我就是这么很快掌握了许多API的。

可能大家会问我为什么一开始不学习《Java编程思想》,的确这本书我们宿舍就有好几本,不过大家普遍反映效果不好,到最后都不知道说的是什么,所以我也没敢看。

    经过20天左右的学习,对Java有了更进一步的了解,熟悉了不少API函数,由于在那本书上写开发SWING占了不少篇幅,所以也对Swing的开发了 解了不少。看完以后因为同学说Java的灵魂就是多线程编程,所以开始看Oreilly的《Java线程》。记得在大学中操作系统这门课我们就提到过线程 的知识。并且课本上就是用Java实现的,当时有了一点点概念,但这次看这本专门说线程的书后才发现我原来了解的那些根本是什么都不算(当然,现在回想起 来,我那时看书学到的也只是很简单的皮毛而已)。看完这本书后我自己学会在我的JBuilder下开发很简单的多线程程序,并且模拟线程冲突,等待等情 况。当时看着自己写的一两百行程序可以顺利执行,那种兴奋劲就别提了。这本书我看得也很快,大概就花了3个星期看完。

    经过上面的学习,自己相比以前来说提升了不少,这时候自己也找到了工作,是做J2EE对日外包的,所以更加坚定了努力学习Java的信心。

    在上面写的程序中,我自己写程序没有规范性,在代码编写的时候自己的盲点特别多,还容易犯低级失误。同学有一个《Effective Java》中文版,可是我看了几页发现自己根本看不懂,里面什么静态工厂啊,什么单例模式什么的根本不知道什么东东。我知道自己目前的水平还不够,所以决 定放下这本书,去寻找别的适合我的书看。这个时候我看到了候捷先生翻译的《Practical Java》一书,当时是刚刚上的书架。这本书我在书店翻了下目录后就感觉如获至宝,马上买回家,在回家的公车上就贪婪地读起来。这本书不算很厚,但是自己 看得却很认真很仔细,也明白了不少东西,比如Java中等号和equals()方法的区别,究竟什么时候用什么。还有Exception处理机制,以前不 知道什么叫Exception,只是JBuilder提示我要我抛出Exception我再抛出Exception,自己觉得这东西基本没什么用呢。但是 看了这本书后我改变了看法,我发现Exception是个很好的东西,可以迅速把程序从正常状态和异常状态区分开来,即使而准确地在指定位置得到处理。那 时自己也有了以后写程序的时候注意编写异常处理部分的想法。《Practical Java》这本书虽然不厚,但是我却非常仔细地去看了,大概花了1个月时间,我把这本书完全消化了下去。

    当时听说Java在网络上的应用非常广,我也不知道究竟是什么应用,我于是买了Oreilly的《Java网络编程》这本书。这本书虽然很厚,其实前半部 分内容不是很复杂,后半部分写什么RMI的东西我也看不大懂,只能理解个概念。通过这本书,我了解了HTTP协议究竟是什么一个东西,在它上面利用 Java传输数据该如何做,知道了什么是Request,什么是Response。这也为以后开始我的J2EE之旅打下了很好的基础。当时自己依然是边看 书边自己写代码来验证,自己写了个服务器端Socket和客户端Socket,成功进行了通信,又在上面加上了安全Socket内容,实现了SSL通信。 当时我把写的这个又套上了Swing的外壳,还和同学拿这个传文件呢。不过当时也没有考虑过什么校验码之类的东西,所以传传小文件还是可以的,文件稍微一 大一点,传过去的文件总是不对头,和我原来的文件经常会出一些差异,导致文件打不开。

    《Java网络编程》这本书看了不少时间,因为书比较厚,东西也比较多,不过除了后面的一些知识以外,其他的还是容易理解的。大概花了2个月左右的时间看 完。看完后,时间也到了2004年的3月。我也轮到开始我毕业设计的时候了。我们的毕业设计导师都还不错,给你自己选个课题,我选的是一个B/S结构的在 线简历处理系统,正好和我所学和下面所工作的东西是一条路上的了。这时我觉得我应该往B/S结构上转了,当时在选择先看Servlet还是先看JSP上犹 豫不决。最终决定先看Servlet,后来也证明了我的决定是对的,我在熟悉了Servlet后再学JSP是非常容易的,基本上根本没有遇到什么难点。

可 能有人会觉得我看了好多Oreilly的书,虽然我不能说Oreilly本本都是好书,不过相对来说,好书的概率总超过许多其他的出版社,而且体系比较齐 全。我看得几本书我都觉得还不错。现说说下面这本我学Servlet时候看的《Java Servlet编程》来说吧,很不错的一本书,让我迅速知道了什么是Servlet,然后通过最简单的实例,让你知道了Servlet如何运行的,跟 HTTP协议是如何配合的,如何返回HTML形式的文本,XML配置符该如何写,究竟每个元素是什么意思等等。由于我原来有一定的XML基础(知道XML 语法各种格式的含义而已),所以掌握起来还算比较快。通过这本书,我知道了如何动态生成HTML文档,知道如何把一个Servlet映射到一个虚拟的地 址。在后半部分写到了数据库操作部分,我对数据库的了解其实也仅限于原来大学课本上的《数据库系统原理》,如何从程序和数据库交互是一窍不通。通过数据库 操作这章,我知道了如何使用JDBC语句如何编写,大家不要笑,对于当初一个新手来说,这个真是一个全新的领域,做什么事情都需要Sample来对照,跟 着依葫芦画瓢吧,其实现在的软件开发也是这样,我想现在大家谁能直接手写Struts或者Hibernate的配置文件都很难吧。闲话少说,大概这个时 候,我对毕业设计的雏形有了点思想上的概念。看完了《Java Servlet编程》后紧接着就又看Oreilly的《JSP设计》,由于有了Servlet的基础,学起JSP特别快。当时没有着重看Tag的自定义设 计,光看了JSP的其他东西,终于在五一节后把毕业设计都写完了,当时总代码量是2000多行,第一次写这么多代码的程序觉得很有成就感。现在看起来那时 做的是标准垃圾,但是当时觉得还是很不错。用了Servlet + JSP。其实Servlet也不是用来当控制器的,而是和JSP做的差不多功能,都是作view的功能的。很快,毕业设计交差过去了,写写毕业论文,准备 答辩。在这个过程中,我又一次考虑自己下面该看什么书。

这次我又看中了侯捷翻译的一本巨著,也就是鼎鼎大名的Martin Fowler写的《重构——改善既有代码的设计》这本书。刚开始听见重构这个名字,总觉得非常高深,加上都评论说重构是和设计模式齐名的东东,感觉更加高 深恐怖了。大概在6月初我开始看了重构,刚开始看的时候虽然抱着试试看的心态,不过还是非常认真的。但是,让我颇感意外的是重构并不是很难,至少这本书中 说的非常通俗易懂,通过大量的实例让你觉得重构是种很简单很基本的技术。虽然我看完了重构以后在真实的代码编写中很少直接按照上面代码所说的方法进行重构 代码,基本上都是通过IDE来重构代码,但是却大大提升了自己编程思维,从此以后写代码就很少瞻前顾后了,因为我拥有了重构这个工具。这本书有点厚,再加 上中间有答辩,拍毕业照,以及毕业手续等等,这本书我花了一个半月看完。我看书的速度也不算快,不过我看书比较有恒心,不像有部分人看几天就不想看了,我 能坚持天天看,所以总的来说还是不慢的。我计算过,如果我每天看10页书,坚持下去,那一年就是3650页书,平均一本书365页来算,1年就是10本。 如果这10本书中有8本不属于垃圾书籍,那么你这年就能有非常大的提高了。

看重构这本书中间我也抽了一段时间看了两本其他的书,第一本是 《Java夜未眠》,挺不错的一本书,虽然是散文,但是还是能让你明白不少道理,受益匪浅。另外一本就是李维的《Borland传奇》,由于自己当时最喜 欢用的工具就是JBuilder,所以也对Borland公司非常敬仰,特别对安德森,简直就顶礼膜拜啊,哈哈。这本书写得很精彩,写了Borland公 司二十年来的血泪史,写了如何跟微软斗争,如何在微软和IBM的夹缝中生存。当然,也有很多的对于技术方面作者李维自己的见解,看了会有不少同感的。就这 样,磨磨蹭蹭地把重构看完了。

    当看完了《重构》这本书之后,我也开始去公司报到上班了。可以看出来,我当时工作的时候水平也很有限,但总比一年前要好不少,至少很多东西都已经知道了。 那时外面极限编程听的比较多,自己也去书店买了本《Java极限编程》回来看,现在想想算是我看得第一本垃圾书籍了。不过也是有收获的,这本书极限编程也 就说了点概念,然后就写了不少工具的使用方法。在看《重构》中对JUnit有了点认识,不过只是皮毛中的皮毛。看了这本《Java极限编程》后对 JUnit的使用又了解了不少皮毛,对于Cactus有了点了解,对Ant了解了不少,至少可以自己写出自己需要的配置文件了,并且可以结合JUnit生 成测试Report。由于我去的是日企,做对日外包的,所以公司开始培训日本语,用的是《标准日本语》这套教材。我于是边学日语边看技术,大概2个星期左 右我把那本《Java极限编程》初步看完后就扔在了家里。这时的我已经开始会用Ant了,觉得是步入J2EE的重要一步。

    很快啃掉那本垃圾书以后又看了本和Java不是非常有关的书:《程序员修炼之道——从小工到专家》,原因其实很简单,大学同学都说这本书是经典书,看书这 东西,别人的评价还是能起不少作用的。这本书字数不是很多,不过排版的时候比较分散,导致书本有点厚,呵呵,可能也算出版社赚钱的一种方法吧。不过总的来 说,我觉得出版社纸张质量最好的是电子工业出版社,其次是中国电力出版社,最烂的恐怕就是机械工业出版社了,机械工业出版社有少量书纸张还能说过去,但有 不少简直让人不得不有脾气啊,纸张薄得感觉和写毛笔字的宣纸都差不多了。这本电子工业出版社的书纸张质量的确不错,不过也许是因为我功力尚浅,所以这本书 虽然都看懂了,但是深有感触并且铭记于心的没有几个,现在再回想,也只记得软件模块设计时要正交等等少数几点了。这本书由于内容不是非常多,所以我就看了 半个月不到搞定。这时的我开发IDE已经转移到了Eclipse上,毕竟商业开发用D版有点说不过去,而且公司也怕查,所以不允许用JBuilder,鼓 励大家用Eclipse。我用了一段时间的Eclipse后,从一开始的不适应到后来觉得Eclipse很方便使用,JBuilder比Eclipse多 的就是一些根据不同类型开发的模版而已,而这些可以由Eclipse的插件来弥补。到了这时,我觉得我的Java基础应该算还可以的了,API也熟悉了非 常多。我觉得看《Effective Java》的时机成熟了。

    由于大学已经毕业了,所以也不会有同学的《Effective Java》放在边上让我看这样的好事出现,老老实实地去了书店买了本《Effective Java》中文版回来研读。呵呵,大家也许会问我为什么不买本E文的看,虽然我大学早早也把英语4级过了,而且大学中不少计算机专业课程教材也是E文的, 当时为了考试也认真读了。但是毕竟英语不是我们的母语,看起来速度上明显比中文版的慢一截。当然,如果是那种垃圾翻译者用机器翻译出来的中文版,看那些垃 圾中文版速度肯定比不上直接看英文原版的。这时的我看《Effective Java》已经不再是当初的那么感觉很陌生了,觉得上面说的那些要点自己想想还都是可以理解的。我个人觉得提高自身编程习惯以及水平最多的还是看类似于 《Practical Java》和《Effective Java》的这种按照条目来进行说明的书,能把你自己平时容易忽略的地方按照重点一个个揪出来进行修正。比如《Effective Java》中的第一条,使用静态工厂来代替构造函数,自己原来在进行开发的时候,从来不怎么会主动想到建立一个静态工厂,总觉得使用构造函数来新建一个对 象是天经地义的事情。但看完这个条目后,我的看法也随之改变,发现静态工厂还是非常好的,当然,也不是什么地方用静态工厂都很好。上面也写到了静态工厂的 优缺点,比如在什么地方适合使用,什么场合最好不要使用等等。这本书我觉得翻译的也不错,绝对值,强烈向有一定开发经验的人推荐。我大概看了3周半的样子 把这本书看完,这时的时间也到了2004年的9月初,新员工入司培训也不再是第一个月纯粹的日语培训,而是技术培训和日语培训一起开展,技术上培训 Java,Web开发,数据库开发这三门课程,日语则开始进行日本语国际三级的培训。公司的日语培训和技术培训都还不错,技术培训纯粹把大家当作什么都不 懂的人,在Java上从最原始的Hello World开始培训,Web开发上从HTML页面开始培训,数据库开发则从Oracle的安装,SQL语句的编写开始培训。当然,在培训的过程中我也不会 闲着,而是又开始寻找自己要啃的书本,这次,我选中了Oreilly新出版不久的《Java与XML》第二版。

    由于XML表达数据的自由性以及强大型,所以XML特别适合于做配置文件以及数据信息文件,在Java中XML的使用可谓是多如牛毛。在J2EE中,从 Web Application的web.xml开始就是XML文件,到下面的Framework配置等等,没有一个没有XML的身影,而且XML都起到了举足轻 重的作用。虽然我原来也懂一点XML,不过也仅限于XML的语法以及结构等等,那些深入下去的东西基本还是盲点,关于Java中如何处理XML更是一窍不 通。为了更好的学习J2EE,XML是必须征服得一座山峰。这次,我依然又再一次信任了Oreilly出版社,买了本当时出版不久的《Java与XML》 中文第二版。这本书刚开始并没有过多介绍XML本身过多的东西,只是为了怕某些读者并不了解XML而对XML语法结构等做了非常简要的介绍,不过也非常到 位的介绍。介绍完了这些XML基础知识后就开始了SAX——〉DOM——〉JDOM——〉JAXP——〉Web Service的历程。不过我现在觉得如果能介绍DOM4J就更好了,因为我现在觉得DOM4J是Java中最好用而且性能也不错的XML处理工具。刚开 始的我其实什么是SAX,什么是DOM都不知道,对JAXP更是一无所知。这本书英文版据说很受好评,中文版我只能说一般,因为有些地方估计译者并不擅长 这一块,所以翻译得很生硬,以至于部分段落难于理解。总体来说,书的绝大多数内容还是可以看懂,由于没有具体实际操作的经验,所以很多也就是把概念理解 了,直到几个月后做正式项目开始应用这些XML处理工具进行开发的时候才达到了熟练运用的能力。在这本书中学会了JDOM的使用方法,JDOM也还是比较 好用的,学会了JDOM,以后操纵XML也方便了许多。这本书我的建议就是,可以一口气读到第十章JAXP部分,后面的Cocoon以及SOAP等等部分 那本书介绍的并不是很好。Cocoon我是看了官方专门的帮助文档以后才感觉入了门。而SOAP是经过别的书籍加上项目中的实际运用才真正学会的。

这 时到我刚进公司已经两个月过去了,时间已经到了9月中旬的样子,还有一个月我们公司新员工入司培训就要结束,也意味着还有一个多月我们就要开始接触正式项 目。这时的我写B/S程序仅仅是JSP + JavaBean的水平,连JSP中的TAG都不会自定义,看见别人网上的程序自己还自己定义Tag很是羡慕,于是决定把那本《JSP设计》认真看完,把 自定义Tag的功能实现。后来看了以后发现原来那本《JSP设计》的精华都在最后的150页内,最后那部分先是介绍了自定义Tag的定义方法以及Tag定 义所带来的一些好处。自从学会了如何自定义Tag,在后来公司的项目中自己也根据项目的特点定义了一些共通的Tag,大大方便了不少项目中的开发人员,提 高了生产力。这本书而且也说了一下B/S开发的两种Web Module。在这里,我第一次知道了Web开发可以用一个Servlet作为控制器,用JSP仅仅作用于表现层,这也为以后掌握MVC打下了很好的基 础。

9月中下旬扫完了《JSP设计》的尾巴后,有一次跟公司给我们培训的老师在闲聊时谈到了项目开发,我询问他项目是不是用JSP和 JavaBean来开发,他笑着和我说不是这样的,而是基于Framework来进行开发。比如Struts就是公司的常用Framework。 Struts这东西以前也好像听说过,不过从来也只是听说而已,并没有看过。得到这个信息的我,为了能尽快熟悉实际项目的开发环境,便决心尽快学会 Struts。当时的市场上讲解Struts的书只有一本,也就是Oreilly的《Jakarta Struts编程》,不像现在连《Struts in Action》的中文版也有了。我去了书店买来开始回家看,刚开始看的时候觉得如同云里雾里一般,因为这本书归纳总结性的东西很多,比较适合当参考手册, 而真正带领新手入门这一块做的并不好。所以当我把这本书都看完了以后,还是不会用Struts编写一个程序,只是感觉自己朦朦胧胧懂了一些概念,比如 MVC什么的。在公司我们的培训也结束了,通知在国庆节过来以后的第一个星期——大概是10月10日左右进行考试,最后根据培训考核情况来调整薪水。当时 跟我一起培训的新员工基本上没有人会Struts,其实这个时候连会用JSP + JavaBean写一个最简单的登录画面的人也没有多少个,大部分人还是模模糊糊懂一点,但是具体做东西还是做不来的那种水平。国庆节大概10月5号的我 去了趟书店,突然发现书架上新上了一本书,就是孙卫琴编写的《精通Struts》这本书。孙卫琴的书我倒是听说过,就是在这之前出的一本关于Tomcat 以及Web App开发的书,据说挺容易上手的。我翻看了这本书的目录结构,觉得可以值得一读,于是虽然价格不菲,仍然买回家当天就研读起来。凭我的读后感觉来说,这 本书也许学术价值并不高,说得深入的东西基本没有,但是特别适合初学者,通过Hello World这种例子迅速让你手把手编写出第一个Struts程序。就这样,在这本书买回来的第二天,我自己就用Struts写了一个很简单的登录画面程 序,当时的感觉别提多兴奋了,就感觉自己入了门,以后的道路一片光明。在这里,我要由衷地感谢孙卫琴女士,写了这么一本适合初学者的书,同时也建议没有学 过Struts但又想掌握Struts的Java程序员们买这本书回来看(不知道我是不是有书托之嫌,我只是说我自己的心里想法)。

    国庆的假期放完了,我也回到了公司准备考核,上午是笔试,下午是上机考试。笔试分为了4块,分别是Java,Web开发,Oracle数据库,以及 CMMI规约。这四门除了Oracle数据库我一向不是很擅长,只考了个中等分数以外,其他三门分数都名列前茅。不过CMMI规约老实说我也不怎么会,不 过碰巧考的很多都是我知道的东西。下午是上机考试,题目给出来了,我一看题目,原来是一个最简易的成绩查询系统,也就是数据库里面已经有一些学生成绩,我 们写一个检索页面,可以输入或者选择检索条件,把符合我们检索条件的数据输出并显示在画面中。我于是拿刚学会不久的Struts进行编写,在3个小时内把 整个页面都写好了,并且还自定义了一个Tag来显示数据信息。考完以后我才知道总共也就五六个人程序可以运行,而且只有我一个人用的是Struts,其他 人基本都是最简单的JSP + JavaBean,有的人连JavaBean都没有,数据库操作全部写在了JSP页面中。毫无疑问,这次上机考试我得到了好评,给了最高分。在全部的培训 成绩中我也居前两名,我们部门新员工我排第一名。带着这个成绩,我们的入司培训基本结束,开始进入部门做实习项目。

    虽然说我们正式进了部门,不过试用期还没有结束,我们试用期最后一个月的任务就是做一个实习项目,当然,每天还是要进行日语培训,因为要参加12月份的国 际日语三级考试。公司也象征性得给大家培训了三节课的技术,第一节是Struts培训,第二节是Web App的MVC结构的培训,第三节是Log4j培训,这几次培训下来,大部分人感觉好象云里雾里一样,基本什么都没听懂,不过我由于有了点Struts的 基本知识,所以感觉收获比较大,特别是MVC的培训中我真正明白了视图——控制器——模型这三层每层应该怎么处理,知道了一个Web App中如何分Java Package比较好,明白了专门有一个DAO层来处理数据库所带来的便捷,明白了Log在Web开发中的重要地位,这为以后的开发带来了很大的好处。实 习项目的课题很快就下来了,要我们做一个电子相册的B/S系统,要求有图片上传,图片检索,图片显示以及要用Struts来构建,这些是基本的要求,其他 功能可以自由扩张。我们部门的新员工分为两个小Group,都是一样的课题,互相促进和学习,每个Group还配备了一个老员工,作为督促我们的进度,防 止我们有过大的偏差等等,不过具体做东西上原则上要求是不会给我们什么帮助。首先每个小Group要选出一个Leader,结果我被大家一致选为我们 Group的Leader。在小组讨论中我们先进行需求分析,大家的讨论很是热烈,主意也很多,不过基本上组员们也都是点子多,具体实现上面还都没有想 过。对于他们的那些建议,绝大多数我决定都作为我们要实现的目标,但也有少部分我觉得目前以我们的水平还无法实现的,就先否决了。会议开完后,当天回家以 后我就开始排开发计划和个人的进度,第二天写画面的基本设计,第三天把组员拉过来开始Review基本设计,我们组的速度还算比较快。从星期二公布课题, 到星期五就和几个组员一起把DEMO画面设计出来了。原来的计划是第二个星期一开始Coding,大概花一个星期完成。不过其余组员似乎还是不怎么会 Struts,于是我回家星期六星期天基本全天都在看书写代码学习,花了两天把项目基本Coding完毕。其中Web页面部分也不再使用一开始使用 Frame的做法,而是采用了Tiles框架。Tiles的使用过后我感觉是非常好的东西,经过简单的配置可以完成大批网页中类似部分的构建,而且生成的 属于一个页面,这样就省去了以前写Frame时提交页面总是要考虑设置Target以及在引用对象的时候大批Parent或者top对象使用的麻烦事了。 在开发过程中我使用了Log4j,这为我的调试程序带来了极大的方便,呵呵,可以想象,没有Log来调试一个Web程序真是不可想象的。

这 段时间我是边开发边翻查那本《精通Struts》的,这样,迅速把Struts中提供的许多Tag弄熟练了,为以后具体的项目开发带来了便捷。也许是一向 以来公司的实习项目完成效果都不是很理想吧,这次我们的迅速完成比较出乎Leader的意料,综合三个月的试用培训,由于我在日语和技术以及实习项目中表 现都还不错,所以定工资级别时也是同一批进公司的新员工中最高的,随着转正会议的结束,我也在10月底成为了公司的正式员工。大概刚刚进入11月份,我们 Group便开动一个项目,项目不是很大。当时老员工们许多都在做项目的详细设计,我便跟着公司一位技术专家(也是当初给我们入司培训的其中一位老师)做 起项目的Framework构建工作。当时的我才进公司,第一资历尚浅,第二我的确也并不是很会什么东西,所以给我的任务很多都是一些模块的 Utility的设计。比如通用的Check方法啊,CSV以及定长文件的读取解析什么的啊,还有某些在IE中可以实现的效果如何在Netscape中也 能实现同样的效果等等。虽然这些东西在现在看来并不是很复杂,但是当时自己的确随着做这些东西而学到了很多很多。比如使用JDOM对XML文件的解析啊, 很多Check方法的小技巧啊,IE和Netscape究竟有什么地方不一致,该如何解决等等,这些都在这几天内了解了很多。在这几天中,我通过网上查找 资料,临场迅速学习了Java反射的使用方法,并且自己边学边写实例,实现了各种情况下的反射案例。我个人觉得掌握Java反射技术是非常重要的,这让你 可以写一些通用的工具。如果不会反射技术的话,也许你永远只能写一些针对特定情况下的解决方法。而会使用反射以后,你可以写一些代码,这些代码可以用在许 多地方,达到自己扩展甚至构建Framework的效果。在这个项目中,我使用了自定义Tag和Java反射技术,定义了些项目中比较需要的通用的 Tag,方便了大家。

    后来听老员工说新员工进公司就开始做Framework是以前从来都没有过的事情,因为我们Leader对我希望比较大,所以想尽可能培养我,让我早点挑 起项目大梁,所以给我的成长提供了一次又一次的机遇。11月中旬以后,项目开始进入编码阶段,我也第一次看到了正式的项目设计书。第一次看到设计书的时候 我都觉得自己脑子有点懵,一大堆日语什么含义自己不是很清楚,而且感觉根本无从下手,不知道从哪里开始看比较好。项目担当耐心得和我说了设计书的格式以及 究竟什么地方是什么一个含义,以及Coding的时候按照什么个思路来看设计书。再加上项目中有老员工先写了个Sample,让大家看了标准的一个流程, 所以我们就依葫芦画瓢,慢慢得把一个画面一个画面Coding完毕。当然了,后来也有测试员来测试我们的画面,发现bug后就发Bug Report给我,那一个月就是在Coding,修正Bug中渡过的,这个项目是用Struts做的,因为不大。所以也没有再用其他的 Framework,数据库操作那里只有个非常简单的单表操作DAO层,其余的DB操作都是自己通过JDBC操作语句来完成的。在这第一个自己接触的真正 项目中,我自己学到了很多B/S设计的技巧,感觉很充实。不过书本学习方面我也没有闲着,我为了能够深入了解Java,大概在11月中旬左右开始看《深入 Java虚拟机》这本书,由于内容比较深入,所以看得也有点吃力。书翻译得和写得都还不错,值得一看,我一直看了前八章,看到Java程序运行细节后就没 再看了,大概看到了12月底的样子吧,呵呵,有时间的话决定把后面的部分也看完。这本书看完后收获就是了解了Class文件的实质,Java的安全模型, 虚拟机是如何工作的。这些知识对后来调试程序Bug或者Exception的时候非常有好处,可以把以前自己觉得莫名其妙的错误的原因找出来,不像以前遇 到很古怪的Exception的时候怎么死的都不知道,从读完这本书以后,在以后的调试异常中很少再有不知所以然的感觉了。

    2004年12月底的时候,我的第一个项目也做完了,由于我空闲着,Leader便在星期三的时候把一个公司内部开发的Source统计的小工具让我进行 修改,使得添加一个比较有用的功能。东西给我的时候基本没有任何文档,在我手上的就是一堆源代码而已,界面是用Swing制作的,因为没有专门在UI上进 行精心设计,所以说不上好看,典型的Java编写的图形界面的程序的样子。软件不是非常大,估计在1万行源代码以内,不过对于只有一个人修改来说,也比较 够呛了。还好我在刚学Java的时候用JBuilder写了一些Swing的程序,现在还是对Swing有概念的,所以拿到手上以后经过仔细分析,逐渐理 清了头绪。经过修改和自己测试完毕后,觉得还比较满意,达到了预期的目标,于是在星期五的时候提交给了Leader。通过这次,对Swing的开发又加深 了印象,自然,在有的细节技巧方面受益匪浅。

元旦很快来临了,在年底以前,公司觉得有必要学习下Hibernate,虽然我们目前的项目中 还没有用过Hibernate,而是用另外一个公司内部开发的ORM工具,不过几名技术专家初步对Hibernate感觉后觉得Hibernate的功能 要强大的多,而且是开源的,不断有人在推动,升级,所以有必要我们要学Hibernate。这次的学习采用学习小组的形式,也就是公司内部先抽几名员工 (主要是技术部门的,当然,开发部门如果有兴趣的话也可以考虑)来进行深入学习,然后定期开会交流互相学习,达到短时间内先行的几名成员迅速深入掌握 Hibernate的形式。由于我第一处于空闲状态,第二也比较有兴趣,而且跟技术部门的专家们也比较谈得来,所以我也加入了其中,成为几名学习小组中成 员的一部分。我们学习资料主要就是《Hibernate in Action》英文版一书以及官方的帮助手册。我负责其中对象操作,Transaction和Cache,还有高级Mapping关系的设置几个部分的学 习。由于资料都是全英文的,所以看书速度并不是很快,不过还是初步得到掌握了。大概学习了半个多月的样子,我们各自基本学习完毕,互相交流后并且写下了读 书笔记,用来后面具体项目开发时候参考用。通过这大半个月的学习,我个人觉得提高了非常多,在这之前,我只知道有ORM这种东西,但是从来没有使用过,也 从来没有看过。自从学过了以后,我不仅对Hibernate在代码编写时的使用比较熟悉了,而且对Hibernate的配置以及许多底层的知识有了很清楚 的认识,让自己在数据持久化方面的认识提高了大大的一步。

    元旦过后,虽然一边在学习Hibernate,不过由于下面项目的需要,Leader跟我说要我学一下Unix下的Shell编程,因为 项目中许多批处理会用Shell来启动。UNIX命令在学校时候学过的,不过这个时候已经忘记了很多,只是翻阅资料的时候还能回想起来不少命令。 Shell并不难,如果有了编程基础,学习Shell编程也很快的,总体感觉就是编程语言大同小异,从基本语法来说,不外乎赋值、条件、循环这几种类型。 只要迅速掌握这几种类型在这种编程语言中的编码格式,那么你就可以迅速掌握这门语言最基本的编程能力。Shell经过一周的学习后觉得感觉不错,不仅可以 顺利看懂别人写的Shell程序,而且自己可以在Linux下顺利写出符合自己需求的Shell程序并能顺利执行。但是突发事件总是有的,那个项目突然决 定延后两个月,所以前一个星期的学得Shell等于暂时派不上用场了。不过嘛,多学一样技能总是没有害处的,而且又复习了那么多Unix命令啦,感觉还是 很不错的。于是我又进入了不在项目中的真空期了。

    但是好景不长啊,好日子还没有过上两个星期,公司去年做的一个比较大的项目开始了2期开发,我也被一下拖入了项目中。说起那个项目,公司好多人还心有余 悸,据说去年那个项目开发的时候,大概50多号人干了好几个月,每天都是11点以后才有可能回家,周六是铁定加班,周日是看情况,晚上就算加班加到凌晨3 点也不是什么奇怪的事情。当时大家都说多来几个这种项目大家就要死了,这次这个项目的2期过来了,大家精神又一次紧张起来咯。一开始照例是项目会议,听完 项目经理描述以后,大家也放心了不少,这次虽然说是二期,不过规模不大,只需要15个人左右干一个月就能搞定。主要是把项目一期里面一些地方进行改修,当 然也有需要新的画面的开发,不过相对于去年的那块不是很多而已。对我来说这次是个很大的考验,因为项目是二期,项目组内除了我,其他的人都做过1期开发, 所以对项目结构都很清楚。这次项目开始并没有什么培训,所以我只能单独看1期的源代码来熟悉项目结构什么的。这个时候项目经理把我叫去要我办理护照,准备 这个项目派遣我去东京现场维护。

这个项目是个比较全面比较大的项目,服务器采取了集群的方式,数据量也是千万乃至上亿级别的,所以性能要求 特别高。在技术方面用到了很多,使用EJB来控制Transaction,使用了ORM工具来操纵DB数据等等等等。而且由于比较庞大,所以服务器初始化 的那块为了Load上去大批配置信息,代码量极其庞大,在权限控制的那块地方,代码非常难以读懂。这也给我一开始的学习代码带来了很大的一块麻烦。不过总 算静下心来后把整个项目框架以及实现手法基本摸清楚了,这个时候觉得非常开心,而且对Web应用程序的构造心里面也非常充实,觉得自己已经具备写 Framework的初步能力了。

项目是紧张的,基本上每天晚上都要加班到11点,然后打车回家,哈哈,公司报销。而且临近过年,这么加班 也一点都感觉不到过年的气息。不过我也不能因此放松了自己的学习。我觉得自己的基础应该算比较好了,便开始啃起另外一本大部头——《Java与模式》。一 直以来我对设计模式的感觉就是一些已经成型的,久经考验的代码框架,具有非常好的可扩展能力以及非常好的代码健壮性。不过初学者最好不要看设计模式,因为 你接触的代码不多,如果贸然看设计模式的话,会造成不明白为什么这种设计模式好,究竟好在什么地方的情况下就在代码中乱套设计模式,对自己的以后编码发展 带来不利的影响。每一种设计模式都有每一种设计模式的特点,自然也有他们自身的适用范围,比如拿最基本的单例模式(Singleton)来说,适合于做配 置信息读取器,主键产生器等全局唯一的东西。如果初学者不明白这些,拿单例模式乱套,什么都用单例模式,比如把普通传递数据用的JavaBean也做成了 单例模式,带来的恶果就严重了。这本《Java与模式》我是从头到尾认认真真看了,每看完一个模式都会仔细回想以前看的代码哪里用到过这个模式,总会自己 想想这些模式适用于哪些地方。因为这个时候我自己编写的代码行数也已经很多了,所以看见这些模式就会特别有感觉。经过50多天的认真研读,所有模式都被我 消化了下去,并且使得我的对程序开发上面的认识提升了非常大的一步。顺路说一句,这本书写得非常好,例子很多,但是不复杂,有一定代码编写经验的人就可以 看懂了。不过看懂并不是最重要的,重要的是消化下去,用来理解以前看过的代码的精华,这样自己才能得到提高。

    这个项目虽然很紧张很忙,不过我还是适应了下来,而且对整个项目结构什么的都有了比较好的整体的把握。项目横跨了整个过年期间,所以在过年的那几天都必须 开着手机,怕有什么突发事件要求去加班。签证在2月4日左右送过去签,Leader跟我说因为在过年期间,所以签证可能会比较缓慢,比较难签,不过一般情 况下1个月应该足够了。原计划我是跟另外两个同事3月6日去东京,这样算算也差不多。不过中国有句话叫好事多磨,呵呵,用在我身上的确不过分,我的签证3 月3日日本领事馆才签,三月四日送到南京。3月5日和3月6日是双休日,所以3月7日签证才送到我手上。由于计划是3月6日派人去东京,所以只好派另外一 个身上有签证还没有过期的人代替我过去,这次的机会就算泡汤咯。不过我并不是很在意,因为公司这里去东京出差的机会狠多,特别对于开发人员,据说工作几年 后一听到去日本出差就不乐意,毕竟也背井离乡么。

    在这个项目的途中,大概在2005年1月底2月初的时候公司也开始进行了制作详细设计的培训,我虽然在项目中,不过也成为了其中一员。这次培训总共大概6 次课,每次2个多小时,虽然时间不长,不过把详细设计的要点以及思路和容易出错的地方都说了出来,感觉很是不错,这几次课的培训后,虽然可能要我立即进行 详细设计编写还有点困难,不过心里面已经有了不少底,我觉得经过一段时间后的锻炼,我应该可以有进行详细设计的能力了。

    3月初这个大项目结束后,本以为可以休整下,不过很快通知我3月7日加入另外一个项目,其实也不算一个正式的项目,属于公司知识库的一个信息查询模块。由 公司的一个技术专家负责,那人也就是我进公司时候第一个项目中带着我的那个技术专家,说起来我和他还真有缘,现在我这个项目还是跟着他,而且公司里面唯一 一个和我同月同日生的人,真是很巧的巧合呢。他人挺好,很热心,所以我也从他那学到了很多东西。这次由于不是正式项目,所以并没有什么基本设计书,而是他 给我们开会议的时候大致说了下项目的内容,每个画面的具体功能以及数据库表格的设计。由于这次项目规模很小,总共就12个画面的量,所以不采取 Struts等Framework,而是采用比较原始的JSP + JavaBeans的构造。我们每个人根据他所跟我们讲解得功能写每个人自己分配到的画面的详细设计,其实也不算真正的详细设计,就是每个人把自己操作的 那块的具体逻辑设计写出来,然后和他一起review一次,再开始编写代码。详细设计这里我做的很快,当天下午就把自己分配到的两个画面业务逻辑什么的都 写好了,星期一布置得任务,我星期三的时候全部编码自测完毕提交,所以我的感觉就好像这个小项目一瞬间就结束了。

日本每年财务结算是在3月 份,所以我们历来的习惯就是每年1月和2月很忙,3月开始清闲,一直可以到5月左右会接到个大项目昨。所以接下来就真正到了我的空闲时期,没有项目的压 力,我可以自由学我自己喜欢的东西。很久以前买了本《精通EJB》第二版,可是一直以来我觉得自己功力尚浅,所以没有看,这次我想认真学学EJB。虽然大 家公认EJB并不是很好,不过历来受到批评的都是EJB中的Entity Bean部分,这部分我觉得可以借助Hibernate来弥补,而会话Bean和消息驱动Bean则还是挺不错的。这次也当学一门技术,学习其好的东西, 不是很好的东西就当作以后开发时候的借鉴。《精通EJB》这本书我的感觉是书质量比较好,不过翻译的水平稍微差了点,特别是有不少错误,而且很低级的错误 居然校对的时候都没有发现,不能不说是个比较大的瑕疵。但是它不失为一本EJB的好教材。从一开始的JNDI开始,然后讲解了对象序列化,RMI- IIOP等等。这些以前都模模糊糊,或者是看过了但是还不知道究竟有什么用。但是经过这次的学习以后,对这些分布式系统比较需要的东西有了进一步的了解, 感觉头脑中比较清晰,究竟RMI是什么样的工作原理,怎样实现一个远程方法调用等等。接下来的EJB学习,自己用Eclipse + Weblogic边看书边动手,写了一个个自己的学习小程序。我个人感觉看书最好就是边看边自己动手写小学习程序,这样比光看不练能学到多得多的东西。学 了EJB后觉得脑子又清晰了很多,看见一个案例后头脑中就会有好几种如何解决的方法,几种方法互相在头脑中自己比较,经过这样,大大提高了自己的思维活跃 性。

    3月中旬开始由于公司比较清闲,大部分人处于没有项目的状态,所以公司举办了第一届全公司范围的编程竞赛。公司只指定了题目为一个日历系统,要求具有日程 记事等功能,其余功能自由发挥。这次不再采用团队形式了,而是采取各自为战的策略。自从培训过详细设计以后,我头脑一直有如何写详细设计的思路,这次我自 己首先指定了开发计划,保证自己控制自己的进度。接着进行了需求分析,确定了我有哪些功能。然后在自己的基本设计中开始进行数据库结构设计。这次我决定采 用Hibernate+Struts的结构进行编写,这样我的数据持久层操作大大简化,而且功能上也增强了许多。DB设计好以后我开始DEMO画面的制 作。说实话,我美工水平实在不怎么样,可以说虽然一般网页的效果我都会自己做出来,不过具体网页设计成什么样我还真是一窍不通。还好 Dreamweaver我还算算是比较熟练,自己捣鼓捣鼓也想摸象样把DEMO画面给设计出来了,不过美观不美观我就觉得不怎么样了,只是我能力有限,也 没办法设计的更好看,这个时候我感受到了一个项目中美工是多么重要啊。下面的详细设计自己写得很开心,把需要的功能都用文字反映了出来,这也算我写成详细 设计样子的第一份详细设计了,做完挺有成就感的。接下来首先构筑自己这个小项目的Framework,经过公司两个正式项目的洗礼后,那两个项目的 Framework我都认真研读过源代码的,所以我自己有了自己心里一套Framework的构造方法,特别是如何把Struts和Hibernate结 合起来的结构,自己有自己的一些想法。在这次Framework构造中,我没有复制任何公司以前的代码段,都是凭着自己对以前看的代码理解后写出来的。这 次项目我觉得对自己的提高也很大,首先锻炼了自己详细设计的能力。其次,自己虽然学习过Hibernate,不过从来没有这么样应用过 Hibernate,这次让自己大大提升了实践运用的经验。公司由于知道这时也没有一个真正的项目使用Hibernate,所以这时的我也算公司内部 Hibernate使用经验最丰富的人了,这也为了后来我协助别的使用了Hibernate的项目解决问题的原因。再次,我这次自己写了 Framework,特别在批处理方面,运用了许多刚学会理解掉的设计模式,这些模式让我的程序更具有健壮性和可扩展性,让我在设计方面的能力大大提升 了。

这次的编程竞赛我写得比较认真,代码量的确也很大,总代码行数超过了3万行,有效代码行数也在1万行以上。经过公司专家们的评定后,我 得到了第一名,虽然没有什么奖品,不过肯定了我这段时间以来的努力,我还是很开心的。而且这次的编程竞赛让我大大增加了编码的熟练度,而且也在其中演练了 许多自己想出来的编程技巧,为以后的发展带来很大的好处。

    从4月份开始,公司由于比较清闲,所以部门内部开始进行各种培训。我们部门开展了3项培训,第一项就是编程能力培训,第二项是Oracle数据库技术培 训,第三项是测试技巧培训。在编程能力培训中,主要就是把原来没有注意的细节采取大家讨论,轮流讲课的方式进行的,虽然其中很多东西我原来都是知道的,不 过也有原来不清楚的地方。而且经过了这次互相讨论,更加加深了印象。在Oracle培训中我觉得收获很大,这个Oracle培训采取了传统的上课的模式, 由我们开发小组中一个取得OCM的老员工给我们讲解。对于Oracle,我原来基本上就只会写写SQL语句,具体Oracle有什么特别的功能,可以做什 么我也不是很清楚。但是这次上课从Oracle的启动原理开始,让我知道Oracle中究竟有什么,Oracle数据库各部分在磁盘上是如何存放的, Control File,Redo File究竟是什么意思,在数据库中起什么作用,数据库是怎么依赖他们运行的,还有如何对Oracle进行系统管理员级别的管理,如何在不停止数据库运行 的情况下进行数据库的更新、升级、备份等等。这些东西虽然非常有用,但在平时的开发是学不到的,这次趁着这个机会大大提升了自己Oracle的水平,感觉 非常开心。数据库一向是我的弱项,在上大学的时候我SQL语句能力只是一般,数据库管理配置什么基本一点都不懂,通过这次集中的培训,我觉得自己的能力又 进一步增强了,弱项也在慢慢退却。在三项培训中最后进行的测试培训我承认我没有怎么认真去学,所以学会的也就是些测试概念,具体的测试技巧什么的还是不怎 么会。现在开发和测试的结合性越来越高,看来要下下功夫,以免给淘汰咯。

    提了这段时间在公司的进展,还没说自己的学习呢,这段时间正好看见中文版的《JUnit in Action》出版了,在书的背后写着“如果没有看过这本书,就不要对J2EE进行单元测试”这句话。我早在去年就了解了JUnit的强大功能,再加上 Ant的话对于回归测试是非常便利的。趁有时间,我便于3月底4月初的时候开始看这本书。当时的我看《精通EJB》第二版看了一半,发现其中错误越来越 多,而且文字也有些地方不知所云了,所以扔下不再浪费时间看那本书,专心攻读《JUnit In Action》。凭良心说,Manning的这套In Action丛书的确很不错,从我先前看的《Hibernate In Action》英文版就能看出来,其中对代码的编排非常方便读者,感觉可以很顺利的看到你所想看到的代码片断。这套《JUnit In Action》也是一样,博文视点的纸张还是很好的,排版使用了Manning的风格,阅读起来很舒服,所以我读得很快,大概就两个多星期就读完了这本 400多页的书。感觉的确收获不浅,首先,原来的自动化配置工具中只会使用一个Ant,其他的基本没听说过,在这本书上详细介绍了Maven。听过书中的 讲解以及自己的试验,的确觉得Maven功能很强大,不过感觉起来配置比Ant要麻烦,所以我自己的感觉是Ant在项目中还是会广泛应用,不过Maven 在大型项目,特别是整个Site中有很大的用武之地,对于我们来说,使用的方法都是很简单的,掌握如何编写配置文件才是我们的关键。

书对 JUnit与Cactus在J2EE的测试手法上给了大量的事例,给人的感觉非常好,In Action这套丛书最大的优点就在这里,用实例代码片断让你迅速了解一样东西。在实际工作中其实JUnit应用也是比较广泛的,特别如果采取测试驱动开 发的话,JUnit是必不可少的一部分。在TagLib测试,JSP单体测试,数据库测试和EJB测试都是我以前根本没有看过的东西。其实这次虽然学是学 会了,不过真正做的时候还是要有个代码例子依葫芦画瓢。我想大家肯定也都有这种感觉,写程序的时候先找一段有点相似的代码片断Copy过来,然后看看要修 改什么地方,真正从头到尾自己用手写的代码片断是不多的,除非你已经烂熟于心。不过这本书快看完的时候,项目又来了。

    这次做一个企业的MIS系统,与以往不同的是,这次客户给了一个比较庞大的基盘,封装了近100个Tag,基本上把各种各样有可能遇到的操作都封装到 Tag里面了。而且所有的画面显示等信息都是放在数据库的Table中,所以这次要求不写任何程序代码,只是学会使用好这些Tag,然后利用这些Tag写 出Jsp页面。一开始的时候还真是头疼,这些Tag一个都不明白,而且文档不是非常齐全,Tag的Source中注释也比较少,学习起来不是很方便。我们 一共3个人投入到这个项目的前期准备中,在第一个星期的学习中大家互相分配好个人学习的模块,随时互相交流。在后来的深入中发现这个项目的业务逻辑操作会 使用PL/SQL以及存储过程来进行,对于我来说,PL/SQL是从来没有做过的东西,就叫做一窍不通,于是我需要从头开始学习PL/SQL,以及如何编 写存储过程。我从网上下了一个PL/SQL的电子书籍,然后在公司花了一天时间进行学习,个人用的是Toad来调试PL/SQL的,虽然别人喜欢用 PL/SQL Developer来进行开发,不过我还是比较钟爱Toad,而且Toad的确功能也很强大,使用起来也很方便就是了。经过第一天的PL/SQL的学习, 基本掌握了全部语法以及存储过程的书写格式等等,开始能够写写非常简单的PL/SQL。接下来的两三天不断巩固熟练,客户那里也发过来几本详细设计让我们 练习着做一下。有了实际的详细设计,再加上我们之间互相交流,我们提高的都很快,大概过了三四天,大家就把基本详细设计代码编写完毕了,而且经过实际锻 炼,我的PL/SQL编写存储过程的水平也大大提升,已经可以满足开发中的需要了。

这个项目因为如果我们一开始做的能让客户满意的话,后续 的项目将会比较庞大,所以Leader决定把我们Group比较空闲的其他人也先培训一下,让他们有点感觉,到以后正式开发的时候也能迅速进入状态,负责 给他们培训的任务也就交给了我。说起来是培训,其实也就是把大概流程以及方法通过一次会议的形式告诉他们,然后把我前面已经作好的那个画面作为他们的作 业,要他们看着设计书自己把画面制作出来。这个时候也要放劳动节了,黄金周可以休息一个星期,想想就觉得很Happy。劳动节的时候基本没有怎么学习,只 是先把XML-RPC仔细看了下,学会了如何去写一个XML-RPC的应用,接着稍微看了点SOAP,看得也不错,只是些简单的SOAP的例子而已,那些 SOAP的复杂东西都没有看。

    很快就五一黄金周七天放假放完,八号开始上班,上班后就开始正式做节前就定好的那个项目,这次性质属于试做,也就是人家先发一批设计书过来,我们然后开始 Coding,大概做了一周后,我自己害了急性结膜炎,只能回家休息,这次可真的是只能休息了,眼睛觉得特别涨,不要说电脑了,连书都不能看,看了眼睛就 疼。所以在家就只能睡大觉,过了一周眼睛大概才复原,可以去公司上班了。回到公司以后,Leader通知我说我不用去做上次那个项目了,要我加入我们 Group的一个新的项目,这个项目比较大,当时还处于东京刚刚做好基本设计,我们从东京把任务接下来,准备发回来做详细设计。我进去的时候项目才开始三 四天,基本上还没有做什么,这次我进入了详细设计制作小组,开始进行这个项目的详细设计的制作。

    由于我属于第一次在正式的项目中参与详细设计,所以很多东西都不明白,特别是业务上面的东西,许多日语中的业务术语我根本不明白,比如什么卖切,切替,仕 入什么的。看着基本设计书,感觉跟以前看详细设计书有很大的不同。具体的东西写的少了,业务流程逻辑框架什么的比较多,所以需要首先把业务内容都熟悉了, 才可能写出详细设计来。这次的详细设计我也不是孤军奋战,而是有一个进公司4年的老员工带着我一起做,我的任务很轻,不过重点是学会如何去写详细设计,也 许下次再有一个比较大的项目,就没有别人再带着我,而是我自己一个人去完成详细设计了。大概详细设计写了20天左右,我被通知当天把手上的一份详细设计写 完,第二天进入方式设计小组进行方式的设计。

进入方式小组以后,接到的任务就是好几个编写DB操作方面的代码自动化生成工具。由于这次DB 方面并没有非常强制性的那种规约,所以SQL语句的编写可以说比较随意,这就给我工具的编写带来了很大的难度和挑战。这次负责管理方式小组的人仍然是进公 司以后经常带着我的那位技术专家,所以也真算很巧呢。写工具其实很对自身代码编写的提高也很有好处,因为首先客户那里资料会不断修改,这些工具你为了以后 客户更新资料后你能顺利更新工具,你需要设计一个优良的Framework,不一定需要多么复杂的Framework,不过一定要尽量把程序各方面的耦合 度尽量降低,这样才有利于自己对工具进行扩展。紧接着很快,项目代码编写开始了,我的任务算中等偏上,有2个画面和一个批处理需要编写,复杂度还算比较繁 一点。这次项目需要编写JUnit程序,每天都要进行回归测试,保证代码Method的正确性。JUnit虽然自己会用,但是从来没有在真正的项目中使 用,所以在真正用的时候感觉有点手足无措。以前做JUnit从来都是觉得给个参数,检测一个返回值就好了,其实不是那么回事,业务逻辑复杂了,自己需要做 大量的Stub来模拟真实的Class的返回值。设计一个好的Stub是比较困难的,特别在数据库内容比较丰富的时候,一张数据库Table就有上百个 域,工作量可见一斑了。项目要到05年9月中旬才会结束,所以现在还在紧张的开发阶段。我写了JUnit的感觉就是难点不在如何去写JUnit程序,而是 如何去设计测试用例。对于我们这样不是以测试出身的程序员来说,设计测试用例是很痛苦而且很艰难的事情,估计有过相似经验的人肯定会表示赞同。

    当然我一边在紧张的做项目,对于书本的学习也没有闲着。这段时间抓紧把侯捷的Word排版艺术扫了一遍,看完觉得收获颇丰。虽然我以前觉得我在Word上 用得挺不错,日常的一些操作什么的我都会,不过看这本书的中间我发现我还是有很多地方不会的,也学到了不少东西,在以后的Word排版中会很受到好处。由 于项目用到了Spring知识,所以我也看了网络上那个流传广泛的Spring开发指南的PDF看了一遍,感觉长了见识,对IOC以及DI有了进一步的了 解,也理解了为什么需要采用IOC以及DI。不过这个也没有深入下去仔细看,以后等项目稍微空闲一点的时候一定再把Hibernate和Spring好好 看一下,学习人家的设计理念,提高自己能力。对了,也许最重要的是我最近在看一本书,就是《J2EE核心模式》的第二版,我当时原来准备看电子版的这本 《Core J2EE Patterns》的,不过突然在书店发现这本书的中文版出来了,而且译者有熊节的名字,也就是跟侯捷一起翻译《重构——改善既有代码的设计》的那个译 者,我比较相信他翻译的水平,于是买回来看,虽然项目非常紧张,我一个月算上周末需要加班在100个小时左右的样子,但是我相信时间是海绵里的水,只要去 挤,肯定会有的。所以我到现在大概看了2周的样子,已经看了300多页,而且感觉自己的设计视野也开阔了许多,这本书的确很好,把J2EE中常用的一些模 块原理都说了出来,说明了为什么这么做好,这么做如何减少了耦合性,提高了可维护性等等,总之,有1年以上J2EE开发经验而且觉得自己对J2EE有了比 较好的了解的开发人员我强烈推荐看这本书。看了这本书以后我都在回想以前设计的一些框架,一些模块,觉得自己有很多地方当时设计的时候觉得很精巧,不过却 属于弄巧成拙,加大了模块的耦合性,所以在修改的时候比较难于下手。

posted @ 2005-12-26 21:43 Dion 阅读(2583) | 评论 (8)编辑 收藏

     摘要: 华为软件编程规范和范例 document.title="华为软件编程规范和范例 - "+document.title 目  录1 排版62 注释113 标识符命名184 可读性205 变量、结构226 函数、过程287 可测性368 程序效率409 质量保证4410 代码编辑、编译、审查5011 代码测试、维护5212 宏531 排版¹1-1:程序块要采...  阅读全文
posted @ 2005-12-20 08:59 Dion 阅读(2974) | 评论 (3)编辑 收藏

     摘要: AOP@Work: 用 AspectJ 进行性能监视,第 2 部分通过装载时织入使用Glassbox Inspector 文档选项 将此页作为电子邮件发送'); //--> 将此页作为电子邮件发送未显示需要 JavaScript 的文档选项样例代码对此页的评价帮助我们改进这些内容级别: 高级Ron Bodkin , 创始人, New Aspects of Software2005 年 ...  阅读全文
posted @ 2005-12-19 21:53 Dion 阅读(1255) | 评论 (0)编辑 收藏

     摘要: AOP@Work: 用 AspectJ 进行性能监视,第 1 部分用 AspectJ 和 JMX 深入观察 Glassbox Inspector文档选项 将此页作为电子邮件发送'); //--> 将此页作为电子邮件发送未显示需要 JavaScript 的文档选项样例代码对此页的评价帮助我们改进这些内容级别: 中级Ron Bodkin , 创始人, New Aspects of Softwa...  阅读全文
posted @ 2005-12-19 21:52 Dion 阅读(1098) | 评论 (0)编辑 收藏

1.如何获得当前文件路径

常用:

字符串类型:System.getProperty("user.dir");

综合:

package com.zcjl.test.base;
import java.io.File;
public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println(
            Thread.currentThread().getContextClassLoader().getResource(""));
        System.out.println(Test.class.getClassLoader().getResource(""));
        System.out.println(ClassLoader.getSystemResource(""));
        System.out.println(Test.class.getResource(""));
        System.out.println(Test.class.getResource("/"));
        System.out.println(new File("").getAbsolutePath());
        System.out.println(System.getProperty("user.dir"));

    }
}

2.Web服务中

(1).Weblogic

WebApplication的系统文件根目录是你的weblogic安装所在根目录。
例如:如果你的weblogic安装在c:\bea\weblogic700.....
那么,你的文件根路径就是c:\.
所以,有两种方式能够让你访问你的服务器端的文件:
a.使用绝对路径:
比如将你的参数文件放在c:\yourconfig\yourconf.properties,
直接使用 new FileInputStream("yourconfig/yourconf.properties");
b.使用相对路径:
相对路径的根目录就是你的webapplication的根路径,即WEB-INF的上一级目录,将你的参数文件放在yourwebapp\yourconfig\yourconf.properties,
这样使用:
new FileInputStream("./yourconfig/yourconf.properties");
这两种方式均可,自己选择。

(2).Tomcat

在类中输出System.getProperty("user.dir");显示的是%Tomcat_Home%/bin

(3).Resin

不是你的JSP放的相对路径,是JSP引擎执行这个JSP编译成SERVLET
的路径为根.比如用新建文件法测试File f = new File("a.htm");
这个a.htm在resin的安装目录下

(4).如何读相对路径哪?

在Java文件中getResource或getResourceAsStream均可

例:getClass().getResourceAsStream(filePath);//filePath可以是"/filename",这里的/代表web发布根路径下WEB-INF/classes

(5).获得文件真实路径

string  file_real_path=request.getRealPath("mypath/filename"); 

通常使用request.getRealPath("/"); 

3.文件操作的类

import java.io.*;
import java.net.*;
import java.util.*;
//import javax.swing.filechooser.*;
//import org.jr.swing.filter.*;

/**
* 此类中封装一些常用的文件操作。
* 所有方法都是静态方法,不需要生成此类的实例,
* 为避免生成此类的实例,构造方法被申明为private类型的。
* @since  0.1
*/

public class FileUtil {
  /**
   * 私有构造方法,防止类的实例化,因为工具类不需要实例化。
   */
  private FileUtil() {

  }

  /**
   * 修改文件的最后访问时间。
   * 如果文件不存在则创建该文件。
   * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

虑中。</b>
   * @param file 需要修改最后访问时间的文件。
   * @since  0.1
   */
  public static void touch(File file) {
    long currentTime = System.currentTimeMillis();
    if (!file.exists()) {
      System.err.println("file not found:" + file.getName());
      System.err.println("Create a new file:" + file.getName());
      try {
        if (file.createNewFile()) {
        //  System.out.println("Succeeded!");
        }
        else {
        //  System.err.println("Create file failed!");
        }
      }
      catch (IOException e) {
      //  System.err.println("Create file failed!");
        e.printStackTrace();
      }
    }
    boolean result = file.setLastModified(currentTime);
    if (!result) {
    //  System.err.println("touch failed: " + file.getName());
    }
  }

  /**
   * 修改文件的最后访问时间。
   * 如果文件不存在则创建该文件。
   * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

虑中。</b>
   * @param fileName 需要修改最后访问时间的文件的文件名。
   * @since  0.1
   */
  public static void touch(String fileName) {
    File file = new File(fileName);
    touch(file);
  }

  /**
   * 修改文件的最后访问时间。
   * 如果文件不存在则创建该文件。
   * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

虑中。</b>
   * @param files 需要修改最后访问时间的文件数组。
   * @since  0.1
   */
  public static void touch(File[] files) {
    for (int i = 0; i < files.length; i++) {
      touch(files);
    }
  }

  /**
   * 修改文件的最后访问时间。
   * 如果文件不存在则创建该文件。
   * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

虑中。</b>
   * @param fileNames 需要修改最后访问时间的文件名数组。
   * @since  0.1
   */
  public static void touch(String[] fileNames) {
    File[] files = new File[fileNames.length];
    for (int i = 0; i < fileNames.length; i++) {
      files = new File(fileNames);
    }
    touch(files);
  }

  /**
   * 判断指定的文件是否存在。
   * @param fileName 要判断的文件的文件名
   * @return 存在时返回true,否则返回false。
   * @since  0.1
   */
  public static boolean isFileExist(String fileName) {
    return new File(fileName).isFile();
  }

  /**
   * 创建指定的目录。
   * 如果指定的目录的父目录不存在则创建其目录书上所有需要的父目录。
   * <b>注意:可能会在返回false的时候创建部分父目录。</b>
   * @param file 要创建的目录
   * @return 完全创建成功时返回true,否则返回false。
   * @since  0.1
   */
  public static boolean makeDirectory(File file) {
    File parent = file.getParentFile();
    if (parent != null) {
      return parent.mkdirs();
    }
    return false;
  }

  /**
   * 创建指定的目录。
   * 如果指定的目录的父目录不存在则创建其目录书上所有需要的父目录。
   * <b>注意:可能会在返回false的时候创建部分父目录。</b>
   * @param fileName 要创建的目录的目录名
   * @return 完全创建成功时返回true,否则返回false。
   * @since  0.1
   */
  public static boolean makeDirectory(String fileName) {
    File file = new File(fileName);
    return makeDirectory(file);
  }

  /**
   * 清空指定目录中的文件。
   * 这个方法将尽可能删除所有的文件,但是只要有一个文件没有被删除都会返回false。
   * 另外这个方法不会迭代删除,即不会删除子目录及其内容。
   * @param directory 要清空的目录
   * @return 目录下的所有文件都被成功删除时返回true,否则返回false.
   * @since  0.1
   */
  public static boolean emptyDirectory(File directory) {
    boolean result = false;
    File[] entries = directory.listFiles();
    for (int i = 0; i < entries.length; i++) {
      if (!entries.delete()) {
        result = false;
      }
    }
    return true;
  }

  /**
   * 清空指定目录中的文件。
   * 这个方法将尽可能删除所有的文件,但是只要有一个文件没有被删除都会返回false。
   * 另外这个方法不会迭代删除,即不会删除子目录及其内容。
   * @param directoryName 要清空的目录的目录名
   * @return 目录下的所有文件都被成功删除时返回true,否则返回false。
   * @since  0.1
   */
  public static boolean emptyDirectory(String directoryName) {
    File dir = new File(directoryName);
    return emptyDirectory(dir);
  }

  /**
   * 删除指定目录及其中的所有内容。
   * @param dirName 要删除的目录的目录名
   * @return 删除成功时返回true,否则返回false。
   * @since  0.1
   */
  public static boolean deleteDirectory(String dirName) {
    return deleteDirectory(new File(dirName));
  }

  /**
   * 删除指定目录及其中的所有内容。
   * @param dir 要删除的目录
   * @return 删除成功时返回true,否则返回false。
   * @since  0.1
   */
  public static boolean deleteDirectory(File dir) {
    if ( (dir == null) || !dir.isDirectory()) {
      throw new IllegalArgumentException("Argument " + dir +
                                         " is not a directory. ");
    }

    File[] entries = dir.listFiles();
    int sz = entries.length;

    for (int i = 0; i < sz; i++) {
      if (entries.isDirectory()) {
        if (!deleteDirectory(entries)) {
          return false;
        }
      }
      else {
        if (!entries.delete()) {
          return false;
        }
      }
    }

    if (!dir.delete()) {
      return false;
    }
    return true;
  }


  /**
   * 返回文件的URL地址。
   * @param file 文件
   * @return 文件对应的的URL地址
   * @throws MalformedURLException
   * @since  0.4
   * @deprecated 在实现的时候没有注意到File类本身带一个toURL方法将文件路径转换为URL。
   *             请使用File.toURL方法。
   */
  public static URL getURL(File file) throws MalformedURLException {
    String fileURL = "file:/" + file.getAbsolutePath();
    URL url = new URL(fileURL);
    return url;
  }

  /**
   * 从文件路径得到文件名。
   * @param filePath 文件的路径,可以是相对路径也可以是绝对路径
   * @return 对应的文件名
   * @since  0.4
   */
  public static String getFileName(String filePath) {
    File file = new File(filePath);
    return file.getName();
  }

  /**
   * 从文件名得到文件绝对路径。
   * @param fileName 文件名
   * @return 对应的文件路径
   * @since  0.4
   */
  public static String getFilePath(String fileName) {
    File file = new File(fileName);
    return file.getAbsolutePath();
  }

  /**
   * 将DOS/Windows格式的路径转换为UNIX/Linux格式的路径。
   * 其实就是将路径中的"\"全部换为"/",因为在某些情况下我们转换为这种方式比较方便,
   * 某中程度上说"/"比"\"更适合作为路径分隔符,而且DOS/Windows也将它当作路径分隔符。
   * @param filePath 转换前的路径
   * @return 转换后的路径
   * @since  0.4
   */
  public static String toUNIXpath(String filePath) {
    return filePath.replace('\\', '/');
  }

  /**
   * 从文件名得到UNIX风格的文件绝对路径。
   * @param fileName 文件名
   * @return 对应的UNIX风格的文件路径
   * @since  0.4
   * @see #toUNIXpath(String filePath) toUNIXpath
   */
  public static String getUNIXfilePath(String fileName) {
    File file = new File(fileName);
    return toUNIXpath(file.getAbsolutePath());
  }

  /**
   * 得到文件的类型。
   * 实际上就是得到文件名中最后一个“.”后面的部分。
   * @param fileName 文件名
   * @return 文件名中的类型部分
   * @since  0.5
   */
  public static String getTypePart(String fileName) {
    int point = fileName.lastIndexOf('.');
    int length = fileName.length();
    if (point == -1 || point == length - 1) {
      return "";
    }
    else {
      return fileName.substring(point + 1, length);
    }
  }

  /**
   * 得到文件的类型。
   * 实际上就是得到文件名中最后一个“.”后面的部分。
   * @param file 文件
   * @return 文件名中的类型部分
   * @since  0.5
   */
  public static String getFileType(File file) {
    return getTypePart(file.getName());
  }

  /**
   * 得到文件的名字部分。
   * 实际上就是路径中的最后一个路径分隔符后的部分。
   * @param fileName 文件名
   * @return 文件名中的名字部分
   * @since  0.5
   */
  public static String getNamePart(String fileName) {
    int point = getPathLsatIndex(fileName);
    int length = fileName.length();
    if (point == -1) {
      return fileName;
    }
    else if (point == length - 1) {
      int secondPoint = getPathLsatIndex(fileName, point - 1);
      if (secondPoint == -1) {
        if (length == 1) {
          return fileName;
        }
        else {
          return fileName.substring(0, point);
        }
      }
      else {
        return fileName.substring(secondPoint + 1, point);
      }
    }
    else {
      return fileName.substring(point + 1);
    }
  }

  /**
   * 得到文件名中的父路径部分。
   * 对两种路径分隔符都有效。
   * 不存在时返回""。
   * 如果文件名是以路径分隔符结尾的则不考虑该分隔符,例如"/path/"返回""。
   * @param fileName 文件名
   * @return 父路径,不存在或者已经是父目录时返回""
   * @since  0.5
   */
  public static String getPathPart(String fileName) {
    int point = getPathLsatIndex(fileName);
    int length = fileName.length();
    if (point == -1) {
      return "";
    }
    else if (point == length - 1) {
      int secondPoint = getPathLsatIndex(fileName, point - 1);
      if (secondPoint == -1) {
        return "";
      }
      else {
        return fileName.substring(0, secondPoint);
      }
    }
    else {
      return fileName.substring(0, point);
    }
  }

  /**
   * 得到路径分隔符在文件路径中首次出现的位置。
   * 对于DOS或者UNIX风格的分隔符都可以。
   * @param fileName 文件路径
   * @return 路径分隔符在路径中首次出现的位置,没有出现时返回-1。
   * @since  0.5
   */
  public static int getPathIndex(String fileName) {
    int point = fileName.indexOf('/');
    if (point == -1) {
      point = fileName.indexOf('\\');
    }
    return point;
  }

  /**
   * 得到路径分隔符在文件路径中指定位置后首次出现的位置。
   * 对于DOS或者UNIX风格的分隔符都可以。
   * @param fileName 文件路径
   * @param fromIndex 开始查找的位置
   * @return 路径分隔符在路径中指定位置后首次出现的位置,没有出现时返回-1。
   * @since  0.5
   */
  public static int getPathIndex(String fileName, int fromIndex) {
    int point = fileName.indexOf('/', fromIndex);
    if (point == -1) {
      point = fileName.indexOf('\\', fromIndex);
    }
    return point;
  }

  /**
   * 得到路径分隔符在文件路径中最后出现的位置。
   * 对于DOS或者UNIX风格的分隔符都可以。
   * @param fileName 文件路径
   * @return 路径分隔符在路径中最后出现的位置,没有出现时返回-1。
   * @since  0.5
   */
  public static int getPathLsatIndex(String fileName) {
    int point = fileName.lastIndexOf('/');
    if (point == -1) {
      point = fileName.lastIndexOf('\\');
    }
    return point;
  }

  /**
   * 得到路径分隔符在文件路径中指定位置前最后出现的位置。
   * 对于DOS或者UNIX风格的分隔符都可以。
   * @param fileName 文件路径
   * @param fromIndex 开始查找的位置
   * @return 路径分隔符在路径中指定位置前最后出现的位置,没有出现时返回-1。
   * @since  0.5
   */
  public static int getPathLsatIndex(String fileName, int fromIndex) {
    int point = fileName.lastIndexOf('/', fromIndex);
    if (point == -1) {
      point = fileName.lastIndexOf('\\', fromIndex);
    }
    return point;
  }

  /**
   * 将文件名中的类型部分去掉。
   * @param filename 文件名
   * @return 去掉类型部分的结果
   * @since  0.5
   */
  public static String trimType(String filename) {
    int index = filename.lastIndexOf(".");
    if (index != -1) {
      return filename.substring(0, index);
    }
    else {
      return filename;
    }
  }
  /**
   * 得到相对路径。
   * 文件名不是目录名的子节点时返回文件名。
   * @param pathName 目录名
   * @param fileName 文件名
   * @return 得到文件名相对于目录名的相对路径,目录下不存在该文件时返回文件名
   * @since  0.5
   */
  public static String getSubpath(String pathName,String fileName) {
    int index = fileName.indexOf(pathName);
    if (index != -1) {
      return fileName.substring(index + pathName.length() + 1);
    }
    else {
      return fileName;
    }
  }

}
 4.遗留问题

目前new FileInputStream()只会使用绝对路径,相对没用过,因为要相对于web服务器地址,比较麻烦

还不如写个配置文件来的快哪

5.按Java文件类型分类读取配置文件

配 置文件是应用系统中不可缺少的,可以增加程序的灵活性。java.util.Properties是从jdk1.2就有的类,一直到现在都支持load ()方法,jdk1.4以后save(output,string) ->store(output,string)。如果只是单纯的读,根本不存在烦恼的问题。web层可以通过 Thread.currentThread().getContextClassLoader().
getResourceAsStream("xx.properties") 获取;Application可以通过new FileInputStream("xx.properties");直接在classes一级获取。关键是有时我们需要通过web修改配置文件,我们不 能将路径写死了。经过测试觉得有以下心得:

1.servlet中读写。如果运用Struts 或者Servlet可以直接在初始化参数中配置,调用时根据servlet的getRealPath("/")获取真实路径,再根据String file = this.servlet.getInitParameter("abc");获取相对的WEB-INF的相对路径。
例:
InputStream input = Thread.currentThread().getContextClassLoader().
getResourceAsStream("abc.properties");
Properties prop = new Properties();
prop.load(input);
input.close();
OutputStream out = new FileOutputStream(path);
prop.setProperty("abc", “test");
prop.store(out, “–test–");
out.close();

2.直接在jsp中操作,通过jsp内置对象获取可操作的绝对地址。
例:
// jsp页面
String path = pageContext.getServletContext().getRealPath("/");
String realPath = path+"/WEB-INF/classes/abc.properties";

//java 程序
InputStream in = getClass().getClassLoader().getResourceAsStream("abc.properties"); // abc.properties放在webroot/WEB-INF/classes/目录下
prop.load(in);
in.close();

OutputStream out = new FileOutputStream(path); // path为通过页面传入的路径
prop.setProperty("abc", “abcccccc");
prop.store(out, “–test–");
out.close();

3.只通过Java程序操作资源文件
InputStream in = new FileInputStream("abc.properties"); // 放在classes同级

OutputStream out = new FileOutputStream("abc.properties");

posted @ 2005-12-16 22:53 Dion 阅读(31483) | 评论 (8)编辑 收藏

六种异常处理的陋习

你觉得自己是一个Java专家吗?是否肯定自己已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗?

1 OutputStreamWriter out = ...
2 java.sql.Connection conn = ...
3 try { // ⑸
4  Statement stat = conn.createStatement();
5  ResultSet rs = stat.executeQuery(
6   "select uid, name from user");
7  while (rs.next())
8  {
9   out.println("ID:" + rs.getString("uid") // ⑹
10    ",姓名:" + rs.getString("name"));
11  }
12  conn.close(); // ⑶
13  out.close();
14 }
15 catch(Exception ex) // ⑵
16 {
17  ex.printStackTrace(); //⑴,⑷
18 }


  作为一个Java程序员,你至少应该能够找出两个问题。但是,如果你不能找出全部六个问题,请继续阅读本文。

  本文讨论的不是Java异常处理的一般性原则,因为这些原则已经被大多数人熟知。我们要做的是分析各种可称为“反例”(anti-pattern)的违背优秀编码规范的常见坏习惯,帮助读者熟悉这些典型的反面例子,从而能够在实际工作中敏锐地察觉和避免这些问题。

  反例之一:丢弃异常

  代码:15行-18行。

   这段代码捕获了异常却不作任何处理,可以算得上Java编程中的杀手。从问题出现的频繁程度和祸害程度来看,它也许可以和C/C++程序的一个恶名远播 的问题相提并论??不检查缓冲区是否已满。如果你看到了这种丢弃(而不是抛出)异常的情况,可以百分之九十九地肯定代码存在问题(在极少数情况下,这段代 码有存在的理由,但最好加上完整的注释,以免引起别人误解)。

  这段代码的错误在于,异常(几乎)总是意味着某些事情不对劲了,或 者说至少发生了某些不寻常的事情,我们不应该对程序发出的求救信号保持沉默和无动于衷。调用一下printStackTrace算不上“处理异常”。不 错,调用printStackTrace对调试程序有帮助,但程序调试阶段结束之后,printStackTrace就不应再在异常处理模块中担负主要责 任了。

  丢弃异常的情形非常普遍。打开JDK的ThreadDeath类的文档,可以看到下面这段说明:“特别地,虽然出现 ThreadDeath是一种‘正常的情形’,但ThreadDeath类是Error而不是Exception的子类,因为许多应用会捕获所有的 Exception然后丢弃它不再理睬。”这段话的意思是,虽然ThreadDeath代表的是一种普通的问题,但鉴于许多应用会试图捕获所有异常然后不 予以适当的处理,所以JDK把ThreadDeath定义成了Error的子类,因为Error类代表的是一般的应用不应该去捕获的严重问题。可见,丢弃 异常这一坏习惯是如此常见,它甚至已经影响到了Java本身的设计。

  那么,应该怎样改正呢?主要有四个选择:

  1、处理异常。针对该异常采取一些行动,例如修正问题、提醒某个人或进行其他一些处理,要根据具体的情形确定应该采取的动作。再次说明,调用printStackTrace算不上已经“处理好了异常”。

  2、重新抛出异常。处理异常的代码在分析异常之后,认为自己不能处理它,重新抛出异常也不失为一种选择。

  3、把该异常转换成另一种异常。大多数情况下,这是指把一个低级的异常转换成应用级的异常(其含义更容易被用户了解的异常)。

  4、不要捕获异常。

  结论一:既然捕获了异常,就要对它进行适当的处理。不要捕获异常之后又把它丢弃,不予理睬。

  反例之二:不指定具体的异常

  代码:15行。

  许多时候人们会被这样一种“美妙的”想法吸引:用一个catch语句捕获所有的异常。最常见的情形就是使用catch(Exception ex)语句。但实际上,在绝大多数情况下,这种做法不值得提倡。为什么呢?

   要理解其原因,我们必须回顾一下catch语句的用途。catch语句表示我们预期会出现某种异常,而且希望能够处理该异常。异常类的作用就是告诉 Java编译器我们想要处理的是哪一种异常。由于绝大多数异常都直接或间接从java.lang.Exception派生,catch (Exception ex)就相当于说我们想要处理几乎所有的异常。

  再来看看前面的代码例子。我们真正想要捕获的异常是什么 呢?最明显的一个是SQLException,这是JDBC操作中常见的异常。另一个可能的异常是IOException,因为它要操作 OutputStreamWriter。显然,在同一个catch块中处理这两种截然不同的异常是不合适的。如果用两个catch块分别捕获 SQLException和IOException就要好多了。这就是说,catch语句应当尽量指定具体的异常类型,而不应该指定涵盖范围太广的 Exception类。

  另一方面,除了这两个特定的异常,还有其他许多异常也可能出现。例如,如果由于某种原因, executeQuery返回了null,该怎么办?答案是让它们继续抛出,即不必捕获也不必处理。实际上,我们不能也不应该去捕获可能出现的所有异常, 程序的其他地方还有捕获异常的机会??直至最后由JVM处理。

  结论二:在catch语句中尽可能指定具体的异常类型,必要时使用多个catch。不要试图处理所有可能出现的异常。

  反例之三:占用资源不释放

  代码:3行-14行。

  异常改变了程序正常的执行流程。这个道理虽然简单,却常常被人们忽视。如果程序用到了文件、Socket、JDBC连接之类的资源,即使遇到了异常,也要正确释放占用的资源。为此,Java提供了一个简化这类操作的关键词finally。

  finally是样好东西:不管是否出现了异常,Finally保证在try/catch/finally块结束之前,执行清理任务的代码总是有机会执行。遗憾的是有些人却不习惯使用finally。

  当然,编写finally块应当多加小心,特别是要注意在finally块之内抛出的异常??这是执行清理任务的最后机会,尽量不要再有难以处理的错误。

  结论三:保证所有资源都被正确释放。充分运用finally关键词。

反例之四:不说明异常的详细信息

  代码:3行-18行。

  仔细观察这段代码:如果循环内部出现了异常,会发生什么事情?我们可以得到足够的信息判断循环内部出错的原因吗?不能。我们只能知道当前正在处理的类发生了某种错误,但却不能获得任何信息判断导致当前错误的原因。

  printStackTrace的堆栈跟踪功能显示出程序运行到当前类的执行流程,但只提供了一些最基本的信息,未能说明实际导致错误的原因,同时也不易解读。

  因此,在出现异常时,最好能够提供一些文字信息,例如当前正在执行的类、方法和其他状态信息,包括以一种更适合阅读的方式整理和组织printStackTrace提供的信息。

  结论四:在异常处理模块中提供适量的错误原因信息,组织错误信息使其易于理解和阅读。

  反例之五:过于庞大的try块

  代码:3行-14行。

   经常可以看到有人把大量的代码放入单个try块,实际上这不是好习惯。这种现象之所以常见,原因就在于有些人图省事,不愿花时间分析一大块代码中哪几行 代码会抛出异常、异常的具体类型是什么。把大量的语句装入单个巨大的try块就象是出门旅游时把所有日常用品塞入一个大箱子,虽然东西是带上了,但要找出 来可不容易。

  一些新手常常把大量的代码放入单个try块,然后再在catch语句中声明Exception,而不是分离各个可能出现异常的段落并分别捕获其异常。这种做法为分析程序抛出异常的原因带来了困难,因为一大段代码中有太多的地方可能抛出Exception。

  结论五:尽量减小try块的体积。

  反例之六:输出数据不完整

  代码:7行-11行。

   不完整的数据是Java程序的隐形杀手。仔细观察这段代码,考虑一下如果循环的中间抛出了异常,会发生什么事情。循环的执行当然是要被打断的,其次, catch块会执行??就这些,再也没有其他动作了。已经输出的数据怎么办?使用这些数据的人或设备将收到一份不完整的(因而也是错误的)数据,却得不到 任何有关这份数据是否完整的提示。对于有些系统来说,数据不完整可能比系统停止运行带来更大的损失。

  较为理想的处置办法是向输出设备写一些信息,声明数据的不完整性;另一种可能有效的办法是,先缓冲要输出的数据,准备好全部数据之后再一次性输出。

  结论六:全面考虑可能出现的异常以及这些异常对执行流程的影响。

  改写后的代码

  根据上面的讨论,下面给出改写后的代码。也许有人会说它稍微有点?嗦,但是它有了比较完备的异常处理机制。

OutputStreamWriter out = ...
java.sql.Connection conn = ...
try {
 Statement stat = conn.createStatement();
 ResultSet rs = stat.executeQuery(
  "select uid, name from user");
 while (rs.next())
 {
  out.println("ID:" + rs.getString("uid") + ",姓名: " + rs.getString("name"));
 }
}
catch(SQLException sqlex)
{
 out.println("警告:数据不完整");
 throw new ApplicationException("读取数据时出现SQL错误", sqlex);
}
catch(IOException ioex)
{
 throw new ApplicationException("写入数据时出现IO错误", ioex);
}
finally
{
 if (conn != null) {
  try {
   conn.close();
  }
  catch(SQLException sqlex2)
  {
   System.err(this.getClass().getName() + ".mymethod - 不能关闭数据库连接: " + sqlex2.toString());
  }
 }

 if (out != null) {
  try {
   out.close();
  }
  catch(IOException ioex2)
  {
   System.err(this.getClass().getName() + ".mymethod - 不能关闭输出文件" + ioex2.toString());
  }
 }
}

  本文的结论不是放之四海皆准的教条,有时常识和经验才是最好的老师。如果你对自己的做法没有百分之百的信心,务必加上详细、全面的注释。

   另一方面,不要笑话这些错误,不妨问问你自己是否真地彻底摆脱了这些坏习惯。即使最有经验的程序员偶尔也会误入歧途,原因很简单,因为它们确确实实带来 了“方便”。所有这些反例都可以看作Java编程世界的恶魔,它们美丽动人,无孔不入,时刻诱惑着你。也许有人会认为这些都属于鸡皮蒜毛的小事,不足挂 齿,但请记住:勿以恶小而为之,勿以善小而不为。



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=553341

posted @ 2005-12-16 22:52 Dion 阅读(664) | 评论 (1)编辑 收藏

在SPRING中实现事务暂停

作者:Juergen Hoeller

译者:xMatrix





版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Juergen Hoeller;xMatrix
原文地址:http://dev2dev.bea.com/pub/a/2005/07/spring_transactions.html
中文地址:http://www.matrix.org.cn/resource/article/44/44054_Transaction+Spring.html
关键词: Transaction Suspension Spring

摘要

Spring 框架是一个流行的基于轻量级控制反转容器的Java/J2EE应用框架,尤其在数据访问和事务管理方面的能力是众所周知的。Spring的声明性事务分离 可以应用到任何POJO目标对象,并且包含所有EJB基于容器管理事务中的已声明事务。后台的事务管理器支持简单的基于JDBC的事务和全功能的基于 JTA的J2EE事务。

这篇文章详细的讨论了Spring的事务管理特性。重点是如何在使用JTA作为后台事务策略的基础上让POJO利 用Spring的声明性事务,这也显示了Spring的事务服务可以无缝地与J2EE服务器(如BEA WebLogic Server的事务协调器)的事务协调器进行交互,作为EJB CMT传统事务分离方式的一个替代者。

POJO的声明性事务

作为Spring声明性事务分离方式的样例,让我们来看一下Spring的样例应用PetClinic的中心服务外观中的配置:
清单1:
<bean id="dataSource" 
   class="org.springframework.jndi.JndiObjectFactoryBean">
     <property name="jndiName">
        <value>java:comp/env/jdbc/petclinic</value>
     </property>
</bean>

<bean id="transactionManager"
   class="org.springframework.transaction.jta.JtaTransactionManager"/>

<bean id="clinicTarget"
   class="org.springframework.samples.petclinic.jdbc.JdbcClinic">
    <property name="dataSource"><ref bean="dataSource"/></property>
</bean>

<bean id="clinic"
   class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="target"><ref bean="clinicTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>


他遵循Spring的标准XMLBean定义格式。定义了:
1、一个DataSource引用,指向一个JNDI位置—在J2EE服务器管理下这将从JNDI环境中获取特定的DataSource。
2、一个应用服务实现—这是一个POJO,封装了业务和数据访问逻辑。在这里实现了应用中的Clinic服务接口。
3、一个应用服务的事务代理—这个代理为目标服务定义了事务属性,匹配特定的方法名模式并为之创建相应的事务。在实际的事务管理中,代理指向一个PlatformTransactionManager实现。
注意:除了显式的代理定义,Spring还支持自动代理机制和通过Commons Attributes或J2SE 5.0注解实现源程序级的元数据使用。这些可选方法的讨论超过了本文的范围。可以参考Spring的文档来了解相关细节。


业务接口和业务实现是特定于应用的并且不需要关心Spring或者Spring的事务管理。普通Java对象可以作为服务的目标对象,而且任何普通Java接口可以作为服务的接口。下面是一个Clinic接口的示例:
清单2:
public interface Clinic {
    Pet loadPet(int id);
    void storePet(Pet pet);
    ...
}



这个接口的实现如下显示,假设他使用JDBC来执行必要的数据访问。他通过bean属性的设置方法来获取JDBC的DataSource;这与上面的配置中的dataSource属性定义相对应。
清单3:
public class JdbcClinic implements Clinic {

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
      this.dataSource = dataSource;
    }

    public Pet loadPet(int id) {
      try {
          Connection con = this.dataSource.getConnection();
          ...
      }
      catch (SQLException ex) {
        ...
      }
    }

    public void storePet(Pet pet) {
      try {
          Connection con = this.dataSource.getConnection();
          ...
      }
      catch (SQLException ex) {
        ...
      }
    }

    ...
}



如你所见,代码相当直接。我们使用一个简单的Java对象,而事务管理由事务代理来处理,这个我们会在下面讨论。
注意在PetClinic示例应用中实际的基于JDBC的Clinic实现利用了Spring的JDBC支持类来避免直接使用JDBC的API。虽然Spring的事务管理也可以与普通的基于JDBC实现一起工作,就向上面的示例。

定义事务代理
除了JdbcClinic实例以外,配置中也定义了一个事务代理。如果愿意这个代理所暴露的实际接口也可以显式定义。默认情况下,所有由目标对象实现的接口都暴露出来,在这个例子中就是应用的Clinic服务接口。

从客户端的观点来看,"clinic" bean只是这个应用的Clinic接口的实现。客户端不需要知道这会被一个事务代理所处理。这就是接口的能力:一个直接的目标对象的引用可以容易的被一个实现相同接口的代理所代替—在这儿就是一个隐式创建事务的代理。
代理的具体事务行为会由为根据特定的方法或方法命名模式而定义的事务属性来驱动,就像下面的例子所示:
清单3:
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>


Key属性决定代理将为方法提供什么样的事务行为。这个属性的最重要部分就是事务传播行为。下面是一些可选的属性值:
1、PROPAGATION_REQUIRED --支持当前的事务,如果不存在就创建一个新的。这是最常用的选择。
2、PROPAGATION_SUPPORTS --支持当前的事务,如果不存在就不使用事务。
3、PROPAGATION_MANDATORY --支持当前的事务,如果不存在就抛出异常。
4、PROPAGATION_REQUIRES_NEW --创建一个新的事务,并暂停当前的事务(如果存在)。
5、PROPAGATION_NOT_SUPPORTED --不使用事务,并暂停当前的事务(如果存在)。
6、PROPAGATION_NEVER --不使用事务,如果当前存在事务就抛出异常。
7、PROPAGATION_NESTED --如果当前存在事务就作为嵌入事务执行,否则与PROPAGATION_REQUIRED类似。

前6 个事务策略与EJB的CMT类似,而且使用相同的常量名,因此对EJB开发人员来说是很亲切的。第7个策略PROPAGATION_NESTED是 Spring提供的一个变体:他需要事务管理器(如DataSourceTransactionManager)提供类似JDBC3.0那样的保存点 API来嵌套事务行为或者通过
JTA支持嵌套事务。

事务属性中的readOnly标识指示相应的事务应该作为一个只读事务来优化。这是一个优化提示:一些事务策略在这种情况下可以得到很好的性能优化,如使用ORM工具如Hibernate或TopLink时避免脏数据检查(“flush”尝试)。

在事务属性中还有一个“timeout”选项来定义事务的超时秒数。在JTA中,这个属性会简单地传递给J2EE服务器的事务协调器并被正确地解释。

使用事务代理
在 运行时,客户端会取得一个“clinic”引用并转换为Clinic接口,然后调用如loadPet或storePet方法。这就隐式地使用了 Spring的事务代理,通过“事务解释器”在目标对象中注册;这样一个新的事务就创建了,然后具体的工作就会代理给JdbcClinic的目标方法。
图1示例了一个使用“建议链”并到达最后目标的AOP代理的潜在概念。在这个示例中,唯一的建议是一个事务解释器用来包装目标方法的事务行为。这是一种用来在声明性事务功能下使用的基于代理的AOP。



Figure 1. An AOP proxy with an advisor chain and a target at the end

例如,一个PetClinic应用的WEB层组件可以执行ServletContext定位来获取Spring WebApplicationContext的引用并且获取受管理的“clinic”BEAN:
清单4:
WebApplicationContext ctx = 
   WebApplicationContexUtils.getWebApplicationContext(servletContext);
Clinic clinic = (Clinic) ctx.getBean("clinic);

Pet pet = new Pet();
pet.setName("my new cat");

clinic.storePet(pet);


在 调用storePet()之前,Spring的事务代理隐式地创建一个事务。当storePet()调用返回时,事务将提交或回滚。缺省情况下任何 RuntimeException或Error将导致回滚。实际的提交或回滚可以是可以定义的:Spring的事务属性支持“回滚规则”的概念。

例如,我们可以可以引入一个强制的PetClinicException并且告诉事务代理在抛出异常时回滚:
清单5:
<prop key="load*">PROPAGATION_REQUIRED,readOnly,-PetClinicException</prop>
<prop key="store*">PROPAGATION_REQUIRED,-PetClinicException</prop>


这儿也有一个类似的“提交规则”语法,指示特定的异常将触发一次提交。
注 意上面示例的显式定位引用的方法只是一种访问受Spring管理BEAN的方法的变化,可以用在任何WEB资源如servlet或filter。在构建基 于Spring自身的MVC框架时,BEAN可以直接被注射到WEB控制器中。当然也支持在如Struts, WebWork, JSF, and Tapestry框架中访问Spring管理BEAN。详情可以参考Spring的文档。

PlatformTransactionManager策略

Spring 事务支持的核心接口是org.springframework.transaction.PlatformTransactionManager。所有 Spring的事务分离功能都会委托给PlatformTransactionManager(传给相应的TransactionDefinition实 例)来做实际的事务执行。虽然PlatformTransactionManager接口可以直接调用,但通常应用只需要配置一个具体的事务管理器并且通 过声明性事务来分离事务。

Spring提供几种不同的PlatformTransactionManager实现,分为如下两个类别:
1、 本地事务策略—支持单一资源的事务(通常是单个数据库),其包括 org.springframework.jdbc.datasource.DataSourceTransactionManager和 org.springframework.orm.hibernate.HibernateTransactionManager。
2、全局事务管理—支持可能跨越多个资源的全局事务。其相应的类为org.springframework.transaction.jta.JtaTransactionManager,将事务委托给遵循JTA规范的事务协调器(通常为J2EE服务器,但不是强制的)。

PlatformTransactionManager 抽象的主要价值在于应用不再被绑定在特定的事务管理环境。相反,事务策略可以很容易地切换—通过选择不同的 PlatformTransactionManager实现类。这就使得应用代码与声明事务分离保持一致,而不需要考虑应用组件所使用的环境了。

例 如,应用的初始版本可能布署在Tomcat上,与单个Oracle数据库交互。这可以方便地利用Spring的事务分离特性,只要选择基于JDBC的 DataSourceTransactionManager作为使用的事务策略。Spring会分离事务,而JDBC驱动会执行相应的原始JDBC事务。

相 同应用的另一个版本可能会布署在WebLogic服务器上,使用两个Oracle数据库。应用代码和事务分离不需要改变。唯一不同的是选择作为 JtaTransactionManager事务策略,让Spring来分离事务而WebLogic服务器的事务协调器来执行事务。

JTA UserTransaction与JTA TransactionManager比较
让我们来看一下Spring对JTA支持的细节。虽然并非经常需要考虑这个细节但了解相关的细节还有必要的。对简单的用例如前面章节的示例,标准的JtaTransactionManager定义已经足够了,
缺 省的Spring JtaTransactionManager设置会从标准JNDI位置(J2EE规范所定义的java:comp/UserTransaction)获取 JTA的javax.transaction.UserTransaction对象。这对大部分标准J2EE环境来说已经足够了。

然而, 缺省的JtaTransactionManager不能执行事务暂停(也就是说不支持PROPAGATION_REQUIRES_NEW和 PROPAGATION_NOT_SUPPORTED)。原因就在于标准的JTA UserTransaction接口不支持事务的暂停和恢复,而只支持开始和完成新的事务。

为了实现事务的暂停,需要一个 javax.transaction.TransactionManager实例,他提供了JTA定义的标准的暂停和恢复方法。不幸的是,J2EE没有为 JTA TransactionManager定义标准的JNDI位置!因此,我们需要使用厂商自己的定位机制。
清单6:
<bean id="transactionManager" 
   class="org.springframework.transaction.jta.JtaTransactionManager">
     <property name="transactionManagerName">
        <value>vendorSpecificJndiLocation</value>
     </property>
</bean>



J2EE 本质上没有考虑将JTA TransactionManager接口作为公共API的一部分。JTA规范自身定义了将TransactionManager接口作为容器集成的想 法。虽然这是可以理解的,但是JTA TransactionManager的标准JNDI位置还是可以增加一定的价值,特别是对轻量级容器如Spring,这样任何J2EE服务器就可以用统 一的方式来定位JTA TransactionManager了。

不仅Spring的JtaTransactionManager可以从 访问中获益,O/R映射工具如Hibernate, Apache OJB, and Kodo JDO也能得到好处,因为他们需要在JTA环境中执行缓存同步的能力(释放缓存意味着JTA事务的完成)。这种注册事务同步的能力只有JTA TransactionManager接口才能提供,而UserTransaction是处理不了的。因此,这些工具都需要实现自己的 TransactionManager定位器。

为JTA TransactionManager定义标准的JNDI位置是许多底层软件供应商最期望J2EE实现的功能。如果J2EE5.0的规范制定团队能够认识 到这个特性的重要性就太好了。幸运地是,高级J2EE服务器如WebLogic Server已经考虑将JTA TransactionManager作为公共的API包含在扩展功能中。

在WebLogic JTA中实现Spring的事务分离
在WebLogic Server中,JTA TransactionManager官方的JNDI位置定义为javax.transaction.TransactionManager。这个值可以 在Spring的JtaTransactionManager中作为“transactionManagerName”使用。原则上这样就可以在 WebLogic's JTA系统中实现事务暂停了,也就是说支持PROPAGATION_REQUIRES_NEW和PROPAGATION_NOT_SUPPORTED行 为。

除了标准的JtaTransactionManager和其支持的通用配置选项外,Spring还提供了一个专用的WebLogicJtaTransactionManager适配器来直接利用WebLogic的JTA扩展。

在享受自动探测WebLogic的JTA TransactionManager的便利之外,他提供超越标准JTA的三个重要特性:
1、事务命名—暴露出Spring的事务名给WebLogic Server,使得Spring事务在WebLogic的事务监听器可见。缺省的,Spring会使用声明性事务的完整方法名。
2、每事务隔离级别—将Spring事务属性中定义的隔离级别应用到WebLogic JTA事务中。这使得每个事务都可以定义数据库的隔离级别,而这是标准JTA所不支持的。
3、强制事务恢复—即使在暂停的事务被标识为回滚时也可以恢复。这需要使用WebLogic的扩展TransactionManager接口来调用forceResume()方法。

image
Figure 2. WebLogic Server's transaction monitor (click the image for a full-size screen shot)

Spring的WebLogicJtaTransactionManager有效地为基于Spring的应用提供了WebLogic Server事务管理的全部功能。这使得Spring事务分离成为一种能与EJB CMT竟争的产品,而且提供了相同级别的事务支持。

Spring and EJB CMT

如 上所示,Spring的POJO声明性事务分离可以作为一种除传统EJB CMT这外的选择。但是Spring与EJB并不是完成互斥的,Spring的应用上下文也可以作为EJB fa&ccedil;ade的后台来管理数据访问(DAO)和其他细纹理的业务对象。

在EJB情景中,事务是由EJB CMT来驱动的。对Spring来说,数据访问支持特性会自动检测到这样的环境并且采用相应的事务。例如,Spring对Hibernate的支持能够提 供隐式的资源管理,即使是EJB驱动的事务,甚至可以在不需要修改任何DAO代码的情况下提供相同的语义。
Spring有效的解耦了DAO实现与实际的运行环境。DAO可以参与Spring的事务就像参与EJB CMT事务一样。这不仅简化在其他环境中的重用,而且更方便在J2EE容器外进行测试。

结论
Spring框架为J2EE和非J2EE环境提供了全量的事务分离的特性,特别表现在POJO的声明性事务上。他用一种灵活而非侵入式的方式为非EJB环境中的事务分离提供了便利。与EJB不同,这样的事务性POJO应用对象可以很容易的被测试和在J2EE容器外补重用。

Spring 提供了各种事务策略,如JtaTransactionManager是用来代理J2EE服务器的事务协调器,而JDBC DataSourceTransactionManager是用来为简单的JDBC DataSource(就是单一目标数据库)执行事务。Spring可以很容易为不同的环境通过后台配置的简单修改来调整事务策略。

超越 标准的JTA支持,Spring为WebLogic Server的JTA扩展提供了完善的集成,可以支持高级特性如事务监视和每事务隔离级别。通过对WebLogic Server的特殊支持,基于Spring的应用可以完全利用WebLogic Server的事务管理功能。

Spring事务分离是继 EJB CMT之外的另一种可选方式,特别是对那些基于POJO的轻量级架构。在那只是因为选择LSSB(本地无状态会话BEAN)来应用声明性事务的情况下,基 于Spring的POJO服务模型是一种可行的选择,他提供了非常高层的灵活性、可测试性和重用性。

资源
&#8226;JTA - The JTA specification JTA规范
&#8226;WebLogic JTA - Documentation of WebLogic's JTA extensions WebLogic  JTA扩展文档

关于作者
Juergen Hoeller是Spring框架的创始人之一
posted @ 2005-12-16 22:39 Dion 阅读(5051) | 评论 (1)编辑 收藏