Java Votary

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

2005年12月2日 #

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 阅读(325) | 评论 (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 阅读(2073) | 评论 (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 阅读(1625) | 评论 (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 阅读(1712) | 评论 (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 阅读(945) | 评论 (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 阅读(1154) | 评论 (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 阅读(1326) | 评论 (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 阅读(5076) | 评论 (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 阅读(1472) | 评论 (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 阅读(921) | 评论 (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 阅读(2578) | 评论 (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 阅读(2971) | 评论 (3)编辑 收藏

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

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

物流软件厂商出路何在?


■本报记者 凡晓芝



“几乎没有一个物流企业是赢利的”、“几乎没有一家物流软件提供商是赚钱的”、“几乎没有一个物流信息化项目是成功的”,这样的判断背后的事实是,物流软件厂商的日子比黄莲还苦。

“大家都知道做ERP苦,我们做物流软件的比ERP更苦。”坐在我对面的某物流软件公司总经理臧先生一脸无奈。三年前,他从一家国外的ERP公司跳槽到此,因为那时候他对“物流信息化高潮已经来临”深信不疑。

“现在,物流软件对于我们集团来说就是一块鸡肋,食之无味,弃之可惜”。事实上,从2004年起,业界就曾风传该公司将被卖掉。熬到2005年年底,情况并没有好转而业绩更糟。“年后我得另谋生路了,”臧先生说,“但物流软件公司,打死我也不去。”

比做ERP还苦

关于物流信息化有几段“名言”:“几乎没有一个物流企业是赢利的”、“几乎没有一家物流软件提供商是赚钱的”、“几乎没有一个物流信息化项目是成功的。”这样的判断就如同“中国ERP实施成功率为零”一样的偏颇。但是其背后的事实是,“物流软件厂商的日子比黄莲还苦”。

随便拿起一个物流软件公司的宣传册,烟草、医药、石油、化工、快速消费品等行业巨头的成功案例都赫然在目。“这些 动辄上百万元,甚至上千万元的项目其实没有几个是赚钱的。”另一家物流软件公司的总经理陈先生对记者说:“就好比夏奈尔每年的时装发布会,只是为了一个品 牌形象,实在不指望它能赚钱。大型物流信息化项目基本上就是赔本赚吆喝。”

也有人甘愿“赔本赚吆喝”,上海博科公司营销副总经理周龙就认为大型项目就算不赚钱也是“值”的。“我们所做的 诸如中国石油这样的大型的物流信息化项目,哪怕利润不是很高,我们也要坚持做,不但坚持做还要让客户满意,这对我们品牌宣传很重要。”周龙认为,博科在国 内物流信息化领域的地位日渐强势,与这些大项目的实施紧密相关。

但是更多的厂商是“赔了夫人又折兵”,被无休止的“烂尾工程”拖入泥沼。臧先生所在的物流公司在2004年希望 能上演“绝地反击”,为了项目和销售额几乎到了不择手段的地步,在竞标中时常被称为“搅屎棍”,以超低价格接连接了四个大的项目,但是灾难也就接踵而至, 项目实施无休无止,甚至有客户难以忍受该公司的“折磨”而要和厂商对簿公堂。

之所以要这样冒险一搏的原因还是恶劣的竞争环境使然。“泥沙俱下,鱼龙混杂”是一位业内人士对目前物流软件市场 的看法。由于2002年前后,业界爆炒物流信息化高潮来临,引来无数的投机者和投资者,物流软件提供商的数量急剧增多,据中国物流信息化中心初步统计,我 国物流软件厂商有500多家,此外还有一些知名的国外物流商、IT公司和咨询公司也在中国的物流软件市场中淘金。

北京海淀东北旺一家物流公司的老板金先生对记者说,今年至少有30家软件公司找过他,报价最低5万元,最高的是 300万元。“说实话,我被他们越搞越糊涂了,不知道究竟该相信谁。也不知道他们说的是不是真话。”事实上,据业内人士透露,声称做物流信息软件的公司中 70%到80%的企业都属于“混水摸鱼”,他们就是在传统仓储管理软件或者运输管理软件的基础上加以修改,然后就宣称这是一套物流信息系统。“懂物流的企 业不多,却有更多的企业在搅局。”

三个错位

业内分析人士认为,造成目前物流软件厂商集体陷入困境的原因是“三个错位”。

AMT企业资源研究中心资深顾问赵杨说:“对物流信息化的爆炒而带来的恶性竞争是物流软件厂商陷入困境的主要原因。”而从另外一个角度看,其实就是市场需求滞长而厂商预期过高之间的错位使然。

据中国物流信息中心预测,2004~2006年,每年物流软件市场的总体规模将维持在20亿~35亿元人民币之 间,年增长率大约为5%左右。但是在此之前,大多数的物流软件厂商都对这个市场过于乐观,使得市场竞争者蜂拥而至。像用友金蝶这样的专业ERP公司都成立 物流部门做企业物流业务,国外巨头如i2、SSA、曼哈顿等凭借在自动化仓库、物流供应链执行等方面强势进入市场,加之数不胜数的“散兵游勇”型软件公 司,物流信息化市场掠食者众多。

博科公司营销中心副总经理周龙说:“第三方物流企业的信息化被认为是拉动物流行业信息化的关键,但事实上,第三 方物流企业目前多数不赚钱,在信息化方面投入很小,而且这一块市场远远没有启动起来。”寄望已久的市场需求爆发点迟迟未到,但是参与竞争者却“人满为 患”,物流软件厂商打单难,赚钱更难也就不难理解。

其次是大系统和中小企业需求之间的错位。根据2005年中国物流信息中心对中国企业物流信息化现状和趋势的调查 情况看,未建立信息系统的企业绝大部分是小型企业,预计投资大于100万的比例只有1.8%,37.5%的企业投资低于10万元,这些数据可以看出,小型 企业的信息化需求潜力巨大。

但是正如中国物流与采购联合会副会长戴定一所指出,由于多数开发商认为,“物流信息化市场在低端不具备开发价 值”,多数厂商只是把眼光盯着几个有限的行业和著名企业,对小企业、小项目并不上心。比如海运、船运和集装箱运输这些高端物流项目确实利润丰厚,项目动辄 就是几百万,甚至10个亿投入,但是这一块市场基本上都被IBM、HP、SAP等等厂商所把持。

国内很多专业的物流软件厂商宁愿为极少的“高端客户”和大项目打得头破血流,也不愿意针对中小企业客户开发“适销对路”的产品。因此大家都做得很辛苦。

第三是高成本与低回报之间的错位。软件开发成本高昂,但是物流企业出得起的钱却很少,软件厂商基本上处于“做一个 亏一个”的恶性循环中。赵杨对记者说:“物流行业的需求千差万别,比如快速消费品和医药、烟草行业就是完全不同的流程,要把物流软件做成标准化产品基本上 是不可能的。目前市场上声称的所谓标准化的产品只不过是一种噱头而已。”由于绝大多数的物流项目都是定制化开发,不但项目实施的周期长,而且项目常常陷入 无休止的修改和二次开发的泥沼之中。

做“全”或者做“专”

“再辛苦,我们也要坚持到底。”一家专业物流软件厂商的总经理对记者说 :“不经历风雨怎么见彩虹。”这其实是多数物流软件厂商的心态,在投入了上千万元的资金、积累了一些客户基础以后,完全放弃物流信息化市场确实是很难。更何况,物流信息化可以说“前途是光明的”。

“但是物流软件厂商必须要改变,如果抱残守缺肯定只有死路一条。”采访中赵杨对物流软件厂商的未来并不十分看好。 “如果说要求解,物流软件厂商要么做全,要么做专。”赵杨认为,所谓做“全”就是不要局限于物流软件,而是由物流供应链切入到包括ERP、SCM、CRM 等业务系 ;而做“专”就是盯准一个行业做产品和客户。

物流信息化市场涉及的目标客户非常广泛,如储运公司、国际货代公司、船代公司、包裹快递公司、邮政部门、零售公司、大型工业企业中的物流部门等等。不同市场对信息化的需求既有一定的共性,又存在一定差异。物流软件厂商必须要立足自身实际情况,确定合适的目标市场。

也有分析人士认为,做专的另一种方式是提供细分化产品。物流管理是一个综合的系统工程,整个物流运作是运输、储 存、装卸、搬运、包装、流通加工、配送等基本功能的有机结合。如果厂商能围绕部门级信息系统如仓储管理系统、运输管理系统、配送管理系统、报关管理系统、 货代管理系统、快递管理系统等开发出一些精深且有特色的产品,将能够更大程度地增强自身的市场竞争力。

事实上,诸如上海博科这样的选择坚守的物流软件厂商也开始进行业务的调整。记者在采访中了解到,针对大型企业物 流的需求,博科将改变以前的单个物流项目的做法。周龙说:“我们的物流系统将和博科集团财务、ERP、供应链等产品组合成为一个整体,做成一个整体解决方 案提供给客户。”而对于中小型的物流公司,则通过平台化产品,通过渠道销售的方式满足客户需求。而记者在采访中得知,另一家知名的物流软件厂商招商迪辰也 “正在业务调整中”。

“物流软件厂商无论做专还是做全,都必须要做精”,赵杨强调说:“厂商的产品和品牌形象至关重要。”根据中国物 流信息中心最新的调查报告,物流客户对开发商最看重的几项条件分别是:成功案例(85%)、咨询与服务经验(73.3%)、品牌46.7%、价格因素 45%。对未建系统的企业调查显示,对有成功案例最看重(40%),其次是品牌(26%)。由此可见,在未来的竞争中,物流软件厂商必须得拿“牌子”见 客,拿产品说话。

(计算机世界报 2005年12月05日 第47期 E5)

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

Google编程大赛入围赛250分真题


Problem Statement
????
You are given a String[] cityMap representing the layout of a city. The city
consists of blocks. The first element of cityMap represents the first row of
blocks, etc. A 'B' character indicates a location where there is a bus stop.
There will be exactly one 'X' character, indicating your location. All other
characters will be '.'. You are also given an int walkingDistance, which is the
maximum distance you are willing to walk to a bus stop. The distance should be
calculated as the number of blocks vertically plus the number of blocks
horizontally. Return the number of bus stops that are within walking distance of
your current location. Definition
????
Class:
BusStops
Method:
countStops
Parameters:
String[], int
Returns:
int
Method signature:
int countStops(String[] cityMap, int walkingDistance)
(be sure your method is public)
????

Constraints
-
cityMap will contain between 1 and 50 elements, inclusive.
-
Each element of cityMap will contain between 1 and 50 characters, inclusive.
-
Each element of cityMap will contain the same number of characters.
-
Each character of each element of cityMap will be 'B', 'X', or '.'.
-
There will be exactly one 'X' character in cityMap.
-
walkingDistance will be between 1 and 100, inclusive.
Examples
0)

????
{"...B.",
 ".....",
 "..X.B",
 ".....",
 "B...."}
3
Returns: 2
You can reach the bus stop at the top (3 units away), or on the right (2 units
away). The one in the lower left is 4 units away, which is too far. 1)

????
{"B.B..",
 ".....",
 "B....",
 ".....",
 "....X"}
8
Returns: 3
A distance of 8 can get us anywhere on the map, so we can reach all 3 bus stops.
2)

????
{"BBBBB",
 "BB.BB",
 "B.X.B",
 "BB.BB",
 "BBBBB"}
1
Returns: 0
Plenty of bus stops, but unfortunately we cannot reach any of them.
3)

????
{"B..B..",
 ".B...B",
 "..B...",
 "..B.X.",
 "B.B.B.",
 ".B.B.B"}
3
Returns: 7

This problem statement is the exclusive and proprietary property of TopCoder,
Inc. Any unauthorized use or reproduction of this information without the prior
written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder,
Inc. All rights reserved.

posted @ 2005-12-15 13:14 Dion 阅读(1179) | 评论 (0)编辑 收藏

面向方面的Annotation

作者:Bill Burke,Enterprise JavaBeans 第四版合著者

译者:yahveyeye





版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Bill Burke;yahveyeye
原文地址:http://www.onjava.com/pub/a/onjava/2004/08/25/aoa.html
中文地址:http://www.matrix.org.cn/resource/article/44/44052_Annotation+Aop.html
关键词: Annotation Aop

Annotation 是J2SE5.0的一项新功能,它允许您附加元数据到Java构建中。同时,面向方面编程(AOP)是一个相当新的技术,它可以使您封装某些行为,这些行 为是在使用面向对象(OO)技术时会更为混乱,困难甚至是不可能完成。这两项技术结合起来给框架开发者开发的APIs更好的表达方式。本文深入结合这些技 术,使用Jboss AOP框架,以不同的代码范例向您展示如何结合两者来实际地扩展Java 语言。

相关文章:
Java Annotation入门:
http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html

Annotation概述

首 先让我们给出这两项技术的一个概述。Annotation是JDK5.0的新功能,它在JSR-175规范中有详细定义。它们允许您以安全的方法定义元数 据并应用到类,方法,构造程序,字段或参数中。对于你们中熟悉XDoclet的人来说,Annotation将非常直观,您可以用来声明标签以产生代码。 两者的主要不同是Annotation是Java语言的一部分而XDoclet标签可能会打错并且难以创建。我喜欢用例子来说明,所以让我们展示一个简单 的例子。

要定义一个Annotation,您所要做的就是声明一个特殊类型的Java接口。

清单1:Orange.java
package org.jboss.collors;
public @interface Orange{}


定义了这个接口,您就可以用来提供更多的描述给您的Java元素。
清单2:Foo.java
package org.jboss.examples;
public class Foo
{
  @Orange void someMethod();
  @Orange private int someField;
}


那么我们可以用Annotation来干什么呢?一些人想用Annotation来产生代码并替代XDoclet,其他人,象J2EE和EJB3.0专家组,将它视为部署描述符的替代。本文谈论在AOP中如何使用Annotation

AOP概述

有许多的文章和书籍解释AOP到底是什么,例如Graham O'Regan的ONJava文章“Introduction to Aspect-Oriented Programming."我将在本文给出一个快速的概览,但我鼓励您在线做更多的研究。

假设您要添加代码到一个应用程序去测试调用一个特定的java方法所需的总的时间。该代码可能看起来如下:
清单3:
public class BankAccount
{
public void withdraw(double amount)
{
long startTime = System.currentTimeMillis();
try
{
// Actual method body...
}
finally
{
long endTime = System.currentTimeMillis() - startTime;
System.out.println("withdraw took: " + endTime);
}
}
}


虽然这些代码能够正常工作,但这个方法有一些问题:
1.它难以打开和关闭测试,您必须在try/finally块中对每个方法或购置函数手工增加代码以进行基准测试。
2。这一轮廓代码并不真正属于贯穿整个应用的代码。它使得您的代码臃肿并难以理解,因为您必须将计时放在try/finally块中。
3、如果您想扩展它的功能以包含一个方法或是失败计数,或甚至是注册这些统计数据到一个更为复杂的报告机制中,您必须修改大量不同文件(又一次)。

Metrics 类提供了一个什么是横切(cross-cutting)关系的完美,简洁的小例子。Jboss AOP以一种含蓄的方式提供了一个简单的方法来封装和应用这样的关系,这样某些象度量操作代码不会弄乱您的编码。让我们稍为深入到Jboss AOP一些来看看如何实现。
为了使用Jboss AOP封装度量功能,您首先需要定义一个方面来指出该度量行为。
清单4:
public class Metrics
{
public Object profile(MethodInvocation invocation) throws Throwable
{
long startTime = System.currentTimeMillis();
try
{
return invocation.invokeNext();
}
finally
{
long endTime = System.currentTimeMillis() - startTime;
java.lang.reflect.Method m = invocation.getMethod();
System.out.println("method " + m.toString() +
" time: " + endTime + "ms");
} }
}


一 个方面只是一个具有定义了您想要附加到您的对象模型的行为的普通Java类。这些方法的签名必须返回一个java.lang.Object并且必须具有一 个(并且只有一个)Jboss AOP 调用对象参数,它被用来封装方法,构造函数或字段调用。方法名可以是任何你想要的并且当您绑定该方面到您的代码片断时被引用。

下面要做的事情就是实际应用方面到您想要它勾勒一个方法的执行的某个程序点。大多数AOP框架提供了一个指向表达式语言,在此处您可以定义您想要某个方面行为被附加到的位置。下面是在Jboss AOP中的做法。
清单5:jboss-aop.xml
<aop>
<aspect class="Metrics"/>

<bind pointcut="execution(public void BankAccount->withdraw(double amount))">
<advice name="profile" aspect="Metrics"/>
</bind>
</aop>


采用在Metrics.java中对方面的定义和jboss-aop.xml中的指向定义,该度量代码现在以含蓄而又透明地应用到BankAccount.withdraw()方法中并能在勾勒代码不再需要时轻易地移除。
对于Jboss AOP更多的信息,请查询分发包中的指南。其中具有大约20个例子来带领您漫游如何使用Jboss AOP框架。

嘘!现在我们已经进行了一个概览,让我们深入到本文的中心内容。我将再次给您提供一些例子,因为这是我所知道的讲授一个新的概念的最好的方法。
正如我前面说的,Annotation加上AOP几乎是给予您扩展Java语言的能力。Annotation提供了声明新的,可兼容的,类型安全的语法机制。AOP提供了封装和应用新的行为到一个语法表达式的机制。

方法Annotation和AOP

让 我们看看如何使用方法Annotation和AOP。使用Annotation和AOP并应用到一个方法类似于使用Java的synchronized关 键字。当您设定一个方法为synchronized,您在告诉JVM:您想该方法在被调用时以一种特殊的方式进行。Annotation允许您定义一个新 的关键字来触发您自己的特殊的定制行为。AOP给予您封装这一行为的能力并将其“编织”进该方法的执行中。再次的,这一概念的最佳描述是通过一个例子。

让我们假设我们想要添加新的语法,使用该语法使得我们可以在方法被标签为@Oneway时,在后台以另一个线程调用这个void方法。可以象这样使用新的语法:

清单6:
Import org.jboss.aspects.Oneway;
public class Foo
{
  @Oneway public static void someMethord(){…}
public static void main(String[] args){
somMethod();//executes in
backgroud
}
}


当someMethod()在main中被调用,它将异步运行,这样main中的代码可以并行执行其他任务。
要实现这一功能,首先要在一个Annotation中为我们的@Oneway标签定义新的Java语法.
清单7:Oneway.java
package org.jboss.aspects;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
public @interface Oneway {}


够简单的。@Target标签允许您缩小Annotation可以应用的地方。在本例中,我们的@OnewayAnnotation只能应用到一个方法。记住,这些都是J2SE5.0百分之百可用的纯Java。
下面要做的事是定义一个封装我们的@Oneway行为的方面类。
清单8:OnewayAspect.java

package org.jboss.aspects;

public OnewayAspect
{
private static class Task implements Runnable
{
private MethodInvocation invocation;

public Task(MethodInvocation invocation)
{
this.invocation = invocation;
}
public void run()
{
try { invocation.invokeNext(); }
catch (Throwable ignore) { }
}
}


public Object oneway(MethodInvocation invocation) throws Throwable
{
MethodInvocation copy = invocation.copy();
Thread t = new Thread(new Task(copy));
t.setDaemon(false);
t.start();
return null;
}
}


这 个方面够简单。oneway()方法拷贝invocation,创建一个线程,在后台启动整个调用并返回。我们可以想象一个更为复杂的例子:使用J2SE 5.0 java.util.concurrent包中的某些新的Executors,但这些代码很有希望阐明了如何基于这个例子构建更为复杂的实现。
最后必须要做的事情是指定当@OnewayAnnotation在一个方法中声明时触发OnewayAspect应用的指向表达式。

清单9:jboss-aop.xml
<aop>
<aspect class="org.jboss.aspects.OnewayAspect"/>
<bind pointcut="execution(void *->@org.jboss.Oneway(..))">
<advice name="oneway"
aspect="org.jboss.aspects.OnewayAspect"/>
</bind>
</aop>


该 指向表达式规定任何具有@Oneway标签的void方法都应该有OnewayAspect.oneway()方法在它本身执行前被执行。随着 Annotation,方面和现在定义的指向表达式,@Oneway语法现在可以用于您的应用程序中。一个简单,清晰,易于实现的方法来扩展Java 语言!

字段Annotation和AOP

让 我们看看如何使用字段Annotation和AOP。使用Annotation和AOP,您可以改变一个对象的字段或是作为一个类的静态成员的实际存储方 式。在这个例子里我们要完成的是当您将一个字段(静态或是成员)标记上@ThreadBased,尽管是将它存储在 java.lang.ThreadLocal,但它的值依然正常。当然,您可以直接使用ThreadLocal变量,但问题是ThreadLocal并非 一个类型并且您必须使用“麻烦的”(好,它们并没有那么罗嗦)get()和set()方法。那么我们现在做的就是创建一个ThreadLocal类型的字 段。我们主要的将创建一个称为@Thradbased变量的新的Java字段类型。
象这样使用新的类型:
清单10:
import org.jboss.aspects.Threadbased;
public class Foo
{
@Threadbased private int counter;
}


为了实现这个功能,我们必须先定义Annotation
清单11:Threadbased.java
package org.jboss.aspects;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
public @interface Threadbased {}



够简单。@Target标签允许您缩小Annotation可以应用的地方。在本例中,我们的@ThreadbasedAnnotation只能应用到字段。
下面的事情是定义封装我们的ThreadLocal行为的方面。
清单12:ThreadbasedAspect.java
package org.jboss.aspects;
import org.jboss.aop.joinpoint.*;
import java.lang.reflect.Field;
public class ThreadbasedAspect
{
private ThreadLocal threadbased = new ThreadLocal();
public Object access(FieldReadInvocation invocation)
throws Throwable
{
// just in case we have a primitive,
// we can't return null
if (threadbased.get() == null)
return invocation.invokeNext();
return threadbased.get();
}
public Object access(FieldWriteInvocation invocation)
throws Throwable
{
threadbased.set(invocation.getValue());
return null;
}
}


ThreadbasedAspect 封装到一个Java字段的访问。它里面具有一个专门的ThreadLocal变量跟踪thradlocal变为一个特殊的字段。它还有一个单独的 access()方法,该方法根据一个字段的get或set方法是否被调用决定它是否被调用。这些方法委托给ThreadLocal来获得字段的当前值。
最后,我们必须定义一个指向表达式,当@ThreadbasedAnnotation在某个字段被指定时触发ThreadbasedAspect的应用。
清单13:jboss-aop.xml
<aop>
<aspect class="org.jboss.aspects.ThreadbasedAspect" scope="PER_JOINPOINT"/>
<bind pointcut="field(* *->@org.jboss.aspects.Threadbased)">
<advice name="access"
aspect="org.jboss.aspects.ThreadbasedAspect"/>
</bind>
</aop>


只 有当我们具有多个@Threadbased变量定义在同一个类时,我们需要为每个静态字段分配一个ThreadbasedAspect实例。对于成员变 量,我们需要为每个字段,每个对象实例分配一个ThreadbasedAspect实例。为了促进这一行为,方面定义通过设定实例为 PER_JOINPOINT限制方面类的实例何时和何地被分配出去的范围。如果我们不做限制,Jboss
AOP会只分配一个ThreadbasedAspect实例并且不同的字段会共享相同的ThreadLocal接口——这不是我们所希望的。

好就这样。一个清晰容易的扩展Java来指定一个新的特殊类型的方法。注意:该特殊的方法来自Jboss AOP束。

依赖注入

字 段Annotation和AOP可以使用的一个有趣的地方是依赖注入。依赖注入是关于对象声明它们需要什么信息,配置或服务引用以及运行时自动注入这些依 赖而不是用代码明确地在一个注册中心查找。在J2EE领域,获得javax.transaction.TransactionManager服务的访问并 未标准化并且实际上不同的厂商有不同的实现。许多框架开发者需要使用TransactionManager来实现定制事务服务。使用字段 AnnotationAOP提供依赖注入并抽取出一个需要TransactionManager的组件如何引用它的细节是一个了不起的方法。让我们定义一 个方面,它将注入一个TransactionManager引用到一个字段值中。

首先,我们再次定义我们的Annotation。

清单14:Inject.java
package org.jboss.aspects;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
public @interface Inject {}


下面我们将定义方面类,它封装了TransactionManager的解析。该方面是特定于JBoss应用服务器,但您可以定义为每个厂商定义不同的实现。
清单15:InjectTMAspect.java
package org.jboss.aspects;
import org.jboss.aop.joinpoint.*;
import java.lang.reflect.Field;
import javax.transaction.TransactionManager;
import org.jboss.tm.TxManager;
public InjectTMAspect
{
private TransactionManager tm = TxManager.getInstance();
public Object access(FieldReadInvocation invocation)
throws Throwable {
return tm;
}
public Object access(FieldWriteInvocation invocation)
throws Throwable {
throw new RuntimeException(
"Setting an @Injected variable is illegal");
}
}


最后,我们必须定义XML绑定来触发当@Inject标签应用到一个字段时InjectTMAspect的应用。指向表达式基本上说明了对任意一个标记为@Inject的TransactionManager字段应用InjectTMAspect。
清单16:
<aop>
<aspect class="org.jboss.aspects.InjectTMAspect"/>
<bind pointcut="field(javax.transaction.TransactionManager *->@org.jboss.aspects.Inject)">
<advice name="access"
aspect="org.jboss.aspects.InjectTMAspect"/>
</bind>
</aop>


现在Annotation、方面类和XML绑定已经定义,我们可以在我们的代码中使用了。
清单17:
import javax.transaction.TransactionManager;
import org.jboss.aspects.Inject;
public class MyTransactionalCache
{
@Inject private TransactionManager tm;
...
}


更多预打包例子

Jboss AOP不仅仅是关于AOP框架。它还有一个丰富的方面库,您可以直接在您的应用中使用。在这个库中是一个比我们现在在本文展示的例子更为复杂的 Annotation方面集。这些方面包括异步调用,事务划分,事务锁定和基于角色的安全。让我们简要地浏览一下以提供给您一个更好的关于 Annotation和AOP共同工作的考虑。

异步方面
Jboss AOP异步方面允许您定义任何方法为异步的,这样它可以在后台被执行。这对于我们的@Oneway例子来说有些困难,因为它使用Oswego并行包中的执 行器工具,并为那些具有一个返回类型的方法提供了一个方法来异步地接收回响应。要使用这个方面,您只需标记一个方法为@Asybchronous.
清单18:
public Foo {
@Asynchronous public int someMethod(int someArg) {...}
}


@Asynchronous 标签的应用做了一些事情。与在本文中的@Oneway例子一样,它应用一个在后台运行该方法的方面。而且,采用@Asynchronous标签,您并不仅 限于void方法并可于实际上返回一个值的方法进行交互。当@Asynchronous标签被应用,它强制Foo类实现 AsynchronousFacade接口。在AOP领域,这称为接口引入(interface introduction)。AsynchronousFacade接口允许您预测一个响应或以超时限定等待一个响应。最好用一个例子来解释。
清单19:
Foo foo = new Foo();
someMethod(555); // executes in background
AsynchronousFacade facade = (AsynchronousFacade)foo;
AsynchronousResponse response = facde.waitForResponse();
System.out.println(response.getReturnValue());


您可以启动多个不同对象的多个不同方法的多个调用,并异步积累它们的响应。

事务锁定
有时在J2EE事务期间而不是一个方法执行,构造函数调用或同步块执行期间同步一个对象或类会很有用。对这类事务同步或锁定,Jboss AOP发明了@TxSynchronized关键字。您可以使用@TxSynchronized在任意成员或静态方法已经构造函数上。
清单20:
import org.jboss.aspects.txlock.TxSynchronized;
public FooBar
{
@TxSynchronized public FooBar() {}
@TxSynchronized static void staticMethod() {}
@TxSynchronized public memberMethod() {}
}


如 果一个被标记为@TxSynchronized的构造函数或静态方法被调用,类的锁监视器会在事务执行期间被保持着。如果一个标记为 @TxSynchronized的成员方法被调用,该对象实例的锁监视器将被保持直到目前的事务提交或回退。控制该行为的方面也将做死锁检测并在发生死锁 时抛出RuntimeException。

J2EE 散餐(原文法文:a la carte)之:事务划分
EJB3.0已经定义了一些Annotation进行事务划分。Jboss AOP在此基础上构建。这样您可以通过指定Annotation应用事务划分到任意方法(静态或成员)以及任何Java类构造函数。
清单21:
import org.jboss.aspects.tx.*;
public class Foo
{
@Tx(TxType.REQUIRED) public Foo {}
@Tx(TxType.REQUIRESNEW) public static createFoo() {
return new Foo();
}
}


J2EE 散餐之:基于角色的安全
EJB 3.0也定义了一些Annotation实现基于角色的安全。Jbos AOP是基于此构建的,所以您可以应用基于角色的安全到任何的字段或方法(静态或成员)已经构造函数。
清单22:
import org.jboss.aspects.security.*;
@SecurityDomain("LDAP Repository")
public class Foo
{
@Permissions({"admin"}) public Foo() {}
@Permissions({"developer"}) public static int status;
@Permissions({"anybody"}) public static void doSomething() {...}
}


EJB演变

随 着AOP与EJB规范一起渐渐成熟,我真正希望发生的是EJB规范定义的Annotation将能在任何上下文作为新的Java语言的形容词被使用,而不 是让它们有限的使用在会话bean中。想象一下,一个真正的无状态bean仅仅成为一个明文Java类的一个静态方法集。
清单23:
public MySessionBean
{
@Tx(TxType.REQUIRED) public static doSomething() {...}
}


无论如何,这些关于AOP和EJB的讨论很可能就是为了EJB4.0。

结论
并非是限制J2SE5.0Annotation用于代码生成,Annotation和AOP可以被结合起来提供新的能力给框架开发者。这一结合允许开发者定义新的具有行为附加到其上的Java语法。基本上,以安全的方式扩展Java语言的能力已尽在掌握。

资源
·Matrix-Java开发者社区:http://www.matrix.org.cn
·onjava.com:onjava.com

关于作者
Bill Burke :JBoss 首席架构师.
posted @ 2005-12-15 13:12 Dion 阅读(1610) | 评论 (0)编辑 收藏

对spring 了解的不够精通,这两天在解决jms异常的过程中发现,spring中提供了jmsTrasactionManager,同样实现了事务管理接口。这样用 自动的拦截器,就可以象数据库一样自动控制事务。在同时配置了JMS和数据库事务的时候,两者同时有效。这样系统的消息和数据库事务就轻量级的一致了!

数据库的spring配置参见:http://steeven.cnblogs.com/archive/2005/06/14/174410.html
jms部分如下:

    <bean id="remoteJmsConnectionFactory"
        class
="org.activemq.ActiveMQConnectionFactory">
        
<property name="useEmbeddedBroker">
            
<value>true</value>
        
</property>
        
<property name="brokerURL">
            
<value>tcp://localhost:61616</value>
        
</property>
    
</bean>

    
<bean id="jmsTM"
        class
="org.springframework.jms.connection.JmsTransactionManager">
        
<property name="connectionFactory">
            
<ref bean="remoteJmsConnectionFactory" />
        
</property>
    
</bean>

    
<bean id="jmsTransactionInterceptor"
        class
="org.springframework.transaction.interceptor.TransactionInterceptor">
        
<property name="transactionManager">
            
<ref bean="jmsTM" />
        
</property>
        
<property name="transactionAttributeSource">
            
<bean
                
class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" />
        
</property>
    
</bean>

    
<bean
        
class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
        
<property name="transactionInterceptor">
            
<ref bean="jmsTransactionInterceptor" />
        
</property>
    
</bean>

    
<bean id="destResolver"
        class
="test.message.EnumDestinationResolver" />

    
<!-- for send jms to remote server -->
    
<bean id="remoteJmsTemplate"
        class
="org.springframework.jms.core.JmsTemplate">
        
<property name="connectionFactory">
            
<ref bean="remoteJmsConnectionFactory" />
        
</property>
        
<property name="destinationResolver">
            
<ref local="destResolver" />
        
</property>
    
</bean>

应用程序很简单
@Transactional
public class TestServiceImpl implements TestService {
    
public void someMethod() {
        getJmsTemplate().send(someMessage);
    }

}
posted @ 2005-12-13 23:49 Dion 阅读(1374) | 评论 (0)编辑 收藏

     摘要: hibernate3.0+ejb3 annotaion配置实战+spring1.21 annotation事务控制 我是比较讨厌xml的人,没有强类型,很多配置出错,包括xdoclet都无法检查。刚好现在的主流框架总算开始支持annotation了,所以玩了一下配置,供参考:hibernate3.05hibernate-annotations-3.0beta2spring1.21几个配置...  阅读全文
posted @ 2005-12-13 23:32 Dion 阅读(2240) | 评论 (2)编辑 收藏

Tiger 中的注释,第 2 部分: 定制注释

Write your own annotations in Java 5

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


对此页的评价

帮助我们改进这些内容


级别: 初级

Brett McLaughlin, 作者/编者, O'Reilly Media, Inc

2004 年 9 月 01 日

本系列文章的 第 1 部分介绍了注释 —— J2SE 5.0 中新的元数据工具,并重点讨论了 Tiger 的基本内置注释。一个更强大的相关特性是支持编写自己的注释。本文中,Brett McLauglin 说明了如何创建定制注释,如何用自己的注释注解文档,并进一步定制代码。

本系列的第一篇文章 介绍了什么是元数据,元数据的重要性,以及如何使用 J2SE 5.0(也叫做 Tiger)的基本内置注释。如果习惯了这些概念,您可能已经在想,Java 5 提供的三种标准注释也并不是特别健壮,能使用的只有 DeprecatedSuppressWarningsOverride 而已。所幸的是,Tiger 还允许定义自己的注释类型。在本文中,我将通过一些示例引导您掌握这个相对简单的过程。您还将了解如何对自己的注释进行注解,以及这样做的一些好处。我要 感谢 O'Reilly Media, Inc.,他们非常慷慨地允许我在本文中使用我关于 Tiger 的书籍的“注释”一章中的代码示例(请参阅 参考资料)。

定义自己的注释类型


通过添加了一个小小的语法(Tiger 添加了大量的语法结构),Java 语言支持一种新的类型 —— 注释类型(annotation type)。注释类型看起来很像普通的类,但是有一些特有的性质。最明显的一点是,可以在类中以符号( @ )的形式注释其他 Java 代码。我将一步一步地介绍这个过程。

@interface 声明


定义新的注释类型与创建接口有很多类似之处,只不过 interface 关键字之前要有一个 @ 符号。清单 1 中给出的是一个最简单的注释类型的示例:
清单 1. 非常简单的注释类型

package com.oreilly.tiger.ch06;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
public @interface InProgress { }

清单 1 的含义非常明显。如果编译这个注释类型,并确信其位于类路径中,那么您就可以在自己的源代码方法中使用它,以指出某个方法或类仍在处理中,如清单 2 所示:

清单 2. 使用定制的注释类型
@com.oreilly.tiger.ch06.InProgress
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

清单 1 所示注释类型的使用方法和内置注释类型的使用方法完全相同,只不过要同时使用名称和所在的包来指示定制注释。当然,一般的 Java 规则仍然适用,您可以导入该注释类型,直接使用 @InProgress 引用它。

不要漏掉本系列的另一部分

一定要阅读本系列文章的“ 第 1 部分”,其中介绍了 Java 5.0 中的注释。

添加成员


上面所示的基本用法还远远不够健壮。您一定还记得“第 1 部分”中曾经提到的,注释类型可以有成员变量(请参阅 参考资料)。 这一点非常有用,尤其是准备将注释作为更加复杂的元数据,而不仅仅将它作为原始文档使用的时候。代码分析工具喜欢加工大量的信息,定制注释可以提供这类信息。

注释类型中的数据成员被设置成使用有限的信息进行工作。定义数据成员后不需要分别定义访问和修改的方法。相反,只需要定义一个方法,以成员的名称命名它。数据类型应该是该方法返回值的类型。清单 3 是一个具体的示例,它澄清了一些比较含糊的要求:

清单 3. 向注释类型添加成员

package com.oreilly.tiger.ch06;

/**
* Annotation type to indicate a task still needs to be
* completed.
*/
public @interface TODO {
String value();
}

尽管清单 3 看起来很奇怪,但这是注释类型所要求的格式。清单 3 定义了一个名为 value 的字符串,该注释类型能够接受它。然后,就可以像清单 4 中那样使用注释类型:

清单 4. 使用带有成员值的注释类型

@com.oreilly.tiger.ch06.InProgress
@TODO("Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

这里同样没有多少花样。清单 4 假设已经引入了 com.oreilly.tiger.ch06.TODO ,因此源代码中的注释 需要包名作前缀。此外,需要注意的是,清单 4 中采用了简写的方法:将值 ("Figure out the amount of interest per month") 直接提供给注释,没有指定成员变量名。清单 4 和清单 5 是等价的,后者没有采用简写形式:

清单 5. 清单 4 的“加长”版

@com.oreilly.tiger.ch06.InProgress
@TODO(value="Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

当然作为编码人员,我们都不愿意跟这种“加长”版搅在一起。不过要注意,只有当注释类型只有 一个 成员变量,而且变量名为 value 时,才能使用简写形式。如果不符合这个条件,那么就无法利用这种特性。

设置默认值


迄 今为止,您已经有了一个很好的起点,但是要做得完美,还有很长的一段路要走。您可能已经想到,下一步就要为注释设置某个默认值。如果您希望用户指定某些 值,但是只有这些值与默认值不同的时候才需要指定其他的值,那么设置默认值就是一种很好的办法。清单 6 用另一个定制注释 —— 来自 清单 4TODO 注释类型的一个全功能版本,示范了这个概念及其实现:
清单 6. 带有默认值的注释类型

package com.oreilly.tiger.ch06;

public @interface GroupTODO {

public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION };

Severity severity()
default Severity.IMPORTANT;
String item();
String assignedTo();
String dateAssigned();
}


清单 6 中的 GroupTODO 注释类型中添加了几个新的变量。因为该注释类型的成员变量不是一个,所以将一个变量命名为 value 没有任何意义。只要成员变量多于一个,就应该尽可能准确地为其命名。因为不可能从 清单 5所示的简写形式中获益,所以您需要创建虽然稍微有点冗长,但是更容易理解的注释类型。

清单 6 中出现的另一个新特性是注释类型定义了自己的枚举(枚举,即 enumeration,通常也称为 enums,是 Java 5 的另一个新特性。它并没有多么地不同凡响,对注释类型更是如此)。然后,清单 6 使用新定义的枚举作为一个成员变量的类型。

最后,再回到我们的主题 —— 默认值。建立默认值的过程非常琐碎,需要在成员声明的后面添加关键字 default ,然后提供默认值。正如您所料,默认值的类型必须与成员变量声明的类型完全相同。同样,这也不是什么火箭科学,只不过是词法上的变异。清单 7 给出了一个具体应用中的 GroupTODO 注释,其中 没有 指定 severity 成员:

清单 7. 使用默认值

@com.oreilly.tiger.ch06.InProgress
@GroupTODO(
item="Figure out the amount of interest per month",
assignedTo="Brett McLaughlin",
dateAssigned="08/04/2004"
)
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

清单 8 中使用了同一个注释,但这一次给出了 severity 的值:

清单 8. 改写默认值

@com.oreilly.tiger.ch06.InProgress
@GroupTODO(
severity=GroupTODO.Severity.DOCUMENTATION,
item="Need to explain how this rather unusual method works",
assignedTo="Jon Stevens",
dateAssigned="07/30/2004"
)
public void reallyConfusingMethod(int codePoint) {
// Really weird code implementation
}



回页首


对注释的注释


结 束关于注释的讨论之前(至少在本系列文章中),我想简要地讨论一下注释的注释。第 1 部分中所接触的预定义注释类型都有预定义的目的。但是在编写自己的注释类型时,注释类型的目的并不总是显而易见的。除了基本的文档外,可能还要针对某个特 定的成员类型或者一组成员类型编写类型。这就要求您为注释类型提供某种元数据,以便编译器保证按照预期的目的使用注释。

当然,首先想到的就是 Java 语言选择的元数据形式 —— 注释。您可以使用 4 种预定义的注释类型(称为 元注释)对您的注释进行注释。我将对这 4 种类型分别进行介绍。

指定目标


最明显的元注释就是允许何种程序元素具有定义的注释类型。毫不奇怪,这种元注释被称为 Target 。但是在了解如何使用 Target 之前,您还需要认识另一个类,该类被称为 ElementType ,它实际上是一个枚举。这个枚举定义了注释类型可应用的不同程序元素。清单 9 给出了完整的 ElementType 枚举:
清单 9. ElementType 枚举

package java.lang.annotation;

public enum ElementType {
TYPE, // Class, interface, or enum (but not annotation)
FIELD, // Field (including enumerated values)
METHOD, // Method (does not include constructors)
PARAMETER, // Method parameter
CONSTRUCTOR, // Constructor
LOCAL_VARIABLE, // Local variable or catch clause
ANNOTATION_TYPE, // Annotation Types (meta-annotations)
PACKAGE // Java package
}

清单 9 中的枚举值意义很明确,您自己可以分析其应用的目标(通过后面的注解)。使用 Target 元注释时,至少要提供这些枚举值中的一个并指出注释的注释可以应用的程序元素。清单 10 说明了 Target 的用法:

清单 10. 使用 Target 元注释

package com.oreilly.tiger.ch06;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
* Annotation type to indicate a task still needs to be completed
*/
@Target({ElementType.TYPE,
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.ANNOTATION_TYPE})
public @interface TODO {
String value();
}

现在,Java 编译器将把 TODO 应用于类型、方法、构造函数和其他注释类型。这样有助于避免他人误用您的注释类型(或者最好的地方是, 您自己也不会因为疲惫而误用它)。

设置保持性


下一个要用到的元注释是 Retention 。这个元注释和 Java 编译器处理注释的注释类型的方式有关。编译器有几种不同选择:
  • 将注释保留在编译后的类文件中,并在第一次加载类时读取它。
  • 将注释保留在编译后的类文件中,但是在运行时忽略它。
  • 按照规定使用注释,但是并不将它保留到编译后的类文件中。

这三种选项用 java.lang.annotation.RetentionPolicy 枚举表示,如清单 11 所示:

清单 11. RetentionPolicy 枚举

package java.lang.annotation;

public enum RetentionPolicy {
SOURCE, // Annotation is discarded by the compiler
CLASS, // Annotation is stored in the class file, but ignored by the VM
RUNTIME // Annotation is stored in the class file and read by the VM
}

现在可以看出, Retention 元注释类型使用清单 11 所示的枚举值中的一个作为惟一的参数。可以将该元注释用于您的注释,如清单 12 所示:

清单 12. 使用 Retention 元注释

@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
// annotation type body
}

如清单 12 所示,这里可以使用简写形式,因为 Retention 只有一个成员变量。如果要将保持性设为 RetentionPolicy.CLASS ,那么什么也不需要做,因为这就是默认行为。

添加公共文档


下一个元注释是 Documented 。这个元注释也非常容易理解,部分原因是 Documented 是一个标记注释。您应该还记得第 1 部分中曾经提到,标记注释没有成员变量。 Documented 表示注释应该出现在类的 Javadoc 中。在默认情况下,注释 包括在 Javadoc 中,如果花费大量时间注释一个类、详细说明未完成的工作、正确完成了什么或者描述行为,那么您应该记住这一点。

清单 13 说明了 Documented 元注释的用法:

清单 13. 使用 Documented 元注释

package com.oreilly.tiger.ch06;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }


Documented 的一个实用技巧是保持性策略。注意,清单 13 中规定注释的保持性(retention)是 RUNTIME ,这是使用 Documented 注释类型所 必需的。Javadoc 使用虚拟机从其类文件(而非源文件)中加载信息。确保 VM 从这些类文件中获得生成 Javadoc 所需信息的惟一方法是将保持性规定为 RetentionPolicy.RUNTIME 。这样,注释就会保留在编译后的类文件中 并且由虚拟机加载,然后 Javadoc 可以从中抽取出来添加到类的 HTML 文档中。

设置继承


最后一个元注释 Inherited ,可能是最复杂、使用最少、也最容易造成混淆的一个。这就是说,我们简单地看一看就可以了。

首先考虑这样一种情况:假设您通过定制的 InProgress 注释标记一个类正在开发之中,这完全没有问题,对吧?这些信息还会出现在 Javadoc 中,只要您正确地应用了 Documented 元注释。现在,假设您要编写一个新类,扩展那个还在开发之中的类,也不难,是不是?但是要记住,那个超类还在开发之中。如果您使用子类,或者查看它的文档,根本没有线索表明还有什么地方没有完成。您本来希望看到 InProgress 注释被带到子类中 —— 因为这是 继承 的 —— 但情况并非如此。您必须使用 Inherited 元注释说明所期望的行为,如清单 14 所示:

清单 14. 使用 Inherited 元注释


package com.oreilly.tiger.ch06;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
@Documented

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }


添加 @Inherited 后,您将看到 InProgress 出现在注释类的子类中。当然,您并不希望所有的注释类型都具有这种行为(因此默认值是 继承的)。比如, TODO 注释就不会(也不应该)被传播。但是对于这里示范的情况, Inherited 可能非常有用。



回页首


结束语


现 在,您也许已经准备回到 Java 世界为所有的事物编写文档和注释了。这不禁令我回想起人们了解 Javadoc 之后发生的事情。我们都陷入了文档过滥的泥潭,直到有人认识到最好使用 Javadoc 来理清容易混淆的类或者方法。无论用 Javadoc 做了多少文章,也没有人会去看那些易于理解的 getXXX()setXXX() 方法。

注 释也可能出现同样的趋势,虽然不一定到那种程度。经常甚至频繁地使用标准注释类型是一种较好的做法。所有的 Java 5 编译器都支持它们,它们的行为也很容易理解。但是,如果要使用定制注释和元注释,那么就很难保证花费很大力气创建的那些类型在您的开发环境之外还有什么意 义。因此要慎重。在合理的情况下使用注释,不要荒谬使用。无论如何,注释都是一种很好的工具,可以在开发过程中提供真正的帮助。



回页首


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 不要遗漏“ Tiger 中的注释,第 2 部分”,即本系列文章的第 2 部分,研究了定制注释。



  • 开放源代码 XDoclet代码生成引擎支持面向属性的 Java 语言编程。



  • JSR 175,将元数据工具合并到 Java 语言中的规范,处于 Java Community Process 的提议最终草案状态。



  • 访问 Sun 的主页,获取 J2SE 5.0 的所有信息



  • 可以 下载 Tiger并自己试用。



  • John Zukowski 的系列文章 Taming Tiger 以实用的基于技巧的形式讲述了 Java 5.0 的新功能。



  • 由 Brett McLaughlin 和 David Flanagan 撰写的 Java 1.5 Tiger: A Developer's Notebook 一书 (O'Reilly & Associates; 2004),以代码为中心、开发人员友好的形式,讲述了几乎所有的 Tiger 的最新功能 — 包括注释。



  • developerWorksJava 技术专区 可以找到数百篇有关 Java 技术的参考资料。



  • 访问 Developer Bookstore,获得技术书籍的完整列表,其中包括数百本 Java 相关主题的书籍。



  • 是否对无需通常的高成本入口点(entry point)或短期评估许可证的 IBM 测试产品感兴趣? developerWorks Subscription为 WebSphere?、DB2?、Lotus?、Rational? 和 Tivoli? 产品提供了低成本的 12 个月单用户许可证,包括基于 Eclipse 的 WebSphere Studio? IDE,用于开发、测试、评估和展示您的应用程序。




回页首


关于作者

作者照片

Brett McLaughlin 从 Logo 时代(还记得那个小三角吗?)就开始从事计算机工作。在最近几年里,他已经成为 Java 和 XML 社区最知名的作者和程序员之一。他曾经在 Nextel Communications 实现复杂的企业系统,在 Lutris Technologies 编写应用程序服务器,目前在为 O'Reilly Media, Inc 撰写和编辑 书籍

posted @ 2005-12-13 23:28 Dion 阅读(826) | 评论 (0)编辑 收藏

Tiger 中的注释,第 1 部分: 向 Java 代码中添加元数据

如何使用 Java 5 的内置注释

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


对此页的评价

帮助我们改进这些内容


级别: 初级

Brett McLaughlin, 作者/编者, O'Reilly Media, Inc

2004 年 9 月 01 日

注 释,J2SE 5.0 (Tiger) 中的新功能,将非常需要的元数据工具引入核心 Java 语言。该系列文章分为两部分,在这第 1 部分中,作者 Brett McLaughlin 解释了元数据如此有用的原因,向您介绍了 Java 语言中的注释,并研究了 Tiger 的内置注释。

编程的一个最新的趋势,尤其是在 Java 编程方面,是使用 元数据。简单地说,元数据就是 关于数据的数据。元数据可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。许多元数据工具,如 XDoclet(请参阅 参考资料),将这些功能添加到核心 Java 语言中,暂时成为 Java 编程功能的一部分。

直到可以使用 J2SE 5.0(也叫做 Tiger,现在是第二个 beta 版本),核心 Java 语言才最接近具有 Javadoc 方法的元数据工具。您使用特殊的标签集合来标记代码,并执行 javadoc 命令来将这些标签转化成格式化的 HTML 页面,该页面说明标签所附加到的类。然而,Javadoc 是有缺陷的元数据工具,因为除了生成文档之外,您没有固定、实用、标准化的方式来将数据用于其他用途。HTML 代码经常混入到 Javadoc 输出中这一事实甚至更进一步降低了其用于任何其他目的的价值。

Tiger 通过名为 注释的新功能将一个更通用的元数据工 具合并到核心 Java 语言中。注释是可以添加到代码中的修饰符,可以用于包声明、类型声明、构造函数、方法、字段、参数和变量。Tiger 包含内置注释,还支持您自己编写的定制注释。本文将概述元数据的优点并向您介绍 Tiger 的内置注释。本系列文章的 第 2 部分将研究定制注释。我要感谢 O'Reilly Media, Inc.,他们非常慷慨地 允许我在本文中使用我关于 Tiger 的书籍的“注释”一章中的代码示例(请参阅 参考资料)。

元数据的价值


一 般来说,元数据的好处分为三类:文档编制、编译器检查和代码分析。代码级文档最常被引用。元数据提供了一种有用的方法来指明方法是否取决于其他方法,它们 是否完整,特定类是否必须引用其他类,等等。这确实非常有用,但对于将元数据添加到 Java 语言中来说,文档编制可能是 最不相关的理由。Javadoc 已经提供了非常容易理解和健壮的方法来文档化代码。另外,当已经存在文档编制工具,并且在大多数时候都工作得很好时,谁还要编写文档编制工具?
不要漏掉本系列的另一部分

一定要阅读本系列的“ 第 2 部分”,该部分研究了定制注释。

编译器检查


元数据更重要的优点是编译器可以使用它来执行基本的编译时检查。例如,您将在本文后面的 Override 注释中 看到 Tiger 引入了一个这样的注释,用于允许您指定一种方法覆盖超类中的另一种方法。Java 编译器可以确保在元数据中指明的行为实际发生在代码级别。如果从来没有找出过这种类型的 bug,这样做似乎有点傻,但是大多数年龄很大的 Java 编程老手都曾经花费至少多个晚上来查明他们的代码为什么不能用。当最后认识到方法的参数有错,且该方法实际上 没有 覆盖超类中的方法时,您可能更感到难受。使用元数据的工具有助于轻松地查明这种类型的错误,从而可以节省那些晚上来看长期进行的 Halo 联赛。
JSR 175

JSR 175, Java 编程语言的元数据工具,为将元数据合并到核心 Java 语言中提供了正式理由和说明(请参阅 参考资料)。根据 JSR,注释“不直接影响程序的语义。然而,开发和部署工具可以读取这些注释,并以某种形式处理这些注释,可能生成其他 Java 编程语言源文件、XML 文档或要与包含注释的程序一起使用的其他构件。”

代码分析


可 以证明,任何好的注释或元数据工具的最好功能就是可以使用额外数据来分析代码。在一个简单的案例中,您可能构建代码目录,提供必需的输入类型并指明返回类 型。但是,您可能想,Java 反射具有相同的优点;毕竟,可以为所有这些信息内省代码。这从表面上看似乎是正确的,但是在实际中通常不使用。许多时候,方法作为输入接受的或者作为输出 返回的类型实际上不是该方法想要的类型。例如,参数类型可能是 Object ,但方法可能仅使用 Integer 。这在好些情况下很容易发生,比如在方法被覆盖而超类使用常规参数声明方法时,还有正在进行许多序列化的系统中也容易发生。在这两种情况中,元数据可以指示代码分析工具,虽然参数类型是 Object ,但 Integer 才是真正需要的。这类分析非常有用,但也不能夸大它的价值。

在 更复杂的情况下,代码分析工具可以执行所有种类的额外任务。示例 du jour 是 Enterprise JavaBean (EJB) 组件。甚至简单 EJB 系统中的依赖性和复杂性都非常令人吃惊。您具有了 home 接口和远程接口,以及本地接口和本地 home 接口,还有一个实现类。保持所有这些类同步非常困难。但是,元数据可以提供这个问题的解决放案。好的工具(还是要提一下 XDoclet)可以管理所有这些依赖性,并确保无“代码级”连接、但有“逻辑级”捆绑的类保持同步。元数据在这里确实可以发挥它的作用。



回页首


注释的基本知识


现在已经了解了元数据的好处,我将介绍 Tiger 中的注释。注释采用“at”标记形式 ( @ ),后面是注释名称。然后在需要数据时,通过 name=value 对向注释提供数据。每次使用这类表示法时,就是在生成注释。一段代码可能会有 10 个、50 个或更多的注释。不过,您将发现多个注释都可能使用相同的 注释类型。类型是实际使用的结构,在特定上下文中,注释本身是该类型的具体使用(请参阅侧栏 注释或注释类型?)。
注释或注释类型?

是否对什么是注释与什么是注释类型感到迷惑?了解这个的最简单方法就是对比所熟悉的 Java 语言概念来想。可以定义一个类(例如 Person ),则在 JVM 中将总是仅有该类的一个版本(假设没有进行麻烦的类路径设置)。然而,在任何给定时间,可能会使用该类的 10 个或 20 个 实例。仍然是只有一个 Person 类,但是它以不同的方式使用多次。注释类型和注释也是这样。注释类型类似于类,注释类似于该类的实例。

注释分为三个基本种类:

  • 标记注释没有变量。注释显示简单,由名称标识,没有提供其他数据。例如, @MarkerAnnotation 是标记注释。它不包含数据,仅有注释名称。

  • 单一值注释与标记注释类似,但提供一段数据。因为仅提供很少的一点数据,所以可以使用快捷语法(假设注释类型接受此语法): @SingleValueAnnotation("my data") 。除了 @ 标记外,这应该与普通的 Java 方法调用很像。

  • 完整注释有多个数据成员。因此,必须使用更完整的语法(注释不再像普通的 Java 方法): @FullAnnotation(var1="data value 1", var2="data value 2", var3="data value 3")

除了通过默认语法向注释提供值外,还可以在需要传送多个值时使用名称-值对。还可以通过花括号为注释变量提供值数组。清单 1 显示了注释中的值数组的示例。

清单 1. 在注释中使用按数组排列的值

@TODOItems({ // Curly braces indicate an array of values is being supplied
@TODO(
severity=TODO.CRITICAL,
item="Add functionality to calculate the mean of the student's grades",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.IMPOTANT,
item="Print usage message to screen if no command-line flags specified",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.LOW,
item="Roll a new website page with this class's new features",
assignedTo="Jason Hunter"
)
})

清单 1 中的示例并没有乍一看那样复杂。 TODOItems 注释类型有一个具有值的变量。这里提供的值比较复杂,但 TODOItems 的使用实际与单一值注释类型相符,只是这里的单一值是数组而已。该数组包含三个 TODO 注释,其中每个注释都是多值的。逗号分隔每个注释内的值,以及单个数组内的值。非常容易,是吧?

但是我讲的可能超前了些。 TODOItemsTODO定制注释, 是本系列文章第 2 部分中的主题。但是我想让您看到,即使复杂注释(清单 1 几乎是最复杂的注释)也不是非常令人害怕的。当提到 Java 语言的标准注释类型时,将很少看到如此复杂的情况。正如将在下面三个部分了解到的,Tiger 的基本注释类型的使用都极其简单。



回页首


Override 注释


Tiger 的第一个内置注释类型是 OverrideOverride 应该仅用于方法(不用于类、包声明或其他构造)。它指明注释的方法将覆盖超类中的方法。清单 2 显示了简单的示例。 清单 2. 操作中的 Override 注释

package com.oreilly.tiger.ch06;

public class OverrideTester {

public OverrideTester() { }

@Override
public String toString() {
return super.toString() + " [Override Tester Implementation]";
}

@Override
public int hashCode() {
return toString().hashCode();
}
}

清单 2 应该很容易理解。 @Override 注释对两个方法进行了注释 — toString()hashCode() ,来指明它们覆盖 OverrideTester 类的超类 ( java.lang.Object ) 中的方法的版本。开始这可能看起来没什么作用,但它实际上是非常好的功能。如果不覆盖这些方法,根本 无法 编译此类。该注释还确保当您将 toString() 弄乱时,至少还有某种指示,即应该确保 hashCode() 仍旧匹配。

当编码到很晚且输错了某些东西时,此注释类型真的可以发挥很大的作用,如清单 3 中所示。

清单 3. 使 Override 注释捕获打字稿


package com.oreilly.tiger.ch06;

public class OverrideTester {

public OverrideTester() { }

@Override
public String toString() {
return super.toString() + " [Override Tester Implementation]";
}

@Override
public int hasCode() {
return toString().hashCode();
}
}

在清单 3 中, hashCode() 错误地输入为 hasCode() 。注释指明 hasCode() 应该覆盖方法。但是在编译中, javac 将发现超类 ( java.lang.Object ) 没有名为 hasCode() 的方法可以覆盖。因此,编译器将报错,如图 1 中所示。

图 1. 来自 Override 注释的编译器警告

缺少的功能

在单一值注释类型中,如果 Deprecated 允许包含错误类型消息将更好。然后,当用户使用声明为过时的方法时,编译器可以打印消息。该消息可以指明使用方法的结果如何重要,说明何时将停止方法,甚至建议备用方法。可能 J2SE 的下一版本(“Mustang”,他们这样命名)将提供这种功能。

这个便捷的小功能将帮助快速捕获打字稿。



回页首


Deprecated 注释


下一个标准注释类型是 Deprecated 。与 Override 一样, Deprecated 是标记注释。正如您可能期望的,可以使用 Deprecated 来对不应再使用的方法进行注释。与 Override 不一样的是, Deprecated 应该与正在声明为过时的方法放在同一行中(为什么这样?说实话我也不知道),如清单 4 中所示。 清单 4. 使用 Deprecated 注释

package com.oreilly.tiger.ch06;

public class DeprecatedClass {

@Deprecated public void doSomething() {
// some code
}

public void doSomethingElse() {
// This method presumably does what doSomething() does, but better
}
}

单独编译此类时,不会发生任何不同。但是如果通过覆盖或调用来使用声明为过时的方法,编译器将处理注释,发现不应该使用该方法,并发出错误消息,如图 2 中所示。

图 2. 来自 Deprecated 注释的编译器警告

注意需要开启编译器警告,就像是必须向 Java 编译器指明想要普通的声明为过时警告。可以使用下列两个标记之一和 javac 命令: -deprecated 或新的 -Xlint:deprecated 标记。



回页首


SuppressWarnings 注释


从 Tiger “免费”获得的最后一个注释类型是 SuppressWarnings 。发现该类型的作用应该不难,但是 为什么该注释类型如此重要通常不是很明显。它实际上是 Tiger 的所有新功能的副功能。例如,以泛型为例;泛型使所有种类的新类型安全操作成为可能,特别是当涉及 Java 集合时。然而,因为泛型,当使用集合而 没有 类型安全时,编译器将抛出警告。这对于针对 Tiger 的代码有帮助,但它使得为 Java 1.4.x 或更早版本编写代码非常麻烦。将不断地收到关于根本无关的事情的警告。如何才能使编译器不给您增添麻烦?

SupressWarnings 可以解决这个问题。 SupressWarningsOverrideDeprecated 不同, 具有变量的 — 所以您将单一注释类型与该变量一起使用。可以以值数组来提供变量,其中每个值指明要阻止的一种特定警告类型。请看清单 5 中的示例,这是 Tiger 中通常会产生错误的一些代码。

清单 5. 不是类型安全的 Tiger 代码

public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List

wordList.add("foo"); // causes error on list addition
}

图 3 显示了清单 5 中代码的编译结果。

图 3. 来自非标准代码的编译器警告

清单 6 通过使用 SuppressWarnings 注释消除了这种问题。

清单 6. 阻止警告

@SuppressWarings(value={"unchecked"})
public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List

wordList.add("foo"); // causes error on list addition
}

非常简单,是吧?仅需要找到警告类型(图 3 中显示为“unchecked”),并将其传送到 SuppressWarnings 中。

SuppressWarnings 中变量的值采用数组,使您可以在同一注释中阻止多个警告。例如, @SuppressWarnings(value={"unchecked", "fallthrough"}) 使用两个值的数组。此功能为处理错误提供了非常灵活的方法,无需进行大量的工作。



回页首


结束语

虽 然这里看到的语法可能都是新的,但您应该知道注释非常容易理解和使用。也就是说,与 Tiger 一起提供的标准注释相当简单,可以添加许多功能。元数据正日益变得有帮助,您肯定会提出非常适用于自己的应用程序的注释类型。在本系列文章的第 2 部分,我将详细说明 Tiger 对编写自己的注释类型的支持。您将了解如何创建 Java 类以及将其定义为注释类型,如何使编译器识别您的注释类型,以及如何使用该类型对代码进行注释。我甚至会更深入地说明奇异但有用的对注释进行注释的任务。 您将快速熟悉 Tiger 中的这一新构造。



回页首


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 不要遗漏“ Tiger 中的注释,第 2 部分”,即本系列文章的第 2 部分,研究了定制注释。



  • 开放源代码 XDoclet代码生成引擎支持面向属性的 Java 语言编程。



  • JSR 175,将元数据工具合并到 Java 语言中的规范,处于 Java Community Process 的提议最终草案状态。



  • 访问 Sun 的主页,获取 J2SE 5.0 的所有信息



  • 可以 下载 Tiger并自己试用。



  • John Zukowski 的系列文章 Taming Tiger 以实用的基于技巧的形式讲述了 Java 5.0 的新功能。



  • 由 Brett McLaughlin 和 David Flanagan 撰写的 Java 1.5 Tiger: A Developer's Notebook 一书 (O'Reilly & Associates; 2004),以代码为中心、开发人员友好的形式,讲述了几乎所有的 Tiger 的最新功能 — 包括注释。



  • developerWorksJava 技术专区 可以找到数百篇有关 Java 技术的参考资料。



  • 访问 Developer Bookstore,获得技术书籍的完整列表,其中包括数百本 Java 相关主题的书籍。



  • 是否对无需通常的高成本入口点(entry point)或短期评估许可证的 IBM 测试产品感兴趣? developerWorks Subscription为 WebSphere?、DB2?、Lotus?、Rational? 和 Tivoli? 产品提供了低成本的 12 个月单用户许可证,包括基于 Eclipse 的 WebSphere Studio? IDE,用于开发、测试、评估和展示您的应用程序。




回页首


关于作者

作者照片

Brett McLaughlin 从 Logo 时代(还记得那个小三角吗?)就开始从事计算机工作。在最近几年里,他已经成为 Java 和 XML 社区最知名的作者和程序员之一。他曾经在 Nextel Communications 实现复杂的企业系统,在 Lutris Technologies 编写应用程序服务器,目前在为 O'Reilly Media, Inc 撰写和编辑 书籍

posted @ 2005-12-13 23:26 Dion 阅读(899) | 评论 (0)编辑 收藏

Java Annotation入门

作者:cleverpig





版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:cleverpig(作者的Blog:http://blog.matrix.org.cn/page/cleverpig)
原 文:[http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html]http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html[/url]
关键字:Java,annotation,标注


摘要:
本 文针对java初学者或者annotation初次使用者全面地说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简 单的annotation程序,但是对于一些高级的annotation应用(例如使用自定义annotation生成javabean映射xml文件) 还需要进一步的研究和探讨。涉及到深入annotation的内容,作者将在后文《Java Annotation高级应用》中谈到。

同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,

一、为什么使用Annotation:

在JAVA应用中,我们常遇到一些需要使用模版代码。例如,为了编写一个JAX-RPC web service,我们必须提供一对接口和实现作为模版代码。如果使用annotation对远程访问的方法代码进行修饰的话,这个模版就能够使用工具自动生成。
另 外,一些API需要使用与程序代码同时维护的附属文件。例如,JavaBeans需要一个BeanInfo Class与一个Bean同时使用/维护,而EJB则同样需要一个部署描述符。此时在程序中使用annotation来维护这些附属文件的信息将十分便利 而且减少了错误。

二、Annotation工作方式:

在5.0 版之前的Java平台已经具有了一些ad hoc annotation机制。比如,使用transient修饰符来标识一个成员变量在序列化子系统中应被忽略。而@deprecated这个 javadoc tag也是一个ad hoc annotation用来说明一个方法已过时。从Java5.0版发布以来,5.0平台提供了一个正式的annotation功能:允许开发者定义、使用 自己的annoatation类型。此功能由一个定义annotation类型的语法和一个描述annotation声明的语法,读取annotaion 的API,一个使用annotation修饰的class文件,一个annotation处理工具(apt)组成。
annotation并不直接影响代码语义,但是它能够工作的方式被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响。annotation可以从源文件、class文件或者以在运行时反射的多种方式被读取。
当然annotation在某种程度上使javadoc tag更加完整。一般情况下,如果这个标记对java文档产生影响或者用于生成java文档的话,它应该作为一个javadoc tag;否则将作为一个annotation。

三、Annotation使用方法:

1。类型声明方式:
通常,应用程序并不是必须定义annotation类型,但是定义annotation类型并非难事。Annotation类型声明于一般的接口声明极为类似,区别只在于它在interface关键字前面使用“@”符号。
annotation 类型的每个方法声明定义了一个annotation类型成员,但方法声明不必有参数或者异常声明;方法返回值的类型被限制在以下的范围: primitives、String、Class、enums、annotation和前面类型的数组;方法可以有默认值。

下面是一个简单的annotation类型声明:
清单1:

    /**
     * Describes the Request-For-Enhancement(RFE) that led
     * to the presence of the annotated API element.
     */
    public @interface RequestForEnhancement {
        int    id();
        String synopsis();
        String engineer() default "[unassigned]";
        String date();    default "[unimplemented]";
    }

代码中只定义了一个annotation类型RequestForEnhancement。

2。修饰方法的annotation声明方式:
annotation 是一种修饰符,能够如其它修饰符(如public、static、final)一般使用。习惯用法是annotaions用在其它的修饰符前面。 annotations由“@+annotation类型+带有括号的成员-值列表”组成。这些成员的值必须是编译时常量(即在运行时不变)。

A:下面是一个使用了RequestForEnhancement annotation的方法声明:
清单2:

    @RequestForEnhancement(
        id       = 2868724,
        synopsis = "Enable time-travel",
        engineer = "Mr. Peabody",
        date     = "4/1/3007"
    )
    public static void travelThroughTime(Date destination) { ... }


B:当声明一个没有成员的annotation类型声明时,可使用以下方式:
清单3:

    /**
     * Indicates that the specification of the annotated API element
     * is preliminary and subject to change.
     */
    public @interface Preliminary { }


作为上面没有成员的annotation类型声明的简写方式:
清单4:

    @Preliminary public class TimeTravel { ... }


C:如果在annotations中只有唯一一个成员,则该成员应命名为value:
清单5:

    /**
     * Associates a copyright notice with the annotated API element.
     */
    public @interface Copyright {
        String value();
    }


更为方便的是对于具有唯一成员且成员名为value的annotation(如上文),在其使用时可以忽略掉成员名和赋值号(=):
清单6:

    @Copyright("2002 Yoyodyne Propulsion Systems")
    public class OscillationOverthruster { ... }


3。一个使用实例:
结合上面所讲的,我们在这里建立一个简单的基于annotation测试框架。首先我们需要一个annotation类型来表示某个方法是一个应该被测试工具运行的测试方法。
清单7:

    import java.lang.annotation.*;

    /**
     * Indicates that the annotated method is a test method.
     * This annotation should be used only on parameterless static methods.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test { }


值得注意的是annotaion类型声明是可以标注自己的,这样的annotation被称为“meta-annotations”。

在 上面的代码中,@Retention(RetentionPolicy.RUNTIME)这个meta-annotation表示了此类型的 annotation将被虚拟机保留使其能够在运行时通过反射被读取。而@Target(ElementType.METHOD)表示此类型的 annotation只能用于修饰方法声明。

下面是一个简单的程序,其中部分方法被上面的annotation所标注:
清单8:

    public class Foo {
        @Test public static void m1() { }
        public static void m2() { }
        @Test public static void m3() {
            throw new RuntimeException("Boom");
        }
        public static void m4() { }
        @Test public static void m5() { }
        public static void m6() { }
        @Test public static void m7() {
            throw new RuntimeException("Crash");
        }
        public static void m8() { }
    }

Here is the testing tool:

    import java.lang.reflect.*;

    public class RunTests {
       public static void main(String[] args) throws Exception {
          int passed = 0, failed = 0;
          for (Method m : Class.forName(args[0]).getMethods()) {
             if (m.isAnnotationPresent(Test.class)) {
                try {
                   m.invoke(null);
                   passed++;
                } catch (Throwable ex) {
                   System.out.printf("Test %s failed: %s %n", m, ex.getCause());
                   failed++;
                }
             }
          }
          System.out.printf("Passed: %d, Failed %d%n", passed, failed);
       }
    }


这 个程序从命令行参数中取出类名,并且遍历此类的所有方法,尝试调用其中被上面的测试annotation类型标注过的方法。在此过程中为了找出哪些方法被 annotation类型标注过,需要使用反射的方式执行此查询。如果在调用方法时抛出异常,此方法被认为已经失败,并打印一个失败报告。最后,打印运行 通过/失败的方法数量。
下面文字表示了如何运行这个基于annotation的测试工具:

清单9:

    $ java RunTests Foo
    Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
    Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
    Passed: 2, Failed 2


四、Annotation分类:

根据annotation的使用方法和用途主要分为以下几类:

1。内建Annotation——Java5.0版在java语法中经常用到的内建Annotation:
@Deprecated用于修饰已经过时的方法;
@Override用于修饰此方法覆盖了父类的方法(而非重载);
@SuppressWarnings用于通知java编译器禁止特定的编译警告。

下面代码展示了内建Annotation类型的用法:
清单10:

package com.bjinfotech.practice.annotation;

/**
* 演示如何使用java5内建的annotation
* 参考资料:
* http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
* http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
* http://mindprod.com/jgloss/annotations.html
* @author cleverpig
*
*/
import java.util.List;

public class UsingBuiltInAnnotation {
        //食物类
        class Food{}
        //干草类
        class Hay extends Food{}
        //动物类
        class Animal{
                Food getFood(){
                        return null;
                }
                //使用Annotation声明Deprecated方法
                @Deprecated
                void deprecatedMethod(){
                }
        }
        //马类-继承动物类
        class Horse extends Animal{
                //使用Annotation声明覆盖方法
                @Override
                Hay getFood(){
                        return new Hay();
                }
                //使用Annotation声明禁止警告
                @SuppressWarnings({"deprecation","unchecked"})
                void callDeprecatedMethod(List horseGroup){
                        Animal an=new Animal();
                        an.deprecatedMethod();
                        horseGroup.add(an);
                }
        }
}


2。开发者自定义Annotation:由开发者自定义Annotation类型。
下面是一个使用annotation进行方法测试的sample:

AnnotationDefineForTestFunction类型定义如下:
清单11:

package com.bjinfotech.practice.annotation;

import java.lang.annotation.*;
/**
* 定义annotation
* @author cleverpig
*
*/
//加载在VM中,在运行时进行映射
@Retention(RetentionPolicy.RUNTIME)
//限定此annotation只能标示方法
@Target(ElementType.METHOD)
public @interface AnnotationDefineForTestFunction{}


测试annotation的代码如下:

清单12:

package com.bjinfotech.practice.annotation;

import java.lang.reflect.*;

/**
* 一个实例程序应用前面定义的Annotation:AnnotationDefineForTestFunction
* @author cleverpig
*
*/
public class UsingAnnotation {
        @AnnotationDefineForTestFunction public static void method01(){}
        
        public static void method02(){}
        
        @AnnotationDefineForTestFunction public static void method03(){
                throw new RuntimeException("method03");
        }
        
        public static void method04(){
                throw new RuntimeException("method04");
        }
        
        public static void main(String[] argv) throws Exception{
                int passed = 0, failed = 0;
                //被检测的类名
                String className="com.bjinfotech.practice.annotation.UsingAnnotation";
                //逐个检查此类的方法,当其方法使用annotation声明时调用此方法
            for (Method m : Class.forName(className).getMethods()) {
               if (m.isAnnotationPresent(AnnotationDefineForTestFunction.class)) {
                  try {
                     m.invoke(null);
                     passed++;
                  } catch (Throwable ex) {
                     System.out.printf("测试 %s 失败: %s %n", m, ex.getCause());
                     failed++;
                  }
               }
            }
            System.out.printf("测试结果: 通过: %d, 失败: %d%n", passed, failed);
        }
}


3。使用第三方开发的Annotation类型
这也是开发人员所常常用到的一种方式。比如我们在使用Hibernate3.0时就可以利用Annotation生成数据表映射配置文件,而不必使用Xdoclet。

五、总结:

1。 前面的文字说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简单的annotation程序,但是对于一些高级的 annotation应用(例如使用自定义annotation生成javabean映射xml文件)还需要进一步的研究和探讨。

2。同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,因为编译时的annotation要使用annotation processing tool。

涉及以上2方面的深入内容,作者将在后文《Java Annotation高级应用》中谈到。

六、参考资源:
·Matrix-Java开发者社区:http://www.matrix.org.cn
·http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·作者的Blog:http://blog.matrix.org.cn/page/cleverpig
posted @ 2005-12-13 23:22 Dion 阅读(58716) | 评论 (39)编辑 收藏

扩展 Spring 的 JMX 支持

用 Spring 框架定制资源管理

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

讨论

样例代码


对此页的评价

帮助我们改进这些内容


级别: 中级

Claude Duguay , 高级 J2EE 架构师, Capital Stream Inc.

2005 年 11 月 24 日

Spring 框架将体系结构依赖性降至最低,并且将应用程序中的组成部分进行了具体化,但是应用程序仍然是需要管理的。幸运的是,Spring 1.2 包括高级的 JMX 集成支持,并且 JMX 为应用程序提供了一种实用的管理基础架构。在本文中,Claude Duguay 从 Spring JMX 更进一步,向您展示了如何为方法和属性透明地增加通知事件。最后得到的代码使您可以监视状态变化,同时不会搞乱 Java™ 对象。

虽 然 Spring 框架的 JMX 管理基础架构的默认配置已经很不错了,但是仍然有定制的余地,特别是涉及 Model MBean 提供的更高层功能时。在本文中,我使用了一种相对简单的操作 —— 为基于 Spring 的应用程序的方法和属性增加通知事件 —— 以帮助您熟悉对 Spring JMX 的定制。从头到尾完成我的例子后,您将可以根据自己应用程序的需要调整 Spring JMX 管理基础架构。

我首 先对 JMX API、Spring 框架和 Spring JMX 进行简单回顾,然后转入开发扩展。第一个扩展让我可以用一个外部 XML 格式配置 MBean 元数据,这个格式(像 Hibernate 映射文件)可以与 Java 对象一起存储在类路径中。我的第二个扩展为 ModelMBean 类增加一个简单的命名规范,以透明地配置定制的通知消息。在属性改变时或者调用了特定的方法之前或者之后触发新的通知消息。

文章的最后是一个基于 mockup 服务对象的实际例子,需要管理它的启动和停止方法和读写属性。我用一个专门为此设计的小型客户机/服务器应用程序测试了这个实现。应用服务器是一个标准 Java 5.0 MBeanServer,并补充了源自 MX4J 开放源码项目的 HTTP 适配器。关于文章源代码请参阅 下载,而技术下载请参阅 参考资料

JMX 概述

Java Management Extensions(JMX)是管理和监视网络上的服务的、基于 Java 的标准。JMX API 的核心是受管 bean,即 MBean。MBean 为受管资源(如应用程序、服务和设备)提供了设施层。简而言之,MBean 提供了一种灵活的、基于适配器的体系结构,用于开放基于 Java 的(或者 Java 包装的)资源的属性和操作。开放后,就可以用浏览器和 HTTP 连接或者通过像 SMTP 或者 SOAP 这样的协议监视和管理这些资源。

编写和部署的 MBean 是通过 MBeanServer 接口开放的,以使不同的应用程序视图具有交互性。MBeanServer 实例还可以结合到任意的联合关系中,构成更复杂的分布式环境。

JMX 标准提供了四种不同的 MBean:

  • Standard MBean 直接实现用于管理对象的方法,既可以通过实现一个由程序员定义的、类名以 “MBean” 结束的接口,也可以使用一个以一个类作为构造函数参数的 Standard MBean 实例,加上一个可选的接口类规范。这个接口可以开放用于管理的部分对象方法。

  • Dynamic MBean 用属性访问器动态地访问属性,并用一个一般化的 invoke() 方法调用方法。可用的方法是在 MBeanInfo 接口中指定的。这种方式更灵活,但是不具有像 Standard MBean 那样的类型安全性。它极大地降低了耦合性,可管理的 POJO(纯粹的老式 Java 对象)不需要实现特定的接口。

  • Model MBean 提供了一个改进的抽象层,并扩展了 Dynamic MBean 模型以进一步减少对给定实现的依赖性。这对于可能使用多个版本的 JVM 或者需要用松散耦合管理第三方类的情况会有帮助。Dynamic MBean 与 Model MBean 之间的主要区别是,在 Model MBean 中有额外的元数据。

  • Open MBean 是受限的 Model MBean,它限制类型为固定的一组类型,以得到最大的可移植性。通过限制数据类型,可以使用更多的适配器,并且像 SMTP 这样的技术可以更容易适应 Java 应用程序的管理。这种变体还指定了数组和表等标准结构以改进复合对象的管理。

如 果要同时控制客户机和服务器,那么 Standard MBean 是最容易实现的一种变体。它们的优点是有类型,但是如果在更一般化的管理控制台环境中使用时会缺少一些灵活性。如果计划使用 Dynamic MBean,那么您也可以更一步使用 Model MBean,在大多数情况下它会改善抽象层而几乎不会增加复杂性。Open MBean 是是可移植性最高的一种变体,如果需要开放复合对象,那么它是惟一的方法。不幸的是,在 Open MBean 中开放复合结构所需要的代码数量过多,只有在需要高级的商业管理解决方案时才合算。

JMX 还支持使用带过滤器和广播器的事件模型的通知。为此目的,Standard MBean 需要声明一个 MBeanInfo 元数据描述。 Standard MBean 实现通常在内部构造这些内容,开发人员不能直接看到它们。在本文后面,您会看到如何用 Model MBean 元数据的 XML 描述符格式和 Spring 的 JMX 支持进行实际上透明的配置。



回页首


Spring 提供帮助

像 J2EE 一样,Spring 框架在一个体系结构中提供了许多强大的 Java 开发功能。与 J2EE 不同的是,Spring 开放型的技术来源提供了范围广泛的选择,不再有依赖性的负担。例如,Spring 的对象关系映射工具可以复制 Enterprise JavaBean 的行为,同时不会导致不灵活。虽然 EJB 规范限制了这种方式,但是 Spring 提供了大量技术接口,使您可以选择最适合应用程序要求的接口,或者在需要时创建自己的接口。与此类似,利用 Spring 的动态代理类为 Java 对象增加事务性或者安全限制,使它们保持整洁并针对应用程序空间而不是基础架构要求。

Spring 的支持 AOP 的、以复合为中心的(IOC)bean 可以很大程度上使基础架构和业务对象彼此分离。因此,横切关注点(如日志、事务和安全)不会再干扰应用程序代码。

IOC (控制反转)是减少耦合度的主要策略。Spring 的 IOC 实现使用依赖性注入有效地将控制从应用程序代码 “反转”到 Spring 容器。Spring 不是在创建时将类耦合到应用程序的对象图,它使您可以用 XML 或者属性文件(尽管 XML 被认为是最好的方法)配置类及它们的依赖性。然后用标准访问器将引用“注入”到类所依赖的对象中。可以将它看成具体化复合(externalizing composition),在典型应用程序中,它的比重远远大于继承。

AOP 是在应用程序开发中管理横切关注点的关键。就像在传统面向对象编程中实现的那样,这些关注点是作为单独的实例处理的,有可能在应用程序类中产生互不相关的代码(就是混乱)。 Spring 使用 AOP 规范和一个 XML 配置文件具体化横切关注点,因而保持了 Java 代码的纯洁性。



回页首


介绍 Spring JMX

Spring 1.2 中的 JMX 支持使用容易配置的 bean 代理提供了自动 MBeanServer 注册,并支持标准 JSR-160 远程连接器。在最简单的情况下,可以用 Spring JMX 以 MBeanExporter 类注册对象。Spring 自动识别 StandardMBean 或者用 ModelMBean 代理包装对象,在默认情况下使用内省。可以以显式引用使用 BeanExporter 以声明 bean,或者可以用默认策略或更复杂的策略自动检测 bean。

Spring 1.2 提供的大量装配器使得透明地构造 MBean 成为可能,包括使用内省、Standard MBean 接口、元数据(使用类级别注释)和显式声明的方法名。Spring 的基于导出器和装配器的模型容易扩展,并在创建注册的 MBean 时提供所需要的控制能力。

JMX 使用 ObjectName 语言注册和访问管理对象。如果选择使用自动注册,那么 Spring 提供了不同的命名策略。使用“键”命名策略时,可以使用一个属性把 MBean 名与 NameObject 实例关联起来。如果实现 ManagedResource 接口,那么可以使用元数据命名规范。由于 Spring 高度灵活的体系结构和大量扩展点,还可以实现自已的策略。

在默认情况下,Spring 会发现运行的 MBeanServer 实例,如果没有实例在运行或者没有显式声明的话,它会创建一个默认实例。用 Spring 配置直接实例化自己的 MBeanServer 与使用各种连接器同样容易。Spring 通过客户机和服务器连接提供控制,并提供客户机代理以协助客户端编程。

所 有这些功能都是 Spring 1.2 默认提供的。虽然 Spring JMX 提供了大量选项,但是默认的导出器对于许多项目来说已经足够了,使您可以很快地投入运行。不过,使用 JMX 时,在使用隐式 MBean 构造时会注意到一些特性。结果,可能会慢慢地从 Standard MBean 转移到 Model MBean,它允许对应用程序的属性、操作和通知元数据施加更多的控制。要保留松散耦合的好处(也就是 Spring 灵活的体系结构内在的优点),需要在 Java 对象之外实现这个控制。

Spring 的 IOC 使得从外部连接(wire)对象依赖性容易了,在 Spring 的体系结构中很容易利用这种优点。IOC 保持对象依赖性的可注入性,这使得增加、替换或者补充对象的行为(包括 Spring 的 JMX 支持)变得轻而易举。在本文的其余部分,我将重点放到扩展 Spring JMX 以得到更细化的应用程序管理,而不会搞乱应用程序代码或者破坏 Spring 固有的灵活性。



回页首


扩展 Spring JMX

Spring 框架提供了许多处理 JMX 的有用工具,包括用于扩展 JMX 功能的扩展点。我将利用它们获得对 MBeanInfo 声明的更多控制,同时不用对 Java 对象施加注释。为此,我将要以两种方式扩展 Spring JMX:第一种方法可以用一个外部 XML 格式(类似于 JBoss 微内核)配置 MBean 元数据,第二种方法可以与其相关联的 Java 对象一同存储在在类路径中(很像 Hibernate 映射文件)。

推荐的文章

Spring 1.2 的文档有整整一章关于 JMX 的内容,如果想要在 Spring 中使用 JMX 就应该阅读它。有关 Spring 1.2 文档的内容请参阅 参考资料

我将扩展 RequiredModelMBean 类,让它使用一个简单的命名规范,以 <ClassName>.mbean.xml 格式寻找相关的 MBean 部署描述符。定义这种基于 XML 的 MBean 描述符改进了对应用程序元数据的控制,而不会失去基于 POJO 的设计和 Spring 复合的灵活性。为了实现这一点,我将实现自己的装配器并扩展基本的 Spring JMX 导出器。扩展的导出器可以创建扩展的 ModelMBean,它支持截获属性的改变以及 beforeafter 方法执行的通知。我可以用 Spring 的 AOP 机制完成这一切,但是 ModelMBean 已经是底层受管资源的代理,因此我将用更直接的方式扩展 RequiredModelMbean 类。

管理基础

不管使用什么技术,在管理资源时有三个要关注的主要领域:

  • 属性(attribute) (有时称为属性(property)、字段或者变量)。只读属性对于开放度量或者状态很有用。读/写属性使管理员可以改变配置。

  • 动作(action) (可执行调用,对于 Java 代码来说就是方法)。动作用于触发像启动和关闭这样的事件,或者其他特定于应用程序的操作。

  • 事件(event) (向监视系统发出的通知,反映状态改变或者一些操作的执行)。通知确认操作或者状态改变确实发生了。通知还可以用于触发事件,如对于超过设置阀值(比如内 存或者磁盘空间等资源不足)的状态改变做出反应。这种通知可以用于在系统需要关注时向应用程序管理员发送电子邮件或者传呼。

在 扩展 Spring JMX 时,我将用一个简单的案例分别展示这三个需要关注的领域:一个带有开始和停止方法并且有一个读写属性要管理的示例服务。我还要用一个小型的客户机/服务器 应用程序测试这个实现,并开放一个使用 MX4J 适配器的 HTTP 管理接口。所有例子将有必要的限制,但是足以使您理解相应的概念。您会看到在基于 Spring 的应用程序方法和属性中加入 JMX 通知事件有多么容易,结果是可以监视状态改变,同时不会在 Java 对象中加入不必要的代码。

MBeanInfo 模型

如果下载与本文有关的代码,您会发现一个名为 com.claudeduguay.mbeans.model 的包,它包含一组用于建模 MBeanInfo XML 文件的类。这个包包含大量类,因此我不会详细讨论所有类,但是其基本内容还是有必要说明的。

模型的根是 MBeanDescriptor 类,它提供了一个 createMBeanInfo() 方法,负责用应用程序元数据创建一个兼容 JMX 的 ModelMBeanInfo 实例。MBeanDescriptorUtil 类提供了两个静态的 read() 方法,它装载并保存这个 XML 文档模型。这个模型中使用的 DocumentElement 实例基于 JDOM 框架。

我使用的基本 MBeanInfo 相关类和描述符模型是密切相关的。基类中的所有基本属性都是在 XML 中建模的,并可以用简单的格式定义。例如,<mbean> 标记是我的文档的根。它与 ModelMBeanInfo 直接相关,它期待一个 typedescription 属性。类型是受管 bean 的完全限定类名。不过,在使用我的 Sping 解决方案时,这个类完全可以在上下文中派生。<mbean> 标记期待零个或者多个 attributeoperationconstructornotification 子类型。它们每一个都提供了基 MBeanInfo 类 XML 属性。


图 1. MBean XML 格式
图 1. MBean XML 格式

MBean 模型实现利用了 com.claudeduguay.util.jdom 包中几个与 JDOM 相关的工具类。它们主要是解析和构建 DocumentElement 对象的接口,一个工具类使读和写 Document 流更容易。要查看的大多数代码在 com.claudeduguay.mbeans.spring 包中。

已经做了足够的预备工作,让我们开始扩展 Spring JMX!



回页首


改进 ModelMBean 中的通知

我要做的第一件事是扩展 Spring JMX ModelMBean 实现,这样就可以发送通知而不用在管理的资源中直接实现这个行为。为此,还需要扩展 Spring 导出器以创建改进的 ModelMBean 实例。最后,还需要用一个新的装配器从映射文件中提取 MBeanInfo 元数据。

ModelMBeanExtension 类

扩展 RequiredModelMBean 类的一个目的是在管理代理中透明地启用通知。这个应用程序需要三种通知:设置属性值、方法调用之前以及方法调用之后。因为消息是我自己配置的,在每一种情况下,它都可以按照我需要的那样提供信息。要实现这一点,我对类型通知使用了一个命名规范,其中对于样式 <matchingType>.<methodOrAttributeName> 检查点分隔的类型名。匹配的类型必须为 setbefore 或者 after 之一。如果类型是 set,那么就认为是一个属性名,否则,就认为是一个方法名。

扩展的 ModelMBean 代码使用额外的类帮助进行耦合。第一个是 NotificationInfoMap,它是一个用通知元数据构建的、简单的 Map,并与前缀(set|before|after)点名(method|attribute)样式相关联,这样就可以更有效地得到匹配的通知元数据。第二个是工具方法的一个静态集合。清单 1 显示了为通知而扩展的 RequiredModelMBean


清单 1. ModelMBeanExtension

package com.claudeduguay.mbeans.spring;

import java.lang.reflect.*;

import javax.management.*;
import javax.management.modelmbean.*;

public class ModelMBeanExtension extends RequiredModelMBean
{
protected NotificationInfoMap notificationInfoMap;
protected ModelMBeanInfo modelMBeanInfo;
protected Object managedBean;

public ModelMBeanExtension() throws MBeanException {}

public ModelMBeanExtension(ModelMBeanInfo modelMBeanInfo)
throws MBeanException
{
super(modelMBeanInfo);
this.modelMBeanInfo = modelMBeanInfo;
notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
}

public void setModelMBeanInfo(ModelMBeanInfo modelMBeanInfo)
throws MBeanException
{
this.modelMBeanInfo = modelMBeanInfo;
notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
super.setModelMBeanInfo(modelMBeanInfo);
}

public MBeanNotificationInfo[] getNotificationInfo()
{
return modelMBeanInfo.getNotifications();
}

public void setManagedResource(Object managedBean, String type)
throws MBeanException, RuntimeOperationsException,
InstanceNotFoundException, InvalidTargetObjectTypeException
{
super.setManagedResource(managedBean, type);
this.managedBean = managedBean;
}

protected void maybeSendMethodNotification(
String type, String name) throws MBeanException
{
MBeanNotificationInfo info = notificationInfoMap.
findNotificationInfo(type, name);
if (info != null)
{
long timeStamp = System.currentTimeMillis();
String notificationType = ModelMBeanUtil.
matchType(info, "." + type + "." + name);
sendNotification(new Notification(
notificationType, this, timeStamp,
info.getDescription()));
}
}

protected void maybeSendAttributeNotification(
Attribute attribute)
throws MBeanException, AttributeNotFoundException,
InvalidAttributeValueException, ReflectionException
{
String name = attribute.getName();
MBeanNotificationInfo info = notificationInfoMap.
findNotificationInfo("set", attribute.getName());
if (info != null)
{
Object oldValue = getAttribute(name);
Object newValue = attribute.getValue();
long timeStamp = System.currentTimeMillis();
String notificationType = ModelMBeanUtil.
matchType(info, ".set." + name);
sendNotification(new AttributeChangeNotification(
this, timeStamp, timeStamp,
info.getDescription(), info.getName(),
notificationType, oldValue, newValue));
}
}

public Object invoke(
String name, Object[] args, String[] signature)
throws MBeanException, ReflectionException
{
maybeSendMethodNotification("before", name);
Object returnValue = super.invoke(name, args, signature);
maybeSendMethodNotification("after", name);
return returnValue;
}

public Object getAttribute(String name) throws MBeanException,
AttributeNotFoundException, ReflectionException
{
try
{
Method method = ModelMBeanUtil.findGetMethod(
modelMBeanInfo, managedBean, name);
return method.invoke(managedBean, new Object[] {});
}
catch (IllegalAccessException e)
{
throw new MBeanException(e);
}
catch (InvocationTargetException e)
{
throw new MBeanException(e);
}
}

public void setAttribute(Attribute attribute)
throws MBeanException, AttributeNotFoundException,
InvalidAttributeValueException, ReflectionException
{
try
{
Method method = ModelMBeanUtil.findSetMethod(
modelMBeanInfo, managedBean, attribute.getName());
method.invoke(managedBean, attribute.getValue());
maybeSendAttributeNotification(attribute);
}
catch (InvocationTargetException e)
{
throw new MBeanException(e);
}
catch (IllegalAccessException e)
{
throw new MBeanException(e);
}
}
}

不需要代理代理!

因为 ModelMBean 已经是一种代理,所以不需要使用 Spring 的代理机制和 AOP 截获器来截获感兴趣的方法。ModelMBean 接口需要 setAttributeinvoke 方法的实现以管理对底层受管资源的调用。可以继承 RequiredModelMBean 类,保证它出现在所有 JMX 实现中,并增加我所需要的功能。

我的 ModelMBeanExtension 实现了同样的构造函数,但是在一个实例变量中存储了 ModelMBeanInfo 的一个副本。因为这个值可以通过构造函数或者调用 setModelMBeanInfo 方法设置,所以我覆盖了这个方法以存储这个值,调用超类以完成默认的行为。在默认情况下,RequiredModelMBean 类增加两个一般性通知描述符,因此我覆盖了 getNotificationInfo() 方法,只返回我描述的通知。仍然会发送一般性通知,但是要求特定通知的客户不会看到它们。

为了发送通知,我覆盖了 setAttribute()invoke() 方法并检查调用是否匹配我的通知信息描述符。每次都遍历列表应该不会带来很大的开销,因为大多数类只会发送有限的一组通知,但是我需要测试每一个通知可能的许多通知类型字符串,而重复这一过程看来是个浪费。为了保证不会遇到性能问题,我实例化了一个通知信息映射,这是一个名称/信息映射,可以用来进行快速查询。关键是一个具有类型前缀(setbefore 或者 after)和所涉及的属性和方法的简单字符串。可以使用 findNotificationInfo() 方法在 setAttribute() 调用或者方法调用时查找通知信息实例。

完成了基础架构后,就可以截获对 setAttribute()invoke() 方法的调用了。属性改变需要发送一个 AttributeChangeNotification 实例,它要求有旧的属性值和新的值,以及从通知信息描述符中可以得到的细节。在发送通知时,如果消息顺序是混乱的,则要发送序列号,让客户机应用程序可以对消息排序。为了简化,我使用了当前时间戳而不是管理一个计数器。创建通知对象时,sendNotification() 方法保证它会发布。对于 invoke() 方法使用同样的思路,尽管在这里我使用了更简单的 Notification 对象。可以调用超类中的 invoke() 方法同时检查这两者(beforeafter),并根据查找结果发送 beforeafter 通知。



回页首


扩展 Spring JMX 导出器

为了使用扩展的 ModelMBean,需要覆盖 Spring MBeanExporter 中的 createModelMBean() 方法。因为可以注入装配器属性,所以必须知道它可能不是我所期待的这一事实。可以在构造函数中设置所需要的装配器,但是当装配器改变时需要返回一个普通 ModelMBean。所要做的就是缓存一个 MBeanInfoAssembler 的本地引用,并在创建新的 ModelMBean 时检查它是什么类型的。清单 2 显示了所有这些改变:


清单 2. MBeanDescriptorEnabledExporter

package com.claudeduguay.mbeans.spring;

import javax.management.*;
import javax.management.modelmbean.*;

import org.springframework.jmx.export.*;
import org.springframework.jmx.export.assembler.*;

public class MBeanDescriptorEnabledExporter extends MBeanExporter
{
protected MBeanInfoAssembler mBeanInfoAssembler;

public MBeanDescriptorEnabledExporter()
{
setAssembler(new MBeanDescriptorBasedAssembler());
}

public ModelMBean createModelMBean() throws MBeanException
{
if (mBeanInfoAssembler instanceof MBeanDescriptorBasedAssembler)
{
return new ModelMBeanExtension();
}
return super.createModelMBean();
}

public void setAssembler(MBeanInfoAssembler mBeanInfoAssembler)
{
this.mBeanInfoAssembler = mBeanInfoAssembler;
super.setAssembler(mBeanInfoAssembler);
}
}

在使用这个扩展的类时,可以用标准 Spring 语言改变装配器,并在需要时回到默认的行为。在大多数情况下,如果最终绕过扩展,那么就不值得使用这个版本。不过,如果想要以新的定制装配器使用扩展的 ModelMBean,那么现在可以这样做。



回页首


构建一个定制的装配器

这个定制装配器的主要任务是查找与管理的类有关的元数据映射文件。找到这个文件后,就装载它并生成必要的 ModelMBeanInfo 实例。为此,我只是实现了 Spring MBeanInfoAssembler 实例建立这个文件的相关类路径,用静态 MBeanDescriptorUtil.read() 方法装载它并返回结果,如清单 3 所示:


清单 3. MBeanDescriptorBasedAssembler

package com.claudeduguay.mbeans.spring;

import java.io.*;

import javax.management.modelmbean.*;

import org.springframework.core.io.*;
import org.springframework.jmx.export.assembler.*;

import com.claudeduguay.mbeans.model.*;

public class MBeanDescriptorBasedAssembler
implements MBeanInfoAssembler
{
public ModelMBeanInfo getMBeanInfo(
Object managedBean, String beanKey)
{
String name = managedBean.getClass().getName();
String path = name.replace('.', '/') + ".mbean.xml";

ClassPathResource resource = new ClassPathResource(path);
InputStream input = null;
try
{
input = resource.getInputStream();
MBeanDescriptor descriptor = MBeanDescriptorUtil.read(input);
return descriptor.createMBeanInfo();
}
catch (Exception e)
{
throw new IllegalStateException(
"Unable to load resource: " + path);
}
finally
{
if (input != null)
{
try { input.close(); } catch (Exception x) {}
}
}
}
}

这个 MBeanDescriptorBasedAssembler 忽略 bean 键参数并直接用受管 bean 引用创建所需的 ModelMBeanInfo 实例。



回页首


示例

在本文其余部分,我将着重展示这个 Spring JMX 扩展的使用。为此,使用一个假想的服务,它开放两个方法和一个属性,因此表现了典型的用例。

ExampleService 是一个 Java 对象,它在被调用时只是向控制台进行输出,如清单 4 所示:


清单 4. ExampleService

package com.claudeduguay.jmx.demo.server;

public class ExampleService
{
protected String propertyValue = "default value";

public ExampleService() {}

public String getPropertyValue()
{
System.out.println("ExampleService: Get Property Value");
return propertyValue;
}

public void setPropertyValue(String propertyValue)
{
System.out.println("ExampleService: Set Property Value");
this.propertyValue = propertyValue;
}

public void startService()
{
System.out.println("ExampleService: Start Service Called");
}

public void stopService()
{
System.out.println("ExampleService: Stop Service Called");
}
}

对管理员友好的消息

这个扩展的描述符可以几乎直接关联属性和操作。描述符方法优于内省式方法的主要一点是可以提供更特定的消息。通知描述符的配置选项有赖于类型(XML)属性的命名规范。实际的名字是任意的,但是代码会被类型中的 set.namebefore.nameafter.name 样式触发。在这种情况下,我将 set 通知与 propertyValue (JMX)属性关联,将 beforeafter 通知与 startService()stopService() 方法关联。同样,这些扩展使我可以很好利用描述性的消息。

在清单 5 中,可以看到定义了一个属性和两个方法。通知描述符定义了方法的之前和之后事件以及一个属性设置通知:


清单 5. ExampleService.mbean.xml

<?xml version="1.0"?>
<mbean name="ExampleService" description="Example Service"
type="com.claudeduguay.jmx.demo.server.ExampleService">

<attribute name="propertyValue"
description="Property Value Access" type="java.lang.String"
readable="true" writable="true" />

<operation name="stopService"
description="Stop Example Service" />
<operation name="startService"
description="Start Example Service" />

<notification name="PropertyValueSet"
types="example.service.set.propertyValue"
description="PropertyValue was set" />
<notification name="BeforeStartService"
types="example.service.before.startService"
description="Example Service is Starting" />
<notification name="AfterStartService"
types="example.service.after.startService"
description="Example Service is Started" />
<notification name="BeforeStopService"
types="example.service.before.stopService"
description="Example Service is Stopping" />
<notification name="AfterStopService"
types="example.service.after.stopService"
description="Example Service is Stopped" />

</mbean>



回页首


配置服务器

要在客户机/服务器环境中运行这个例子,需要配置和启动一个 MBeanServer 实例。为此,我使用 Java 5.0 MBeanServer 实例,它保证我可以使用 JVM 中提供的管理扩展,同时管理自己的代码。如果愿意,还可以运行 MBeanServer 的多个实例,您愿意的话也可以自己试一试作为练习。

就像 Java 5.0 一样,Spring 框架使您可以配置自己的 MBeanServer 实例。我选择使用 Java 5.0,因为它支持 JSR-160 连接器,我的客户机代码会需要它。


清单 6. SpringJmxServer

package com.claudeduguay.jmx.demo.server;

import org.springframework.context.*;
import org.springframework.context.support.*;

import mx4j.tools.adaptor.http.*;

/*
* To use the SpringJmxServer, use the following command line
* arguments to activate the Java 1.5 JMX Server.
*
* -Dcom.sun.management.jmxremote.port=8999
* -Dcom.sun.management.jmxremote.ssl=false
* -Dcom.sun.management.jmxremote.authenticate=false
*/

public class SpringJmxServer
{
public static void main(String[] args)
throws Exception
{
String SPRING_FILE =
"com/claudeduguay/jmx/demo/server/SpringJmxServer.xml";
ApplicationContext context =
new ClassPathXmlApplicationContext(SPRING_FILE);
HttpAdaptor httpAdaptor =
(HttpAdaptor)context.getBean("HttpAdaptor");
httpAdaptor.start();
}
}

由于有了 MBeanDescriptorEnabledExporter,服务器的 Spring 配置文件非常简单。除了声明 ExampleService,我增加了开放一个 HTTP 适配器和连接 XSLTProcessorHttpAdaptor 所需要的 MX4J 项。注意这是 Spring 的 IOC 实现非常有用的一个领域。清单 7 显示了我的 SpringJmxServer 实例的 Spring 配置文件:


清单 7. SpringJmxServer.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="exporter" class=
"com.claudeduguay.mbeans.spring.MBeanDescriptorEnabledExporter">
<property name="beans">
<map>
<entry key="Services:name=ExampleService"
value-ref="ExampleService" />
<entry key="MX4J:name=HttpAdaptor"
value-ref="HttpAdaptor" />
<entry key="MX4J:name=XSLTProcessor"
value-ref="XSLTProcessor" />
</map>
</property>
</bean>

<bean id="XSLTProcessor"
class="mx4j.tools.adaptor.http.XSLTProcessor" />
<bean id="HttpAdaptor"
class="mx4j.tools.adaptor.http.HttpAdaptor">
<property name="processor" ref="XSLTProcessor"/>
<property name="port" value="8080"/>
</bean>

<bean id="ExampleService"
class="com.claudeduguay.jmx.demo.server.ExampleService" />

</beans>

如果愿意(假定您遵循了我的设置),那么现在就可以运行这个服务器了。它会注册 ExampleService 并运行 HTTP 适配器。不要忘记使用注释中提到的命令行参数启动 Java 5.0 MBeanServer,否则会得到默认实例,客户机示例就不能工作了。



回页首


运行客户机代码

启动服务器后,可以运行如清单 8 所示的客户机代码看看会发生什么。这段代码实现了 JMX NotificationListener 接口,这样就可以交互式地看到所发生的事情。连接后,可以注册监听器,然后触发几个调用、启动和停止服务、设置和取得属性。在每一种情况下,都应当在控制台上看到一个确认操作的通知消息。


清单 8. SpringJmxClient

package com.claudeduguay.jmx.demo.client;

import java.util.*;

import javax.management.*;
import javax.management.remote.*;

public class SpringJmxClient implements NotificationListener
{
public void handleNotification(
Notification notification, Object handback)
{
System.out.println(
"Notification: " + notification.getMessage());
}

public static void main(String[] args)
throws Exception
{
SpringJmxClient listener = new SpringJmxClient();

String address =
"service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(address);
Map<String,Object> environment = null;
JMXConnector connector =
JMXConnectorFactory.connect(serviceURL, environment);
MBeanServerConnection mBeanConnection =
connector.getMBeanServerConnection();

ObjectName exampleServiceName =
ObjectName.getInstance("Services:name=ExampleService");
mBeanConnection.addNotificationListener(
exampleServiceName, listener, null, null);

mBeanConnection.invoke(
exampleServiceName, "startService", null, null);
mBeanConnection.setAttribute(exampleServiceName,
new Attribute("propertyValue", "new value"));
System.out.println(mBeanConnection.getAttribute(
exampleServiceName, "propertyValue"));
mBeanConnection.invoke(
exampleServiceName, "stopService", null, null);
}
}

由于 HTTP 适配器也是可用的,可以试着使用 MX4J (通过一个到端口 8080 的浏览器连接)管理同样的方法和属性。如果同时让客户机代码运行,那么也会看到这些操作的通知。



回页首


结束语

在 本文中,我展示了如何扩展 Spring 的 JMX 支持以满足应用程序的特定需求。在这里,我使用了 Spring 的基于容器的体系结构和 AOP 框架来为 JMX 方法和属性增加通知事件。当然,我只触及到了 Spring JMX 能力的皮毛。还可以有许多其他扩展,Spring 和 JMX 都是很大的主题,每一个都值得进一步研究。我建议您研究本文的源代码并参阅后面 参考资料 一节以了解更多关于 Spring JMX 和相关技术的内容。




回页首


下载

描述名字大小 下载方法
Source codej-Extending-Spring-JMX.jar22 KB  FTP
Source codej-Extending-Spring-JMX-src.zip3 MB  FTP
关于下载方法的信息获取 Adobe® Reader®


回页首


参考资料

学习

获得产品和技术

讨论
  • 加入本文的论坛 。(您也可以通过点击文章顶部或者底部的论坛链接参加讨论。)

  • developerWorks blogs:参加 developerWorks 社区。


回页首


关于作者

Claude 是 Capital Stream Inc 的高级 J2EE 架构师。他有超过 25 年的软件开发经验,并且在 Java 平台的第一个 beta 版本发布时就开始使用它了。Claude 已发表了超过 75 篇文章和 超过 150 篇书评,主要关注 Java 语言和 XML。

posted @ 2005-12-09 23:23 Dion 阅读(917) | 评论 (0)编辑 收藏

将 Java Swing 应用程序连接到 Geronimo 服务器

创建可以与 Geronimo EJB 应用程序对话的独立客户端

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

讨论

样例代码


对此页的评价

帮助我们改进这些内容


级别: 初级

Neal Sanche , Java 开发人员, Pure Technologies

2005 年 8 月 24 日

在前两篇 developerWorks 文章(参阅 参考资料) 中,作者 Neal Sanche 使用简单的电话簿应用程序来展示如何将 Apache Geronimo 应用服务器连接到数据库,以及如何使用 Geronimo 创建具有 Enterprise JavaBeans (EJB) 后端的基于 Struts 的 Web 应用程序。本文进一步使用电话簿应用程序来展示如何创建独立客户端应用程序以操作电话号码数据库。您还将学习如何配置 Geronimo 以允许来自特定客户端的安全访问。

简介

本文将展示如何开发可以与运行在 Geronimo 应用服务器内部的 EJB 应用程序通信的独立(胖)客户端。基于我的前两篇文章 ——“将数据库连接到 Geronimo 应用服务器的三种方法”(developerWorks,2005 年 6 月)和“利用 Geronimo 深入 EJB Web 应用程序” (developerWorks,2005 年 7 月)—— 本文向您展示一个连接到使用 Geronimo EJB 应用程序构建的小型电话簿数据库的 Swing 客户端。您将阅读简要的设计说明,然后阅读有关运行该应用程序所需的客户端库的信息。接下来我将介绍联系服务器并对服务器上远程无状态会话 bean 执行操作的方法。最后,您将学习如何开发、编译并运行客户端应用程序,以及如何配置服务器以允许来自网络中特定客户端的安全访问。

要最有效地利用本文,您需要熟悉用于构建 Java 桌面应用程序的 Java Swing API 以及 Apache Maven 构建系统(参阅 参考资料 以链接到 Maven Web 站点)。



回页首


设计概述

首先简要介绍一下示例应用程序设计 —— 一个描述电话簿客户端应用程序的统一建模语言 (Unified Modeling Language, UML) 部署图 —— 如 图 1 所示。客户端应用程序通过其 EJB 端口连接到 Geronimo,并与 PhoneBook Session EJB 对话以通过 PhoneBook Entry Container-Managed Persistence (CMP) 操作数据库中的数据。


图 1. 电话簿客户端部署图
电话簿客户端部署图

Geronimo 的默认发行版对 EJB 端口有限制。仅当客户端应用程序运行在同一机器上并且通过环回地址(localhost 或 127.0.0.1) 连接时才能连接到该端口。本文稍后的 配置 Geronimo 的 EJB 端口 一节提供了有关如何让其他机器上的客户端访问服务器的详细信息。



回页首


用于连接到 Geronimo 的客户端库

要让客户端应用程序能够连接到 Geronimo 的 EJB 端口并与 EJB 层通信,客户端类路径中必须要有下列 Java 库:

  • geronimo-spec-j2ee-1.4-rc4.jar
  • geronimo-kernel-1.0-SNAPSHOT.jar
  • geronimo-j2ee-1.0-SNAPSHOT.jar
  • geronimo-security-1.0-SNAPSHOT.jar
  • cglib-nodep-2.1.jar
  • openejb-core-2.0-SNAPSHOT.jar
对构建结构的备注

因为对前面文章中代码的构建结构进行了一些小的修改,所以本文的代码包含了电话簿应用程序和电话簿客户端应用程序。下载 包括了有关如何使用 Maven 构建这两个应用程序的详细说明。

从源代码编译 Geronimo 时,当您使用 Maven 构建脚本来编译电话簿客户端应用程序时,这些库被放置到本地 Maven 资源库中且可供访问。您可以在 project.xml 文件的依赖关系部分中查看所有这些库位于 Maven 资源库的哪个位置。

其中一些库在客户端与服务器的通信中起着非常重要的作用。Geronimo 使用 CGLib 库来执行动态代理生成。这使得服务器动态生成远程调用服务器端组件的代码。如果在调试器中检查客户端上 InitialContext 对象的 lookup() 方法返回的一个对象,可以看到动态生成的对象的类名包括 CGLib。geronimo-spec-j2ee.jar 文件包含所有的 Sun Java 2 Platform, Enterprise Edition (J2EE) 接口和类。没有该文件,客户端将无法理解任何动态代理实例。openejb-core.jar 文件是与服务器的 EJB 端口进行对话所必需的。用于在 Geronimo 服务器中执行远程目录查询的 Java Naming and Directory Interface (JNDI) 类就在该 .jar 文件中。最后的三个 .jar 文件提供了其他支持类,比如与 Geronimo 对话的安全主体。



回页首


执行远程会话本地查询

客 户端通信部分的实现十分简单。将客户端连接到服务器时,Geronimo 与其他任何 J2EE 服务器没有任何不同,遵守健全的通过 JNDI 查询和远程方法调用 (RMI) 进行的通信标准。JNDI 查询是获得对远程对象的引用的标准访问。要通过 JNDI 进行连接,必须使用大量特定于 Geronimo 的属性来创建 InitialContext 实例,该实例用于执行查询。 清单 1 展示了有关如何创建会话的示例。


清单 1. 创建到 Geronimo 托管会话 bean 的远程会话
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

public void Connect() {
String hostName = getHostName();
String port = getPort();

Properties props = new Properties();

props.setProperty("java.naming.factory.initial",
"org.openejb.client.RemoteInitialContextFactory");
props.setProperty("java.naming.provider.url", hostName+":"+port);
props.setProperty("java.naming.security.principal", "username");
props.setProperty("java.naming.security.credentials", "passwd");

Context ic;
try {
ic = new InitialContext(props);
PhoneBookSessionHome sessionHome = (PhoneBookSessionHome)
PortableRemoteObject.narrow(
ic.lookup(PhoneBookSessionHome.JNDI_NAME),
PhoneBookSessionHome.class);
phoneBookSession = sessionHome.create();
} catch (Throwable ex) {
ex.printStackTrace();
} finally {
if (ic != null) {
ic.close();
}
}
}

清单 1 所示,创建了 Properties 对象并设置了四个属性。第一个并且是最重要的属性是 java.naming.factory.initial 属性,它必须设置为 org.openejb.client.RemoteInitialContextFactory。其他属性指定提供者 URL 以及安全主体和凭证。提供者 URL 是用冒号隔开的主机名和端口。

前已提及,EJB 端口当前只接受从客户端连接到 127.0.0.1 或 localhost 的连接。默认端口是 4201。尽管如此,主机名和端口都可以进行配置。有关详细信息,请参阅 配置 Geronimo 的 EJB 端口 一节。

创建好属性之后,就可以创建并使用 InitialContext 实例了。通过将属性传递给构造函数完成这一操作。创建好实例之后,可以执行查询。清单 1 包括一行复杂的代码,该行执行查询并对结果执行 PortableRemoteObject.narrow()。对于向用户隐藏有关协议传输 —— RMI 或者可能是 Internet Inter-ORB Protocol (IIOP)—— 的详细信息,这是必需的。完成之后,远程会话就可供使用了。在 清单 1 中,该行只创建了新 PhoneBookSession,并将其存储在一个字段中以供将来使用。

具有对远程会话的引用之后,该引用可用于所有操纵电话簿数据库信息的操作。现在只需要一个应用程序来练习该远程会话。



回页首


客户端的设计和开发

现 在我们来深入研究一个小 Swing 应用程序的设计和开发,这个小应用程序用来浏览、创建、删除和修改电话簿数据库条目。我尽量将 Swing 行话减到最少,以防您比较熟悉的是另一种 GUI 技术,比如 Standard Widget Toolkit (SWT)。事实上,如果需要的话,应用程序架构已经使得将显示从应用程序内部逻辑分离出来并将其连接到另一种 GUI 技术变得非常容易。

应用程序架构如 图 2 所示,这是一个详细显示应用程序静态结构的 UML 类图。


图 2. 客户端应用程序的 UML 类图
客户端应用程序的 UML 类图

图 2 中带绿色阴影的类是主要的应用程序类。Main 类是包含菜单和拆分窗格(其左侧是 PhoneNumberListPanel,右侧是 PhoneBookEditorPanel)的框架。该菜单还允许用户设置连接到哪个服务器的首选项,连接到服务器,并退出程序。Application 类是一个单身类(singleton class),用作应用程序的所有操作的控制器。它是惟一一个执行 EJB 操作的类,并保存对 PhoneBookSession 无状态会话 bean 的引用。

橙色的两个接口定义系统中的主要事件。每当 Application 决定电话号码列表需要更新时,就会激活 DataChangeEventPhoneNumberListModel 注册该事件。因为它是 PhoneNumberListPanel 中的主要数据模型列表视图,所以列表是通过模型更改来更新的。这与 Swing 应用程序的设计方法一致。

PhoneNumberListPanelPhoneBookEditorPanel 类都实现 PhoneBookSelectionListener 接口并注册来自 Application 单身类的事件。当它们收到事件时,它们相应地更新当前的选择。如果是 PhoneBookEditorPanel,当前选择导致 Name 和 Number 字段由来自当前选择的电话簿条目中的数据填充。

如果希望节省编写用户接口代码的时间,通常可以在 Internet 上找到高质量的免费工具。优秀的工具有 JGoodies Forms 1.0.5 和 FormLayoutMaker,FormLayoutMaker 是一个用于可视化创建窗体的小工具(参阅 参考资料 以获得到这些工具的链接)。FormLayoutMaker 工具生成代表 JGoodies 窗体布局约束的 XML 文件。这些工具帮助我快速创建了 Phone Number 编辑面板和 Preferences 面板的窗体。



回页首


构建应用程序

编 译应用程序有两种方法。我使用 Eclipse Visual Editor (VE) 插件 1.2 版本在 Eclipse 中开发了该应用程序。它生成应用程序的大部分代码框架,但它是以一种非入侵的方式完成的(没有代码标记和不可访问的代码块),所以如果没有安装 VE 的话也应该没有问题。可以只加载项目并尝试运行它。

您可能需要设置 MAVEN_REPO 构建变量以指向本地 Maven 资源库。还需要构建与本文一起提供的源代码中包括的 Geronimo 和 PhoneBook 服务器应用程序(参阅 下载)。这是因为,要编译客户端应用程序,包含服务器应用程序中 EJB 接口的 .jar 文件必须发布到本地 Maven 资源库中。PhoneBook 的 Maven 构建脚本通过下列 Maven 构建脚本段完成该操作:


清单 2. Maven 构建脚本段
<goal name="client" prereqs="java:compile">
<ant:jar destfile="target/${pom.artifactId}-client.jar">
<fileset dir="target/classes">
<include name="**/*.class"/>
</fileset>
</ant:jar>
<artifact:install artifact="target/${pom.artifactId}-client.jar"
type="jar" project="${pom}"/>
</goal>

用于构建应用程序的第二种方法就是使用 Maven。在 PhoneBook 目录中解压文件并运行 maven 命令。然后在 PhoneBookClient 目录中进行相同操作。如果一切顺利,就已经在目标子目录中创建了 UberJar —— 一个包含运行客户端所需的所有内容的 JAR 文件。

两种构建方法运行得同样好。使用 Maven 方法的优点是如果您尚未下载依赖关系,则它会自动从 ibiblio Web 站点(参阅 参考资料)上的远程 Maven 资源库中下载这些依赖关系。所以如果 Eclipse 中的依赖关系有问题,就在项目上至少运行一次 Maven 来校正缺少的库。



回页首


运行应用程序

确保 PhoneBook 服务器应用程序部署到 Geronimo 服务器中且正在运行。然后键入下列命令:

java -jar phonebook-client-uber.jar

将会看到应用程序弹出,如 图 3 所示。


图 3. Geronimo 电话簿客户端应用程序
Geronimo 电话簿客户端应用程序

首先,从 File 菜单中选择 Connect。如果是连接到 localhost:4201 端口,则应该获得一个连接;否则,控制台窗口将会显示错误消息。可以通过选择 Edit > Preferences、更改信息并尝试重新连接来更改连接的服务器和端口。一旦连接上之后,可以通过在电话号码编辑器中键入姓名和号码并单击 Save 来创建新记录。该记录将显示在姓名列表中。通过选择条目并单击 Delete 来删除条目。通过选择条目、进行修改并单击 Save 来更改条目。



回页首


配置 Geronimo 的 EJB 端口

当 前,配置 Geronimo 的 EJB 端口的方法需要编辑 XML 文件,然后重新编译 Geronimo。Tom McQueeney 的大型 Geronimo Live blog 上的一篇短文清楚介绍了如何使用 openejb\modules\assembly\src\plan\j2ee-server-plan.xml 文件更改 Geronimo Jetty 监听端口的详细信息(参阅 参考资料 以链接到该 blog)。同一文件还包含 EJB 端口的配置信息(参阅 清单 3)。


清单 3. j2ee-server-plan.xml 文件中的代码段
<gbean name="EJBNetworkService" 
class="org.openejb.server.StandardServiceStackGBean">
<attribute name="name">EJB</attribute>
<attribute name="port">4201</attribute>
<attribute name="address">127.0.0.1</attribute>
<attribute name="allowHosts">127.0.0.1</attribute>
<attribute name="logOnSuccess">HOST,NAME,THREADID,USERID</attribute>
<attribute name="logOnFailure">HOST,NAME</attribute>
<reference name="Executor"><name>DefaultThreadPool</name></reference>
<reference name="Server">
<gbean-name>openejb.server:name=EJBServer,*</gbean-name>
</reference>
</gbean>

您需要编辑 j2ee-server-plan.xml 文件并更改 allowHosts 属性。Geronimo 支持许多不同类型的地址。必须用下列模式之一输入逗号分隔的地址列表:

  • 最后一格为 0 的 IP 地址。例如,192.168.10.0 允许 192.168.10 网络上的任何机器与服务器通信。
  • 任何完全指定的 IP 地址。
  • 分解的 IP 地址。这是一种特殊模式,允许指定地址的网络部分和以大括号扩住的主机地址列表。例如,192.168.10.{5,6,7} 允许以下三个机器访问服务器:192.168.10.5、192.168.10.6 和 192.168.10.7。
  • 网络掩码 IP 地址。这是网络管理员熟悉的一种地址。基于精确的位模式匹配规则(超出本文范围),IP 地址与网络掩码相匹配。例如,192.168.255.255 允许 192.168.* 网络中的所有地址访问服务器。
  • 准确的 IPv6 地址。当将来的 IP 网络到来时,Geronimo 将准备好服务,允许列出特定的 IP 地址。
  • 网络掩码 IPv6 地址。

有 关服务器接受的特定模式的详细信息,请咨询源代码文件 —— ServiceAccessController.java —— 位于来源的 openejb\modules\core\src\java\org\openejb\server 目录中。在此将会找到与支持的每个地址类型相匹配的明确的正则表达式。

对 j2ee-server-plan.xml 文件进行修改之后,重新编译并更新服务器部署,您将具有一个专门满足您需要的服务器。(如果只想查看针对同一机器上的服务器运行的客户端,则无需这样做。默认情况下,Geronimo 被配置来完成这些操作。)



回页首


结束语

本 文提供了一个构建独立(胖)客户端的具体示例,该客户端可以与运行在 Geronimo 应用服务器内部的 EJB 应用程序进行对话。Geronimo 团队已经认真实现了健全的标准,推广用简单的 JNDI 查询方法获得与无状态会话 bean 的远程连接。如果只想让简单的应用程序运行,那么这是一个好消息,因为它只需要您编写少量代码。

按照我所介绍的模式,您能够将许多比较大的 数据库连接到与 Geronimo 服务器位于同一机器上的客户端应用程序。使用本文提供的指示,您还能够配置 Geronimo 服务器以允许从连接到您的网络或 Internet 上的其他机器访问服务器 EJB 端口。不妨尝试一下。




回页首


下载

描述名字大小 下载方法
Source code for the phonebook applicationPhonebookClient.zip227 KB  FTP
关于下载方法的信息获取 Adobe® Reader®


回页首


参考资料



回页首


关于作者

作者照片

Neal Sanche 是一位最近才涉足 Microsoft® .NET 世界的 Java 开发人员,他正在克服困难脱离他原来习惯了的开发环境。他的经验包括开发多个商业 J2EE 应用程序和多个独立 Java 应用程序。在业余时间,他作曲、摄影并撰写技术文章。访问他的 Web 站点 可以看到相应的多个示例。您可以通过 neal@nsdev.org 与 Neal 联系。

posted @ 2005-12-09 23:22 Dion 阅读(1232) | 评论 (0)编辑 收藏

什么是Portlet ?

作者:Sunil Patil

译者:observer





版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Sunil Patil;observer
原文地址:http://www.onjava.com/pub/a/onjava/2005/10/19/challenging-java-dominance.html
中文地址:http://www.matrix.org.cn/resource/article/44/44029_Portlet.html
关键词: Portlet Java


Portlets
“Portlets 是一种Web组件-就像servlets-是专为将合成页面里的内容聚集在一起而设计的。通常请求一个portal页面会引发多个portlets被调 用。每个portlet都会生成标记段,并与别的portlets生成的标记段组合在一起嵌入到portal页面的标记内。”(摘自Portlet规范, JSR 168)

本文探讨了以下内容:
1.        Portal页面的元素
2.        Portal是什么?
3.        Portlets是什么?
4.        开发“Hello World” Portlet
5.        在Pluto上部署HelloWorld Portlet
6.        如何创建Portal页面
7.        结束语
8.        资源


  Portlet规范将portlet定义为一种“基于Java技术的web组件,由处理请求和生成动态内容的portlet容器管理”。这段话听起来是不是有些费解?本文将说明portlets是什么以及能用它们做什么。


图1显示了在访问一个portal服务器时浏览器中页面的样子。

image
图1 典型的portal服务器的页面(点击查看原图)

   如果仔细查看浏览器里的页面,就会看到页面是由不同的“窗口”组成的。一个窗口用于刷新天气,另一个用于新闻,还有一个用于刷新股价,等等。这里的每一 个窗口都代表了一个portlets。如果看得再仔细些,还会发现每个窗口都有一个标题条和一些按钮,包括最小化和最大化按钮。

  在系 统里,这些窗口是相互独立开发、各不同的应用。新闻portlet的开发者创建应用并打包成war格式的文件,随后portal服务器的管理员在服务器上 部署该war文件并创建页面,接下来每个用户会选择在他的页面里有哪些应用。例如,如果用户对股价不感兴趣而对体育感兴趣,他可以用“体育”窗口替换“股 价”窗口。

  Portlet技术需要学习许多新概念,本文不可能全都涵盖,因此本文分为两部分。在第一部分里我们详细说明portals和portlets,并开发一个简单的“Hello World”portlet;在第二部分我们将探讨一些高级主题。

  我们将用Apache的Pluto服务器(Portlet API 1.0规范的参考实现)来测试我们的示例portlets,我们还会花些时间探讨如何安装和使用Pluto服务器。

Portal页面的元素

图2显示了Portal页面的各种元素。

image
图2 portal页面的元素

  每个portlet页面由一个或多个portlet窗口组成,每个portlet窗口又分为两部分:一个是外观,它决定了portlet窗口的标题条、控制和边界的样式;另一个是portlet段,它由portlet应用填充。

  Portal服务器决定了portal页面的整体观感,像标识、标题条颜色、控制图标等。通过修改几个JSP和css模板文件就可以改变portal的整个观感。我们将在“如何创建portal页面”部分对此做深入讨论。

Portal是什么?

   在了解portlet之前有必要先了解portal。在Portlet规范里是这样讲的:“portal是一种web应用,通常用来提供个性化、单次登 录、聚集各个信息源的内容,并作为信息系统表现层的宿主。聚集是指将来自各个信息源的内容集成到一个web页面里的活动”。

  Portal的功能可以分为三个主要方面:
1.        Portlet 容器:Portlet容器与servlet容器非常类似,所有的portlet都部署在portlet容器里,portlet容器控制portlet的生 命周期并为其提供必要的资源和环境信息。Portlet容器负责初始化和销毁portlets,向portlets传送用户请求并合成响应。
2.        内容聚集:Portlet规范中规定portal的主要工作之一是聚集由各种portlet应用生成的内容,我们将在“如何创建Portal页面”部分对此做进一步讨论。
3.        公共服务:portlet服务器的一个强项是它所提供的一套公共服务。这些服务并不是portlet规范所要求的,但portal的商业实现版本提供了丰富的公共服务以有别于它们的竞争者。在大部分实现中都有望找到的几个公共服务有:
         o 单次登录:只需登录portal服务器一次就可以访问所有其它的应用,这意味着你无需再分别登录每一个应用。例如一旦我登录了我的intranet网站,我就能访问mail应用、IM消息应用和其它的intranet应用,不必再分别登录这些应用。
   Portal服务器会为你分配一个通行证库。你只需要在mail应用里设定一次用户名和密码,这些信息将以加密的方式存储在通行证库中。在你已登录到 intranet网站并要访问mail应用的时候,portal服务器会从通行证库中读取你的通行证替你登录到mail服务器上。你对其它应用的访问也将 照此处理。
          o个性化:个性化服务的基本实现使用户能从两方面个性化她的页面:第一,用户可以根据她的自身喜好决定标题条的颜 色和控制图标。第二,用户可以决定在她的页面上有哪些portlets。例如,如果我是个体育迷,我可能会用一个能提供我钟爱球队最新信息的 portlet来取代股票和新闻portlets。
        一些在个性化服务方面领先的商业实现版本允许你建立为用户显示什么样的应用所 依据的标准(如收入和兴趣)。在这种情况下,可以设定一些像“对任何收入为X的用户显示馈赠商品的portlet”和“对任何收入为X的用户显示打折商品 的portlet”这样的商业规则。

        此外还有一些公共服务,比如机器翻译,是由portal服务器将portlet生成的内容翻译为用户要求的语言。大部分的商业portal服务器都支持手持设备访问并具有针对不同的浏览终端生成不同内容的能力。

Portlets是什么?

  与servlets类似,portlets是部署在容器内用来生成动态内容的web组件。从技术角度讲portlet是一个实现了javax.portlet.Portlet接口的类,它被打包成war文件格式部署到portlet容器里。

  Portlets在以下方面与servlets相似:
1.        portlets由特定的容器管理。
2.        portlets生成动态内容。
3.        portlet的生命周期由容器管理。
4.        portlets通过请求/响应模式与web客户端交互。

  Portlets在以下方面与servlets相异:
1.        portlets只能生成标记段,而不是整个文档。
2.        portlets没有可供直接访问的URL地址。不过你还是能够让别人通过URL访问到portlet,你可以把包含该portlet的页面的URL发给他。
3.        portlets 不能随意地生成内容,这是因为portlet生成的内容最终要成为portal页面的一部分。如果portal服务器要求的是html/text类型,那 么所有的portlets都应生成html/text类型的内容。再比方说,如果portal服务器要求的是WML类型,那么所有的portlets都应 生成WML类型的内容。

  portlets还提供了一些附加的功能:
1.        设置参数的持久化存储:portlets提供了一个PortletPreferences对象用来保存用户的设置参数。这些参数被存入一个持久化数据库,这样服务器重启后数据依然有效。开发者不必关心这些数据存储的具体实现机制。
2.         请求处理:portlets提供了更为细粒度的请求处理。对于用户在portlet上动作时向该portlet发出的请求(一种称为活跃期的状态),或者 因用户在其它portlet上动作而引发的刷新页面请求,Portal服务器提供了两种不同的回调方法来处理。
3.        Portlet 模式:portlets用模式的概念来表示用户在做什么。在使用mail应用的时候,你可能会用它来读信、写信或检查信件――这些都是mail应用的预定 功能,Portlets通常以VIEW模式提供这些功能。但还有一些活动,像指定刷新时间或(重新)设置用户名和密码,这些活动允许用户定制应用的行为, 因此它们用的是EDIT模式。Mail应用的帮助功能用的是HELP模式。

  如果仔细想想其实这里面并没有什么新东西,它们反而大部分都是普通的业务需求。Portlet规范的作用在于它提供了一个抽象层,这才是它对所有与之相关的人-最终用户、开发者和管理员-的价值所在。

  作为一个开发者,我会将所有与VIEW模式有关的业务逻辑放入doView()方法,将与应用配置有关的业务逻辑放入doEdit()方法,将与帮助有关的逻辑放入doHelp()方法

  这就简化了管理员对portlet应用的访问控制管理,因为他只需改变portlet的访问权限就能决定用户能做什么。例如,如果mail应用的一个用户能够在EDIT模式下设定用户名和密码,那么就可以断定他具有EDIT模式访问权限。

   不妨考虑这样一种情形:我是一个intranet网站的管理员,我的公司买了一个能显示新闻信息的第三方portlet应用,该应用允许用户指定跟踪新 闻更新的URL地址,我想借助它为用户显示公司的内部新闻。另一个需求是我不想让用户通过该应用来跟踪任何其它的新闻信息来源。作为管理员,我可以为所有 的用户指定一个用于内部新闻更新的URL地址,同时通过改变portlet应用的部署描述符来取消其它人修改该地址的权限。

  由于所有的portlet应用都具有相似的UI界面,因此采用portlets可使网站对最终用户更具吸引力。如果她想阅读任何一个应用的帮助信息,她可以点击帮助按钮;她也知道点击编辑按钮能让她进入应用的配置屏。标准化的用户界面使你的portlet应用更引人。

4.         窗口状态:窗口状态决定了portal页面上留给portlet生成内容的空间。如果点击最大化按钮,portlet将占据整个屏幕,成为用户唯一可用的 portlet;而在最小化状态,portlet只显示为标题条。作为开发者应当根据可用空间的大小来定做内容。

5.        用 户信息:通常portlets向发出请求的用户提供个性化的内容,为了能更加行之有效,portlets需要访问用户的属性信息,如姓名、email、电 话等。Portlet API为此提供了用户属性的概念,开发者能够用标准的方式访问这些属性,并由管理员负责在这些属性与真实的用户信息数据库(通常是LDAP服务器)之间建 立映射关系。

  我们将在本文的第二部分深入讨论这些特点-请求处理、用户信息和portlet模式。

开发"Hello World" Portlet

  现在我们就来开发一个简单的HelloWorld portlet。
1.        创建一个名为HelloWorld的web项目,它与通常的servlet项目类似,有一个/WEB-INF/web.xml文件作为项目的部署描述符。

2.        在build path里加入portlet-api-1.0.jar文件,该jar文件是Pluto发行包的一部分。

3.        在Source文件夹中按如下内容创建HelloWorld.java文件:
public class HelloWorld extends GenericPortlet{
  protected void doView(RenderRequest request,
  RenderResponse response) throws
  PortletException, IOException {
        response.setContentType("text/html");
        response.getWriter().println("Hello Portlet");
        }
}


   每个portlet都要实现Portlet接口,该接口为portlet定义了生命周期方法。由于不想覆盖所有这些方法,我们只对 GenericPortlet类进行扩展,它是一个实现了Portlet接口的适配器类。GenericPortlet类提供了所有生命周期方法的默认实 现,所以我们只需实现我们所需要的方法。

  我们在 HelloWorld portlet里要做的只是显示“Hello Portlet”,所以我们将覆盖GenericPortlet类的doView()方法,该方法以PortletRequest 和 PortletResponse作为参数。在doView()方法中首先调用response.setContentType()以通知portlet容 器该portlet将要生成何种类型的内容-如果不这样做就会导致IllegalStateException异常。一旦设置了内容的类型,就可以从 response对象中获得PrintWriter并开始写入。

4.        每个portlet应用在/WEB-INF文件夹中都有一个portlet.xml文件,它是portlet应用的部署描述符。按以下内容创建portlet.xml文件:
<portlet>
  <description>HelloWorldDescription
        </description>
    <portlet-name>HelloWorld
        </portlet-name>
    <display-name>Hello World
        </display-name>

    <portlet-class>com.test.HelloWorld
        </portlet-class>
    <expiration-cache>-1
        </expiration-cache>
        <supports>
          <mime-type>text/html</mime-type>
      <portlet-mode>VIEW
          </portlet-mode>
        </supports>
    <supported-locale>en
        </supported-locale>

        <portlet-info>
          <title>Hello World</title>
          <short-title>Hello World
          </short-title>
          <keywords>Hello,pluto</keywords>
      </portlet-info>
</portlet>


   <portlet-name>元素声明了portlet的名字,<portlet-class>元素指定了portlet的全 限定类名,<expiration-cache>元素以秒为单位指定了内容超期的时间。这里面有一点需要注意:你在portlet上的某些动 作可能会导致内容刷新,这与缓存时间无关。
  <supports>元素指定对于给定的<mime-type>有哪些模 式可供支持。在示例中我们假定HelloWorld只能生成text/html类型的内容,且只有view模式可支持该内容类型。如果要增加对其它内容类 型的支持,需要添加新的<support>元素并指定支持该MIME类型的模式有哪些。通常portlet对于text/html类型有 VIEW、EDIT和HELP模式可供支持,而对于WML MIME类型则只有VIEW模式。
  还可以用<supported- locale>元素来指定portlet支持哪些本地化。<title>元素用来指定portlet的标题。如果要对标题做国际化处 理,可以用元素<resource-bundle>指定资源(比例properties文件)的文件名。在这种情况下,容器将根据用户所在的 地区从适当的properties文件中选择标题。

5.        每个portlet应用都是一个web应用,因此除了portlet.xml文件之外还需要有web.xml文件。
<web-app>
  <display-name>Hello World Portlet
  </display-name>
  <welcome-file-list
    <welcome-file>index.jsp
        </welcome-file>
  </welcome-file-list>
</web-app>


6.        接下来将这些文件进行编译并打包为war文件。你可以自己完成这些工作,或者下载带有build.xml 的示例代码(参见“资源”部分)来创建war文件。
在Pluto上部署HelloWorld Portlet

   Pluto尚处于开发阶段的早期,因此还没有一套易于使用的管理工具。为了能使用Pluto服务器,需要将编译和源代码两个版本都下载。需要注意的是以 下说明是针对Windows平台的,Unix用户通过修改斜杠符号和执行sh shell脚本(不是bat批命令文件)会得到类似的结果。

1.        创建一个文件夹,比如C:\PlutoInstallation。
2.        从Pluto的网站下载pluto-1.0.1-rc1.zip和pluto-src-1.0.1-rc1.zip。
3.        将pluto-1.0.1-rc1.zip解压到C:\PlutoInstallation.文件夹,它应被解压到C:\PlutoInstallation\pluto-1.0.1-rc1文件夹下。
4.        执行C:\PlutoInstallation\pluto-1.0.1-rc1\bin\startup.bat启动Pluto,现在可以通过地址http://localhost:8080/pluto/portal访问Pluto服务器。
5.        将pluto-src-1.0.1-rc1.zip解压到C:\PlutoInstallation\PlutoSrc文件夹。
6.         进入C:\PlutoInstallation\PlutoSrc文件夹,执行maven distribute:all.,编译并下载运行常规管理任务所必需的相关资源文件。现在可以将HelloWorldPortlet.war作为 portlet进行安装了。
7.        首先将HelloWorldPortlet.war文件拷贝到C:\PlutoInstallation\portlets目录,如果没这个目录就创建它。
8.        将C:\PlutoInstallation\plutosrc\build.properties.sample更名为build.properties。
9.        编辑build.properties,将maven.tomcat.home指向Pluto编译版的安装位置,在本例中应改为maven.tomcat.home=C:/PlutoInstallation/pluto-1.0.1-rc1。
10.         为了安装portlet,进入C:\plutoInstallation\plutosrc\deploy文件夹,执行maven deploy -Ddeploy=c:\PlutoInstallation\portlets\HelloWorldPortlet.war,应能看到“build successful”信息。
11.        在C:\PlutoInstallation\pluto-1.0.1-rc1\webapps文件夹下,应该有一个HelloWorldPortlet文件夹。
12.        现在进入C:\PlutoInstallation\pluto-1.0.1-rc1\webapps\HelloWorld\WEB-INF\ folder文件夹,打开portlet的web.xml文件,你会发现里面自动多了几行,如下所示:
<servlet>
  <servlet-name>HelloWorld</servlet-name>
     <display-name>HelloWorld Wrapper</display-name>
      <description>Automated generated
      Portlet Wrapper</description>
      <servlet-class>org.apache.pluto.core.PortletServlet
      </servlet-class>
      <init-param>
         <param-name>portlet-class</param-name>
         <param-value>com.test.HelloWorld
         </param-value>
      </init-param>
      <init-param>
         <param-name>portlet-guid</param-name>
         <param-value>HelloPluto.HelloWorld
         </param-value>
      </init-param>
</servlet>

13.         接下来我们将该portlet加到页面里。进入C:\PlutoInstallation\pluto-1.0.1-rc1\webapps\pluto \WEB-INF\data文件夹,可以看到有两个XML文件:pageregistry.xml和 portletentityregistry.xml。
14.        portletentityregistry.xml包含了portlet的定义,在该文件中加入以下几行:
 <application id="5">
   <definition-id>HelloWorld</definition-id>
     <portlet id="1">
       <definition-id>HelloWorld.HelloWorld</definition-id>
     </portlet>
</application>

  应用的<definition-id>应为web应用所在文件夹的名字,portlet的<definition-id>应与web.xml中生成的portlet-guid相一致。
15.        pageregistry.xml定义了页面中包含了哪些portlets,对该文件做如下改动:
  <fragment name="p2" type="portlet">
    <property name="portlet" value="5.1"/>
</fragment>

16.        执行shutdown命令和startup命令重启Pluto服务器,返回到地址http://localhost:8080/pluto/portal并点击“Test Link”-此时页面中将出现我们的

HelloWorld portlet。

图3的右侧显示了HelloWorld portlet看上去的样子。

image
图3 portlet的屏幕截图

如何创建Portal页面

图4显示了portal容器如何将分离的portlets组装为页面。

image
图4 创建Portal页面

   大部分的portal服务器基本上都是部署于应用服务器上的web应用,通过servlet来处理访问portal服务器的请求。查看一下Pluto的 安装目录就会发现Pluto不过是一个部署于Tomcat服务器上的一个普通web应用,再看看C:\PlutoInstallation\pluto- 1.0.1-rc1\webapps\pluto\WEB-INF\web.xml会发现所有发往Pluto服务器的请求都被映射到 org.apache.pluto.portalImpl.Servlet上。

  在本文开始部分“Portal页面的元素”中,我们提到portal页面由两部分组成。一部分是由页面中的portlets生成的内容,另一部分是由portal服务器生成的内容。

  在Pluto里,只要用户发出请求,就会由servlet进行控制,根据用户所请求的页面来确定需要显示的portlets的列表。一旦生成了列表,servlet就将控制转给这些portlets线程并收集由它们生成的内容。

   对于由portal服务器生成的内容(像portal网站的观感及每个portlet的外观和控制之类)则取决于C:\ PlutoInstallation\pluto-1.0.1-rc1\webapps\pluto\WEB-INF\aggregation文件夹下的 JSP文件。RootFragment.jsp是主JSP文件,它决定了整体的观感和对齐方式;它还包含了Heads以定义在生成的页面中的< HEAD>标签里的内容。TabNavigation.jsp用来选择在banner中该显示什么(默认情况下在banner显示列表中也包扩了 pluto.png图片)。TabNavigation.jsp用来确定portal网站的导航方案。这意味着只需改动该文件夹下少量的几个JSP文件, 就能改变整个portal网站的观感。

  Pluto根据pageregistry.xml中的设置确定页面中有多少行,并用 RowFragment.jsp去填充。ColumnFragment.jsp用来填充每个栏目。PortletFragmentHeader.jsp用 来填充每个portlet的页头,像标题条及最大化和最小化控制。footer.jsp用来填充JSP的页脚。如果去看一下portal页面的HTML代 码就会发现每个portlet窗口无非都是嵌入<TD>标签的内容块。

结束语

  任何一种新技术要想获得成功都应具备以下条件:首先,它能提升现有技术;其次,它能解决现有技术遇到的普遍问题;再次,它能提供多于一个的抽象层(有人说,每抽象出一层,问题就解决一半)。

   由于portlet与现有的应用服务器架构兼容,这对Portlet API来说是一次发展servlet技术的好机会。你可以从portlet里调用EJB,或者用它启动和参与由应用服务器控制的全局性事务。换句话说,在 以商业逻辑为核心的领域里,portlet完全可以做得和servlet一样好。

  Portlets提供了一个抽象层,现在你不必再担 心客户端使用了什么样的HTTP方法,也不必自己编写程序去捕获像点击按钮这样的客户端事件。最后但绝不是最次要的一点是,portlets以提供像单次 登录、个性化等服务的方式解决了servlets不能解决的大部分问题。

资源
·本文的示例代码
·JSR 168的首页:http://www.jcp.org/en/jsr/detail?id=168
·Pluto的首页:http://portals.apache.org/pluto/
·onjava.com:onjava.com
·Matrix-Java开发者社区:http://www.matrix.org.cn


Sunil Patil从事J2EE技术工作已有5年,他感兴趣的领域包括对象关系映射工具、UI框架以及portals。
posted @ 2005-12-05 20:11 Dion 阅读(10023) | 评论 (5)编辑 收藏

利用XMLBean轻轻松松读写XML

作者:叶枫




版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:叶枫(http://blog.matrix.org.cn/page/叶枫)
原 文:[http://www.matrix.org.cn/resource/article/44/44027_XMLBean.html]http://www.matrix.org.cn/resource/article/44/44027_XMLBean.html[/url]
关键字:XML XMLBean Parser

一、关于XML解析

  XML在Java应用程序里变得越来越重要, 广泛应用于数据存储和
交换. 比如我们常见的配置文件,都是以XML方式存储的. XML还应用
于Java Message Service和Web Services等技术作为数据交换.
因此,正确读写XML文档是XML应用的基础.
  Java提供了SAX和DOM两种方式用于解析XML,但即便如此,要读写一个
稍微复杂的XML,也不是一件容易的事.

二、XMLBean简介

    Hibernate已经成为目前流行的面向Java环境的对象/关系数据库映射工具.
在Hibernate等对象/关系数据库映射工具出现之前,对数据库的操作是
通过JDBC来实现的,对数据库的任何操作,开发人员都要自己写SQL语句
来实现. 对象/关系数据库映射工具出现后,对数据库的操作转成对
JavaBean的操作,极大方便了数据库开发. 所以如果有一个类似的工具能够
实现将对XML的读写转成对JavaBean的操作,将会简化XML的读写,即使对XML
不熟悉的开发人员也能方便地读写XML. 这个工具就是XMLBean.

三、准备XMLBean和XML文档

   XMLBean是Apache的一个开源项目,可以从http://www.apache.org下载,
最新的版本是2.0. 解压后目录如下:
xmlbean2.0.0
     +---bin
     +---docs
     +---lib
     +---samples
     +---schemas


另外还要准备一个XML文档(customers.xml),
在本文的例子里,我们将对这个文档进行读写操作. 文档源码如下:

<?xml version="1.0" encoding="UTF-8"?>
<Customers>
    <customer>
            <id>1</id>
            <gender>female</gender>
            <firstname>Jessica</firstname>
            <lastname>Lim</lastname>
            <phoneNumber>1234567</phoneNumber>
            <address>
                <primaryAddress>
                        <postalCode>350106</postalCode>
                        <addressLine1>#25-1</addressLine1>
                        <addressLine2>SHINSAYAMA 2-CHOME</addressLine2>
                </primaryAddress>
                <billingAddress>
                        <receiver>Ms Danielle</receiver>
                        <postalCode>350107</postalCode>
                        <addressLine1>#167</addressLine1>
                        <addressLine2>NORTH TOWER HARBOUR CITY</addressLine2>
                </billingAddress>
            </address>
    </customer>
    <customer>
            <id>2</id>
            <gender>male</gender>
            <firstname>David</firstname>
            <lastname>Bill</lastname>
            <phoneNumber>808182</phoneNumber>
            <address>
                <primaryAddress>
                        <postalCode>319087</postalCode>
                        <addressLine1>1033 WS St.</addressLine1>
                        <addressLine2>Tima Road</addressLine2>
                </primaryAddress>
                <billingAddress>
                        <receiver>Mr William</receiver>
                        <postalCode>672993</postalCode>
                        <addressLine1>1033 WS St.</addressLine1>
                        <addressLine2>Tima Road</addressLine2>
                </billingAddress>
            </address>
    </customer>
</Customers>


这是一个客户的数据模型,每个客户都有客户编号(ID),姓名,性别(gender),
电话号码(phoneNumber)和地址,其中地址有两个: 首要地址(PrimaryAddress)
和帐单地址(BillingAddress),每个地址有邮编,地址1,和地址2组成.
其中帐单地址还有收件人(receiver).

    此外,还要准备一个配置文件(文件名customer.xsdconfig),这个文件的
作用我后面会讲,它的内容如下:

<xb:config xmlns:xb="http://xml.apache.org/xmlbeans/2004/02/xbean/config">

  <xb:namespace>
    <xb:package>sample.xmlbean</xb:package>
  </xb:namespace>

</xb:config>


四、XMLBean使用步骤

    和其他面向Java环境的对象/关系数据库映射工具的使用步骤一样,
在正式使用XMLBean前,我们要作两个准备.

    1. 生成XML Schema文件

       什么是XML Schema文件? 正常情况下,每个XML文件都有一个Schema文件,
       XML Schema文件是一个XML的约束文件,它定义了XML文件的结构和元素.
       以及对元素和结构的约束. 通俗地讲,如果说XML文件是数据库里的记录,
       那么Schema就是表结构定义.

       为什么需要这个文件? XMLBean需要通过这个文件知道一个XML文件的
       结构以及约束,比如数据类型等. 利用这个Schema文件,XMLBean将会产生
       一系列相关的Java Classes来实现对XML的操作. 而作为开发人员,则是
       利用XMLBean产生的Java Classes来完成对XML的操作而不需要SAX或DOM.

       怎样产生这个Schema文件呢? 如果对于熟悉XML的开发人员,可以自己来
       写这个Schema文件,对于不熟悉XML的开发人员,可以通过一些工具来完成.
       比较有名的如XMLSPY和Stylus Studio都可以通过XML文件来生成Schema
       文件. 加入我们已经生成这个Schema文件(customer.xsd):
      

       <?xml version="1.0" encoding="UTF-8"?>
       <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
                  elementFormDefault="qualified">
         <xs:element name="Customers">
           <xs:complexType>
             <xs:sequence>
               <xs:element maxOccurs="unbounded" name="customer"
                           type="customerType"/>
             </xs:sequence>
           </xs:complexType>
         </xs:element>
       <xs:complexType name="customerType">
             <xs:sequence>
               <xs:element name="id" type="xs:int"/>
               <xs:element name="gender" type="xs:string"/>
               <xs:element name="firstname" type="xs:string"/>
               <xs:element name="lastname" type="xs:string"/>
               <xs:element name="phoneNumber" type="xs:string"/>
               <xs:element name="address" type="addressType"/>
             </xs:sequence>
       </xs:complexType>
         <xs:complexType name="addressType">
             <xs:sequence>
               <xs:element name="primaryAddress" type="primaryAddressType"/>
               <xs:element name="billingAddress" type="billingAddressType"/>
             </xs:sequence>
         </xs:complexType>

         <xs:complexType name="primaryAddressType">
             <xs:sequence>
               <xs:element name="postalCode" type="xs:string"/>
               <xs:element name="addressLine1" type="xs:string"/>
               <xs:element name="addressLine2" type="xs:string"/>
             </xs:sequence>
         </xs:complexType>
         <xs:complexType name="billingAddressType">
             <xs:sequence>
                   <xs:element name="receiver" type="xs:string"/>
               <xs:element name="postalCode" type="xs:string"/>
               <xs:element name="addressLine1" type="xs:string"/>
               <xs:element name="addressLine2" type="xs:string"/>
             </xs:sequence>
         </xs:complexType>
       </xs:schema>
      


    2. 利用scomp来生成Java Classes

       scomp是XMLBean提供的一个编译工具,它在bin的目录下. 通过这个工具,
       我们可以将以上的Schema文件生成Java Classes.
       scomp的语法如下:-

      

       scomp [options] [dirs]* [schemaFile.xsd]* [service.wsdl]* [config.xsdconfig]*
      


       主要参数说明:
       -src [dir]                  -- 生成的Java Classes存放目录
     -srconly                  -- 不编译Java Classes,不产生Jar文件
     -out [jarFileName]  -- 生成的Jar文件,缺省是xmltypes.jar
       -compiler                 -- Java编译器的路径,即Javac的位置
       schemaFile.xsd    -- XML Schema文件位置
       config.xsdconfig   -- xsdconfig文件的位置, 这个文件主要用来制定生成的Java Class
                              的一些文件名规则和Package的名称,在本文,package是sample.xmlbean

       在本文,我是这样运行的:
      

       scomp -src build\src  -out build\customerXmlBean.jar schema\customer.xsd
             -compiler C:\jdk142_04\bin\javac customer.xsdconfig
      


       这个命令行的意思是告诉scomp生成customerXmlBean.jar,放在build目录下,同时
       生成源代码放在build\src下, Schema文件是customer.xsd,xsdconfig文件是customer.xsdconfig.

       其实, 生成的Java源代码没有多大作用,我们要的是jar文件.我们先看一下build\src\sample\xmlbean下生成的Classes.
      

          CustomersDocument.java    -- 整个XML文档的Java Class映射
       CustomerType.java              -- 节点sustomer的映射
       AddressType.java                 -- 节点address的映射
       BillingAddressType.java        -- 节点billingAddress的映射
       PrimaryAddressType.java      -- 节点primaryAddress的映射
    


       好了,到此我们所有的准备工作已经完成了. 下面就开始进入重点:利用刚才生成的jar文件读写XML.

五、利用XMLBean读XML文件

    新建一个Java Project,将XMLBean2.0.0\lib\下的Jar文件和刚才我们生成的customerXmlBean.jar加入
    到Project的ClassPath.

    新建一个Java Class: CustomerXMLBean.  源码如下:
    

    package com.sample.reader;

    import java.io.File;
    
    import sample.xmlbean.*;
    import org.apache.commons.beanutils.BeanUtils;
    import org.apache.xmlbeans.XmlOptions;
    public class CustomerXMLBean {
    private String filename = null;
    
    public CustomerXMLBean(String filename) {
            super();
            this.filename = filename;
    }

    public void customerReader() {
            try {
              File xmlFile = new File(filename);
              CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
              CustomerType[] customers = doc.getCustomers().getCustomerArray();
          
              for (int i = 0; i < customers.length; i++) {
                CustomerType customer = customers[i];
                println("Customer#" + i);
                println("Customer ID:" + customer.getId());
                println("First name:" + customer.getFirstname());
                println("Last name:" + customer.getLastname());
                println("Gender:" + customer.getGender());
                println("PhoneNumber:" + customer.getPhoneNumber());
                // Primary address
                PrimaryAddressType primaryAddress = customer.getAddress().getPrimaryAddress();
                println("PrimaryAddress:");
                println("PostalCode:" + primaryAddress.getPostalCode());
                println("AddressLine1:" + primaryAddress.getAddressLine1());
                println("AddressLine2:" + primaryAddress.getAddressLine2());
                // Billing address
                BillingAddressType billingAddress = customer.getAddress().getBillingAddress();
                println("BillingAddress:");
                println("Receiver:" + billingAddress.getReceiver());
                println("PostalCode:" + billingAddress.getPostalCode());
                println("AddressLine1:" + billingAddress.getAddressLine1());
                println("AddressLine2:" + billingAddress.getAddressLine2());
            
              }
            } catch (Exception ex) {
                    ex.printStackTrace();
            }
    }
    private void println(String str) {
          System.out.println(str);
    }
   public static void main(String[] args) {
      String filename = "F://JavaTest//Eclipse//XMLBean//xml//customers.xml";
                  
     CustomerXMLBean customerXMLBean = new CustomerXMLBean(filename);
                   customerXMLBean.customerReader();
    }

    }
    


    运行它,参看输出结果:
    

       Customer#0
       Customer ID:1
       First name:Jessica
       Last name:Lim
       Gender:female
       PhoneNumber:1234567
       PrimaryAddress:
       PostalCode:350106
       AddressLine1:#25-1
       AddressLine2:SHINSAYAMA 2-CHOME
       BillingAddress:
       Receiver:Ms Danielle
       PostalCode:350107
       AddressLine1:#167
       AddressLine2:NORTH TOWER HARBOUR CITY

       Customer#1
       Customer ID:2
       First name:David
       Last name:Bill
       Gender:male
       PhoneNumber:808182
       PrimaryAddress:
       PostalCode:319087
       AddressLine1:1033 WS St.
       AddressLine2:Tima Road
       BillingAddress:
       Receiver:Mr William
       PostalCode:672993
       AddressLine1:1033 WS St.
       AddressLine2:Tima Road
    

    怎么样,是不是很轻松? XMLBean的威力.

六、利用XMLBean写XML文件

    利用XMLBean创建一个XML文档也是一件轻而易举的事.我们再增加一个Method,
    请看一下的Java Class:
    

    public void createCustomer() {
    try {
        // Create Document
        CustomersDocument doc = CustomersDocument.Factory.newInstance();
        // Add new customer
        CustomerType customer = doc.addNewCustomers().addNewCustomer();
        // set customer info
        customer.setId(3);
        customer.setFirstname("Jessica");
        customer.setLastname("Lim");
        customer.setGender("female");
        customer.setPhoneNumber("1234567");
        // Add new address
        AddressType address = customer.addNewAddress();
        // Add new PrimaryAddress
        PrimaryAddressType primaryAddress = address.addNewPrimaryAddress();
        primaryAddress.setPostalCode("350106");
        primaryAddress.setAddressLine1("#25-1");
        primaryAddress.setAddressLine2("SHINSAYAMA 2-CHOME");

        // Add new BillingAddress
        BillingAddressType billingAddress = address.addNewBillingAddress();
        billingAddress.setReceiver("Ms Danielle");
        billingAddress.setPostalCode("350107");
        billingAddress.setAddressLine1("#167");
        billingAddress.setAddressLine2("NORTH TOWER HARBOUR CITY");

        File xmlFile = new File(filename);
        doc.save(xmlFile);
        } catch (Exception ex) {
                ex.printStackTrace();
        }

  }
    

    修改main method.
    

    public static void main(String[] args) {
    String filename = "F://JavaTest//Eclipse//XMLBean//xml//customers_new.xml";
        CustomerXMLBean customerXMLBean = new CustomerXMLBean(filename);
        customerXMLBean.createCustomer();
    }
    

    运行,打开customers_new.xml:
    

    <?xml version="1.0" encoding="UTF-8"?>
    <Customers>
    <customer>
            <id>3</id>
            <gender>female</gender>
            <firstname>Jessica</firstname>
            <lastname>Lim</lastname>
            <phoneNumber>1234567</phoneNumber>
            <address>
                    <primaryAddress>
                         <postalCode>350106</postalCode>
                         <addressLine1>#25-1</addressLine1>
                                       <addressLine2>SHINSAYAMA 2-CHOME</addressLine2>
                    </primaryAddress>
                    <billingAddress>
                        <receiver>Ms Danielle</receiver>
                        <postalCode>350107</postalCode>
                       <addressLine1>#167</addressLine1>
                       <addressLine2>NORTH TOWER HARBOUR CITY</addressLine2>
                    </billingAddress>
                    </address>
            </customer>
    </Customers>
    



七、利用XMLBean修改XML文件

    我们再增加一个Method:
    

      public void updateCustomer(int id,String lastname) {
         try {
        File xmlFile = new File(filename);
        CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
        CustomerType[] customers = doc.getCustomers().getCustomerArray();
      
        for (int i = 0; i < customers.length; i++) {
           CustomerType customer = customers[i];
          if(customer.getId()==id){
                customer.setLastname(lastname);
                break;
            }
        }
        doc.save(xmlFile);
         } catch (Exception ex) {
          ex.printStackTrace();
         }
           }
    

    main method:
    

    public static void main(String[] args) {
     String filename = "F://JavaTest//Eclipse//XMLBean//xml//customers_new.xml";
                    
    CustomerXMLBean customerXMLBean = new CustomerXMLBean(filename);
                    
    customerXMLBean.updateCustomer(3,"last");
    }
    

    运行之后,我们将会看到客户编号为3的客户的lastname已经改为last.

八、利用XMLBean删除一个customer

    再增加一个Method:
    

    public void deleteCustomer(int id) {
     try {
      File xmlFile = new File(filename);
     CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
    CustomerType[] customers = doc.getCustomers().getCustomerArray();

   for (int i = 0; i < customers.length; i++) {
        CustomerType customer = customers[i];
        if(customer.getId()==id){
                        customer.setNil() ;
                        break;
               }
   }
   doc.save(xmlFile);
   } catch (Exception ex) {
        ex.printStackTrace();
        }
   }


         main method:
    

    public static void main(String[] args) {
    String filename = "F://JavaTest//Eclipse//XMLBean//xml//customers_new.xml";
                    
    CustomerXMLBean customerXMLBean = new CustomerXMLBean(filename);
                    
    customerXMLBean.deleteCustomer(3);
    }

        
运行,我们将会看到客户编号为3的客户的资料已经被删除.

九、查询XML

    除了本文在以上讲述的,利用XMLBean能轻轻松松完成XML的读写操作外,结合XPath和XQuery,
   XMLBean还能完成象SQL查询数据库一样方便地查询XML数据. 关于XML查询以及如何创建XML数据库, 我将在另一篇文章里讨论.



十、结束语
    XMLBean能帮助我们轻易读写XML,这将有助于我们降低XML的学习和使用,有了这个基础,
    开发人员将为学习更多地XML相关技术和Web Services,JMS等其他J2EE技术打下良好地基础.


关于作者:
叶枫:热爱Java和Oracle. 在软件开发有近10年, 目前在国外一家美国大公司担任SA, 负责技术研究。作者Blog:http://blog.matrix.org.cn/page/叶枫
posted @ 2005-12-05 19:52 Dion 阅读(1099) | 评论 (0)编辑 收藏

Java JDBC里如何取得Oracle存储过程返回的动态结果集
作者:叶枫


版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:叶枫(http://blog.matrix.org.cn/page/叶枫)
原 文:[http://www.matrix.org.cn/resource/article/43/43999_JDBC_Oracle.html]http://www.matrix.org.cn/resource/article/43/43999_JDBC_Oracle.html[/url]
关键字:JDBC Oracle Cursor



1. 关于oracle和结果集

其实在大多数情况下,我们并不需要从oracle存储过程里返回一个或多个结果集,
除非迫不得已。

如果大家用过MS SQL Server或Sybase SQL Server,那么从存储过程返回一个
动态的结果集是一件非常容易的事情,只要在存储过程结束时写上

“select column1,column2,.... from table_list where condition“

就可以了。

但在Oracle中不能这样做. 我们必须使用Oracle Cursor.
在Oracle PL/SQL中,Cursor用来返回一行或多行记录,借助Cursor,我们可以从结果集中取得所有记录.

Cursor并不难,但是要从Oracle存储过程中返回结果集, 就需要用到Cursor变量,Cursor变量Oracle PL/SQL
的类型是REF CURSOR, 我们只要定义了REF CURSOR 类型就可以使用Cursor变量. 比如我们可以这样定义:
TYPE ref_cursor IS REF CURSOR;
了解了Cursor以及Cursor变量,下面就介绍如何使用Cursor变量给JDBC返回结果集.

2. 定义表结构

在以下例子里,我们要用到一张表Hotline.

Create table hotline(
country varchar2(50),
pno varchar2(50));


3. 定义存储过程

create or replace package PKG_HOTLINE is

type HotlineCursorType is REF CURSOR;

function getHotline return HotlineCursorType;

end;

create or replace package body PKG_HOTLINE is
function getHotline return HotlineCursorType is
hotlineCursor HotlineCursorType;
begin
open hotlineCursor for select * from hotline;
return hotlineCursor;
end;
end;


在这个存储过程里,我们定义了HotlineCursorType 类型,并且在存储过程中
简单地查找所有的记录并返回HotlineCursorType.

4. 测试存储过程

在Oracle SQL/Plus里登陆到数据库. 按以下输入就看到返回的结果集.

SQL> var rs refcursor;
SQL> exec :rs := PKG_HOTLINE.getHotline;
SQL> print rs;


5. Java调用

简单地写一个Java Class.

....
public void openCursor(){
Connection conn = null;
ResultSet rs = null;
CallableStatement stmt = null;
String sql = “{? = call PKG_HOTLINE.getHotline()}“;

try{
conn = getConnection();
stmt = conn.prepareCall(sql);
stmt.registerOutParameter(1,OracleTypes.CURSOR);
stmt.execute();
rs = ((OracleCallableStatement)stmt).getCursor(1);
while(rs.next()){
String country = rs.getString(1);
String pno = rs.getString(2);
System.out.println(“country:“+country+“|pno:”+pno);
}

}catch(Exception ex){
ex.printStackTrace();
}finally{
closeConnection(conn,rs,stmt);
}

}
.....


好了,大功告成.
posted @ 2005-12-02 21:59 Dion 阅读(3184) | 评论 (0)编辑 收藏

解决JAVA服务器性能问题

通过负载测试和分析来改善JAVA服务器应用的性能

作者:Ivan Small

译者:xMatrix





版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Ivan Small;xMatrix
原文地址:http://www.javaworld.com/javaworld/jw-02-2005/jw-0207-server-p3.html
中文地址:http://www.matrix.org.cn/resource/article/43/43998_server_capacity.html
关键词: server capacity

摘要

改善JAVA服务器的性能需要模拟负载下的服务器。创建一个模拟环境、搜集数据并且分析结果可能是对许多开发人员的挑战。这篇文章中的示例介绍了JAVA服务器性能分析的概念和工具。作者使用这个示例来研究超额请求次数下内存使用和同步竟争的影响。
作者Ivan Small

项目团队已经很熟悉如何组织一些具体的任务并完成他们。简单的性能问题很容易由一个开发人员分离并解决。然而大的性能问题,通常在系统处于高负载情况下发生,就不是这么简单能处理的了。这些问题需要一个独立的测试环境、一个模拟的负载,并且需要仔细地分析和跟踪。

在这篇文章中,我使用比较通用的工具和设备创建了一个测试环境。我会专注于两个性能问题,内存和同步,他们很难用简单的分析得到。通过一个具体的例子,我希望比较容易地解决复杂的性能问题而且可以提供处理问题过程中的细节。

改善服务器的性能

服 务器的性能改善是依赖于数据的。没有可靠的数据基础而更改应用或环境会导致更差的结果。分析器提供有用的JAVA服务器应用信息,但由于从单用户负载下的 数据与多用户负载下得到的数据是完全不同的,这导致分析器的数据并不精确。在开发阶段使用分析器来优化应用的性能是一个好的方式,但在高负载下的应用分析 可以取到更好的效果。


在负载下分析服务器应用的性能需要一些基本的元素:
        1、可控的进行应用负载测试的环境。
        2、可控的人造负载使得应用满负荷运行。
        3、来自监视器、应用和负载测试工具自身的数据搜集。
        4、性能改变的跟踪。

不要低估最后一个需求(性能跟踪)的重要性因为如果不能跟踪性能你就不能实际的管理项目。性能上10-20%的改善对单用户环境来说并没有什么不同,但对支持人员来说就不一样了。20%的改善是非常大的,而且通过跟踪性能的改善,你可以提供重要的反馈和持续跟踪。
虽然性能跟踪很重要,但有时为了使后续的测试更加精确而不得不抛弃先前的测试结果。在性能测试中,改善负载测试的精确性可能需要修改模拟环境,而这些变化是必须的,通过变化前后的负载测试你可以观察到其中的转变。


可控的环境        
可 控的环境最少也需要两台独立的机器和第三台控制的机器。其中一台用来生成负载,另一台作为控制机与前一台建立测试应用并接受反馈,第三台机器运行应用。此 外,负载和应用机器间的网络应该与局域网分开。控制机接受运行应用机器的反馈如操作系统、硬件使用率、应用(特别是VM)的状态。

负载模拟
最精确的模拟通常用实际的用户数据和WEB服务器端的访问日志。如果你还没有实际布署或者缺少实际的用户数据,你可以通过构造类似的场景或询问销售和产品管理团队或做一些有依据的猜想。协调负载测试和实际用户体验是一个持续的过程。

在 模拟中一些用户场景是必须的。如在一个通用地址薄应用中,你应该区分更新和查询操作。在我的测试应用中GrinderServlet类只有一个场景。单用 户连接10次访问这个servlet(在每一次访问间有一段暂停)。虽然这个应用很小,我认为这可以重复一些常见的东西。用户通常不会连接给服务器请求而 没有间断。如果没有间断,我们可能不能得到更精确的实际用户上限。

串行10个请求的另一个原因是实际应用中不会只有一个HTTP请求。单一而又分离的请求可以影响环境中的许多因素。对Tomcat来说,会为每一个请求创建一个会话,并且HTTP协议允许不同的请求重用连接。我会修改一下负载测试来避免混洧。

GrinderServlet类不会执行任何排序操作,但这个需求在大部分应用中都很普通。在这些应用中,你需要创建模拟的数据集并且用他们来构造相关用例的负载测试。

例如,如果用例涉及到用户登录一个WEB应用,从可能的用户列表中选取随机的用户会只使用一个用户更精确。否则,你可能不经意地使用了系统缓存或其他的优化或一些微妙的东西,而这会使得结果不正确。

负载测试软件
负 载测试软件可以构造测试场景并且对服务进行负载测试。我会在下面的示例中使用OpenSTA测试软件。这软件简单易学,结果也很容易导出,并且支持参数化 脚本,还可以监视信息的变化,他的主要缺点是基于Windows,但在这儿不是个问题。当然还有很多可选项如Apache的JMeter和Mercury 的LoadRunner。

The GrinderServlet

列表1中显示了GrinderServlet类,列表2中显示了Grinder类
Listing 1

package pub.capart;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class GrindServlet extends HttpServlet {
   protected void doGet(HttpServletRequest req, HttpServletResponse res)
         throws ServletException, IOException {
      Grinderv1 grinder = Grinderv1.getGrinder();
      long t1 = System.currentTimeMillis();
      grinder.grindCPU(13);
      long t2 = System.currentTimeMillis();

      PrintWriter pw = res.getWriter();
      pw.print("<html>\n< body> \n");
      pw.print("Grind Time = "+(t2-t1));
      pw.print("< body> \n< /html> \n");
   }
}


Listing 2

package pub.capart;

/**
* This is a simple class designed to simulate an application consuming
* CPU, memory, and contending for a synchronization lock.
*/
public class Grinderv1 {
   private static Grinderv1 singleton = new Grinderv1();
   private static final String randstr =
      "this is just a random string that I'm going to add up many many times";

   public static Grinderv1 getGrinder() {
      return singleton;
   }
   public synchronized void grindCPU(int level) {
      StringBuffer sb = new StringBuffer();
      String s = randstr;
      for (int i=0;i<level;++i) {
         sb.append(s);
         s = getReverse(sb.toString());
      }
   }
   public String getReverse(String s) {
      StringBuffer sb = new StringBuffer(s);
      sb = sb.reverse();
      return sb.toString();
   }
}


类 很简单,但他们会产生两个很常见的问题。咋一看瓶颈可能由grindCPU()方法的同步修饰符引起,但实际上内存消耗才是真正的问题所在。如图1,我的 第一个负载测试显示了常见的负载变化。在这里负载变化很重要因为你正在模拟一个高的负载。这种热身的方式也更精确因为避免了JSP编译引起的问题。我通常 习惯于在进行负载测试前先进行单用户模拟。

image
Figure 1        

我 在这篇文章中会使用相同的容量小结图。在执行负载测试时还有更多的可用信息,但这里只用了有用的部分。最上面的面板包含每秒完成的请求数和请求时间信息。 第二个面板包含活动用户数和失败率,我将超时、不正确的服务器应答和长于5秒的请求认为是失败的。第三个面板包含JVM内存统计和CPU使用率。CPU值 是所有处理器的用户时间的平均值,这里所有的测试机器都是双CPU的。内存统计图包含垃圾回收表和每秒垃圾回收数。

图1中两个最明显的数据是50%的CPU使用率和大量内存使用和释放。从列表2中可以看出这个原因。同步修饰符导致所有进程串行处理,就好像只用了一个CPU,而算法导致大量内存消耗在局部变量上。

通过CPU是个受限的资源,如果在这个测试中我可以完全利用到两个CPU的话就可以提高一倍的性能。垃圾回收器运行得如此频繁以致于不能忽略。在测试中每秒释放的内存达到100M,很显然这是个限制因素。失败数这么大明显这个应用是不可用的。

监视

在生成合理的用户负载后,监视工具需要收集进程的运行状况。在我的测试环境中可以收集到各种有用的信息:

1、        所有计算机、网络设备
2、        等等的使用率
3、        JVM的统计数据。
4、        个别JAVA方法所花费的时间。
5、        数据库性能信息,6、        包括SQL查询的统计。
7、        其他应用相关的信息

当 然这些监视也会影响负载测试,但如果影响比较小也可以忽略。基本上如果我们想获取所有上面的信息,肯定会影响测试的性能。但如果不是一次获取所有信息还是 有可能保证负载测试的有效性。仅对特定的方法设置定时器,仅获取低负载的硬件信息和低频率地获取样例数据。当然不加载监视器来做测试是最好的,然后和加载 监视器的测试来做比较。虽然有时候侵入式监视是个好主意,但就不可能有监视结果了。


获取所有监视数据到一个中央控制器来做分析是 最好的,但使用动态运行时工具也可以提供有用的信息。例如,命令行工具如PS、TOP、VMSTAT可以提供UNIX机器的信息;性能监视器工具可以提供 WINDOWS机器的信息;而TeamQuest, BMC Patrol, SGI's Performance Co-Pilot, and ISM's PerfMan这样的工具会在所有的测试环境中的机器安装代理并且将需要的信息传回中央控制机,这样就可以提供文本或可视化的信息。在本文中,我使用开源 的Performance Co-Pilot作为测试统计的工具。我发现他对测试环境的影响最小,并且以相对直接的方式来提供数据。

JAVA 分析器提供很多信息,但通常对负载测试来说影响太大而没有太多的用处。工具甚至可以让你在负载服务器上做一些分析,但这也很容易便测试无效。在这些测试 中,我激活了详细的垃圾收集器来收集内存信息。我也使用jconsole 和jstack工具(包含在J2SE 1.5中)来检查高负载下的VM。我没有保留这些测试用例中负载测试的结果因为我认为这些数据不是很正确。


同步瓶颈

在 诊断服务器问题时线程的信息是非常有用的,特别是对同步之类的问题。jstack工具可以连接到运行的进程并且保存每一个线程的堆栈信息。在UNIX系统 可以用信号量3来保存线程的堆栈信息,在WINDOWS系统的控制台中可以用Ctrl-Break。在第一项测试中,jstack指出许多线程在 grindCPU()方法中被阻塞。

你可以已经注意到列表2中grindCPU()方法的同步修饰符实际上并不必须。我在后一项测试中删除了他,如图2显示

image
Figure 2        

在图2中,你会注意到性能下降了。虽然我使用了更多的CPU,但吞吐量和失败数都更差了。虽然垃圾回收周期变了,但每秒依然需要回收100M。显然我们还没有找到主要的瓶颈。
非 竟争的同步相对于简单的函数调用还是很费时的。竟争性的同步就更费时了,因为除了内存需要同步外,VM还需要维护等待的线程。在这种状况下,这些代价实际 上要小于内存瓶颈。实际上,通过消除了同步瓶颈,VM内存系统承担了更多的压力最后导致更差的吞吐量,即使我使用了更多的CPU。显然最好的方式是从最大 的瓶颈开始,但有时这也不是很容易确定的。当然,确保VM的内存处理足够正常也是一个好的开始方向。

内存瓶颈

现在我会首先也定位内存问题。列表3是GrinderServlet的重构版本,使用了StringBuffer实例。图3显示了测试结果。

Listing 3

package pub.capart;

/**
* This is a simple class designed to simulate an application consuming
* CPU, memory, and contending for a synchronization lock.
*/
public class Grinderv2 {
   private static Grinderv2 singleton = new Grinderv2();
   private static final String randstr =
      "this is just a random string that I'm going to add up many many times";
   private StringBuffer sbuf = new StringBuffer();
   private StringBuffer sbufrev = new StringBuffer();

   public static Grinderv2 getGrinder() {
      return singleton;
   }
   public synchronized void grindCPU(int level) {
      sbufrev.setLength(0);
      sbufrev.append(randstr);
      sbuf.setLength(0);
      for (int i=0;i<level;++i) {
         sbuf.append(sbufrev);
         reverse();
      }
      return sbuf.toString();
   }

   public String getReverse(String s) {
      StringBuffer sb = new StringBuffer(s);
      sb = sb.reverse();
      return sb.toString();
   }
}


image
Figure 3        

通 常重用StringBuffer并不是一个好主意,但这里我只是为了重现一些常见的问题,而不量提供解决方案。内存数据已经从图上消失了因为测试中没有垃 圾回收器运行。吞吐量戏剧性的增加而CPU使用率又回到了50%。列表3不只是优化了内存,但我认为主要了改善了过度的内存消耗。

检视同步瓶颈

列表4另一个GrinderServlet类的重构版本,实现了一个小的资源池。图4显示了测试结果。
Listing 4

package pub.capart;

/**

* This is just a dummy class designed to simulate a process consuming
* CPU, memory, and contending for a synchronization lock.
*/
public class Grinderv3 {
   private static Grinderv3 grinders[];
   private static int grinderRoundRobin = 0;
   private static final String randstr =
      "this is just a random string that I'm going to add up many many times";
   private StringBuffer sbuf = new StringBuffer();
   private StringBuffer sbufrev = new StringBuffer();

   static {
      grinders = new Grinderv3[10];
      for (int i=0;i<grinders.length;++i) {
         grinders[i] = new Grinderv3();
      }
   }
   public synchronized static Grinderv3 getGrinder() {
      Grinderv3 g = grinders[grinderRoundRobin];
      grinderRoundRobin = (grinderRoundRobin +1) % grinders.length;
      return g;
   }
   public synchronized void grindCPU(int level) {
      sbufrev.setLength(0);
      sbufrev.append(randstr);
      sbuf.setLength(0);
      for (int i=0;i<level;++i) {
         sbuf.append(sbufrev);
         reverse();
      }
      return sbuf.toString();
   }
   public String getReverse(String s) {
      StringBuffer sb = new StringBuffer(s);
      sb = sb.reverse();
      return sb.toString();
   }
}
  


image
Figure 4        


吞吐量有一定的增加,而且使用更少的CPU资源。竟争和非竟争性同步都是费时的,但通常最大的同步消耗是减少了系统的可伸缩性。我的负载测试不再满足系统的需求了,因此我增加了虚拟的用户数,如图5 所示。

image
Figure 5        


在图5 中吞吐量在负载达到饱和时下降了一些然后在负载减少时又提高了。此外注意到测试使得CPU使用率达到100%,这意味着测试超过了系统的最佳吞吐量。负载测试的一个产出是性能计划,当应用的负载超过他的容量时会产生更低的吞吐量。


水平可伸缩性

水平伸缩允许更大的性能,但并不一定是费用相关的。运行在多个服务器上的应用通常比较运行在单个VM上的应用复杂。但水平伸缩支持在性能上的最大增加。

图6是我的最后一项测试的结果。我已经在三台基本一致的机器上使用了负载平衡,只是在内存和CPU速度上稍有不同。总的吞吐量要高于三倍的单机结果,而且CPU从来没有完全利用。在图6中我只显示了一台机器上的CPU结果,其他的是一样的。

image
Figure 6        


小结

我曾经花了9个月来布署一个复杂的JAVA应用,但却没有时间来做性能计划。但差劲的性能使得用户合约几乎中止。开发人员使用分析器花了很长时间找到几个小问题但没有解决根本的瓶颈,而且被后续的问题完全迷惑了。最后通过负载测试找到解决方法,但你可以想到其中的处境。

又一次我碰得更难的问题,应用只能达到所预期性能的1/100。但通过前期检测到的问题和认识到负载测试的必要性,这个问题很快被解决了。负载测试相对于整个软件开发的花费并不多,但其所归避的风险就高多了。

关于作者
Ivan Small拥有14年的软件开发经验。他在LBNL从开发Supernovae Cosmology Project开始他的职业生涯。这个项目是导致反重力和无限扩展宇宙理论被发现的两个项目之一。他从此工作于数据挖掘和企业级JAVA应用。现在他是 nnovative Interfaces公司的首席软件工程师。

资源
·javaworld.com:javaworld.com
·Matrix-Java开发者社区:http://www.matrix.org.cn/
·JAVA性能调优第二版:http://www.amazon.com/exec/obidos/ASIN/0596003773/javaworld
·并发编程技术:JAVA并发编程第二版:http://www.amazon.com/exec/obidos/ASIN/0201310090/javaworld
·JAVA网站分析:JAVA网站的性能分析:http://www.amazon.com/exec/obidos/ASIN/0201844540/javaworld
·JAVA性能:高性能JAVA平台计算:http://www.amazon.com/exec/obidos/ASIN/0130161640/javaworld
·JAVA2性能和术语指南:http://www.amazon.com/exec/obidos/ASIN/0130142603/javaworld
·BEA WebLogic服务器性能调优,包含有用的一般信息:BEA WebLogic服务器上J2EE应用性能测试:http://www.amazon.com/exec/obidos/ASIN/1904284000/javaworld
·JAVA性能调优:http://www.javaperformancetuning.com
·过度的JAVA同步:“轻量级线程”:http://www-106.ibm.com/developerworks/java/library/j-threads1.html
·负载和性能测试工具:http://www.softwareqatest.com/qatweb1.html#LOAD
posted @ 2005-12-02 21:59 Dion 阅读(5759) | 评论 (0)编辑 收藏