Java Votary

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

2005年11月23日 #

ThreadLocal是什么

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

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

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

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

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

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

ThreadLocal的接口方法

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

  • void set(Object value)

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

  • public Object get()

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

  • public void remove()

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

  • protected Object initialValue()

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

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

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

代码清单1 SimpleThreadLocal

public class SimpleThreadLocal {

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

public void set(Object newValue) {

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

}

public Object get() {

Thread currentThread = Thread.currentThread();

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

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

中保存起来。

o = initialValue();

valueMap.put(currentThread, o);

}

return o;

}

public void remove() {

valueMap.remove(Thread.currentThread());

}

public Object initialValue() {

return null;

}

}

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

一个TheadLocal实例

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

代码清单2 SequenceNumber

package com.baobaotao.basic;

public class SequenceNumber {

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

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

public Integer initialValue(){

return 0;

}

};

获取下一个序列值

public int getNextNum(){

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

return seqNum.get();

}

public static void main(String[] args)

{

SequenceNumber sn = new SequenceNumber();

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

TestClient t1 = new TestClient(sn);

TestClient t2 = new TestClient(sn);

TestClient t3 = new TestClient(sn);

t1.start();

t2.start();

t3.start();

}

private static class TestClient extends Thread

{

private SequenceNumber sn;

public TestClient(SequenceNumber sn) {

this.sn = sn;

}

public void run()

{

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

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

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

}

}

}

}

 

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

thread[Thread-2] sn[1]

thread[Thread-0] sn[1]

thread[Thread-1] sn[1]

thread[Thread-2] sn[2]

thread[Thread-0] sn[2]

thread[Thread-1] sn[2]

thread[Thread-2] sn[3]

thread[Thread-0] sn[3]

thread[Thread-1] sn[3]

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

Thread同步机制的比较

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

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

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

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

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

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

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

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

通通透透理解ThreadLocal

1同一线程贯通三层

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

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

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

public class TopicDao {

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

public void addTopic(){

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

}

}

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

代码清单4 TopicDao:线程安全

import java.sql.Connection;

import java.sql.Statement;

public class TopicDao {

①使用ThreadLocal保存Connection变量

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

public static Connection getConnection(){

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

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

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

Connection conn = ConnectionManager.getConnection();

connThreadLocal.set(conn);

return conn;

}else{

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

}

}

public void addTopic() {

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

Statement stat = getConnection().createStatement();

}

}

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

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

小结

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • janino-2.3.2.jar

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

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

Drools and Mandarax

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

Open Source Rule Engines Written In Java

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

始终会用上的Common BeanUtils

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

1.属性的动态getter、setter

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

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

2.BeanCompartor 动态排序

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

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

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

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

常用:

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

综合:

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

    }
}

2.Web服务中

(1).Weblogic

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

(2).Tomcat

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

(3).Resin

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

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

在Java文件中getResource或getResourceAsStream均可

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

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

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

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

3.文件操作的类

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

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

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

  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

}
 4.遗留问题

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

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

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

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

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

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

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

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

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

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

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

六种异常处理的陋习

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

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


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

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

  反例之一:丢弃异常

  代码:15行-18行。

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

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

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

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

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

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

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

  4、不要捕获异常。

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

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

  代码:15行。

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

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

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

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

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

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

  代码:3行-14行。

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

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

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

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

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

  代码:3行-18行。

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

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

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

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

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

  代码:3行-14行。

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

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

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

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

  代码:7行-11行。

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

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

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

  改写后的代码

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

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

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

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

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



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

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

在SPRING中实现事务暂停

作者:Juergen Hoeller

译者:xMatrix





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

摘要

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

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

POJO的声明性事务

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

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

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

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


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


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



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

    private DataSource dataSource;

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

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

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

    ...
}



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

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

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


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

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

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

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

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



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

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

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

clinic.storePet(pet);


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

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


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

PlatformTransactionManager策略

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

Spring and EJB CMT

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

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

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

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

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

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

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

关于作者
Juergen Hoeller是Spring框架的创始人之一
posted @ 2005-12-16 22:39 Dion 阅读(5052) | 评论 (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 阅读(810) | 评论 (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 阅读(1187) | 评论 (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 阅读(1619) | 评论 (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 阅读(1386) | 评论 (0)编辑 收藏

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

Quartz - Quartz 1 - CronTriggers Tutorial

Some of the content in this tutorial is taken from the Quartz 1.4.2 javadocs for CronTrigger.

Introduction

cron is a UNIX tool that has been around for a long time, so its scheduling capabilities are powerful and proven. The CronTrigger class is based on the scheduling capabilities of cron.

CronTrigger uses "cron expressions", which are able to create firing schedules such as: "At 8:00am every Monday through Friday" or "At 1:30am every last Friday of the month".

Cron expressions are powerful, but can be pretty confusing. This tutorial aims to take some of the mystery out of creating a cron expression, giving users a resource which they can visit before having to ask in a forum or mailing list.

Format

A cron expression is a string comprised of 6 or 7 fields separated by white space. Fields can contain any of the allowed values, along with various combinations of the allowed special characters for that field. The fields are as follows:

Field Name Mandatory? Allowed Values Allowed Special Characters
Seconds YES 0-59 , - * /
Minutes YES 0-59 , - * /
Hours YES 0-23 , - * /
Day of month YES 1-31 , - * ? / L W C
Month YES 1-12 or JAN-DEC , - * /
Day of week YES 1-7 or SUN-SAT , - * ? / L C #
Year NO empty, 1970-2099 , - * /

So cron expressions can be as simple as this: * * * * ? *
or more complex, like this: 0 0/5 14,18,3-39,52 ? JAN,MAR,SEP MON-FRI 2002-2010

Special characters

  • * ("all values") - used to select all values within a field. For example, "*" in the minute field means "every minute".
  • ? ("no specific value") - useful when you need to specify something in one of the two fields in which the character is allowed, but not the other. For example, if I want my trigger to fire on a particular day of the month (say, the 10th), but don't care what day of the week that happens to be, I would put "10" in the day-of-month field, and "?" in the day-of-week field. See the examples below for clarification.
  • - - used to specify ranges. For example, "10-12" in the hour field means "the hours 10, 11 and 12".
  • , - used to specify additional values. For example, "MON,WED,FRI" in the day-of-week field means "the days Monday, Wednesday, and Friday".
  • / - used to specify increments. For example, "0/15" in the seconds field means "the seconds 0, 15, 30, and 45". And "5/15" in the seconds field means "the seconds 5, 20, 35, and 50". You can also specify '/' after the '*' character - in this case '*' is equivalent to having '0' before the '/'. '1/3' in the day-of-month field means "fire every 3 days starting on the first day of the month".
  • L ("last") - has different meaning in each of the two fields in which it is allowed. For example, the value "L" in the day-of-month field means "the last day of the month" - day 31 for January, day 28 for February on non-leap years. If used in the day-of-week field by itself, it simply means "7" or "SAT". But if used in the day-of-week field after another value, it means "the last xxx day of the month" - for example "6L" means "the last friday of the month". When using the 'L' option, it is important not to specify lists, or ranges of values, as you'll get confusing results.
  • W ("weekday") - used to specify the weekday (Monday-Friday) nearest the given day. As an example, if you were to specify "15W" as the value for the day-of-month field, the meaning is: "the nearest weekday to the 15th of the month". So if the 15th is a Saturday, the trigger will fire on Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you specify "1W" as the value for day-of-month, and the 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary of a month's days. The 'W' character can only be specified when the day-of-month is a single day, not a range or list of days.

The 'L' and 'W' characters can also be combined in the day-of-month field to yield 'LW', which translates to "last weekday of the month".

  • # - used to specify "the nth" XXX day of the month. For example, the value of "6#3" in the day-of-week field means "the third Friday of the month" (day 6 = Friday and "#3" = the 3rd one in the month). Other examples: "2#1" = the first Monday of the month and "4#5" = the fifth Wednesday of the month. Note that if you specify "#5" and there is not 5 of the given day-of-week in the month, then no firing will occur that month.
  • C ("calendar") - this means values are calculated against the associated calendar, if any. If no calendar is associated, then it is equivalent to having an all-inclusive calendar. A value of "5C" in the day-of-month field means "the first day included by the calendar on or after the 5th". A value of "1C" in the day-of-week field means "the first day included by the calendar on or after Sunday".

The legal characters and the names of months and days of the week are not case sensitive. MON is the same as mon.

Examples

Here are some full examples:

Expression Meaning
0 0 12 * * ? Fire at 12pm (noon) every day
0 15 10 ? * * Fire at 10:15am every day
0 15 10 * * ? Fire at 10:15am every day
0 15 10 * * ? * Fire at 10:15am every day
0 15 10 * * ? 2005 Fire at 10:15am every day during the year 2005
0 * 14 * * ? Fire every minute starting at 2pm and ending at 2:59pm, every day
0 0/5 14 * * ? Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day
0 0/5 14,18 * * ? Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day
0 0-5 14 * * ? Fire every minute starting at 2pm and ending at 2:05pm, every day
0 10,44 14 ? 3 WED Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.
0 15 10 ? * MON-FRI Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday
0 15 10 15 * ? Fire at 10:15am on the 15th day of every month
0 15 10 L * ? Fire at 10:15am on the last day of every month
0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month
0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month
0 15 10 ? * 6L 2002-2005 Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005
0 15 10 ? * 6#3 Fire at 10:15am on the third Friday of every month
0 0 12 1/5 * ? Fire at 12pm (noon) every 5 days every month, starting on the first day of the month.
0 11 11 11 11 ? Fire every November 11th at 11:11am.

Pay attention to the effects of '?' and '*' in the day-of-week and day-of-month fields!

posted @ 2005-11-25 23:13 Dion 阅读(1227) | 评论 (1)编辑 收藏

架构设计师与 SOA , 第 2 部分

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

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


对此页的评价

帮助我们改进这些内容


王 强 , IBM中国软件开发实验室 - SOA Design Center

2005 年 11 月 24 日

本系列的第 1 部分 介绍了有关架构设计师以及 SOA 架构的知识,分析了 SOA 架构师在设计 SOA 系统架构时有哪些应该特别注意的地方。本文将延续第一部分的内容,向您介绍了 SOA 为企业级架构设计带来的影响,以及在构建基于 SOA 架构的企业系统时应该怎样保证所构建的系统架构能够满足系统中不同的服务级别需求。

1. SOA 为企业级架构设计带来的影响

1.1 SOA 的特点及其使用范围

SOA 既不是一种语言,也不是一种具体的技术,它是一种新的软件系统架构模型。 SOA 最主要的应用场合在于解决在Internet环境下的不同商业应用之间的业务集成问题。Internet环境区别于Intranet环境的几个特点主要是:

(a)大量异构系统并存,不同计算机硬件工作方式不同,操作系统不同、编程语言也不同;

(b)大量、频繁的数据传输的速度仍然相对较缓慢并且不稳定;

(c)无法完成服务(service)的版本升级,甚至根本就无法知道互联网上有哪些机器直接或者间接的使用某个服务。

SOA 架构具有一些典型特性,主要包括松耦合性,位置透明性以及协议无关性。松耦合性要求 SOA 架构中的不同服务之间应该保持一种松耦合的关系,也就是应该保持一种相对独立无依赖的关系;位置透明性要求 SOA 系统中的所有服务对于他们的调用者来说都是位置透明的,也就是说每个服务的调用者只需要知道他们调用的是哪一个服务,但并不需要知道所调用服务的物理位置 在哪里;而协议无关性要求每一个服务都可以通过不同的协议来调用。通过这些 SOA 架构所具有的特性我们可以看到,SOA 架构的出现为企业系统架构提供了更加灵活的构建方式,如果企业架构设计师基于 SOA 来构建系统架构,就可以从底层架构的级别来保证整个系统的松耦合性以及灵活性,这都为未来企业业务逻辑的扩展打好了基础。

1.2 SOA 架构的分层模型

接下来简要介绍一下 SOA 系统中的分层模型,整个 SOA 架构的分层模型如图2所示。




在 SOA 系统中不同的功能模块可以被分为7层:第一层就是系统已经存在的程序资源,例如ERP或者CRM系统等。第2层就是组件层,在这一层中我们用不同的组件把 底层系统的功能封装起来。第3层就是 SOA 系统中最重要的服务层,在这层中我们要用底层功能组件来构建我们所需要的不同功能的服务。总的来说,SOA 中的服务可以被映射成具体系统中的任何功能模块,但是从功能性方面可以大致划分为以下三种类型:(1)商业服务(business service) 或者是商业过程(business process)。这一类的服务是一个企业可以暴露给外部用户或者合作伙伴使用的服务。比如说提交贷款申请,用户信用检查,贷款信用查询。(2)商业功能 服务(business function service), 这类服务会完成一些具体的商业操作,也会被更上层的商业服务调用,不过大多数情况下这类服务不会暴露给外部用户直接调用,比如说检索用户帐户信息,存储用 户信息等。(3)技术功能服务(technical function service),这类服务主要完成一些底层的技术功能,比如说日志服务以及安全服务等。在服务层之上的第4层就是商业流程层,在这一层中我们利用已经封 装好的各种服务来构建商业系统中的商业流程。在商业流程层之上的就是第5层表示层了,我们利用表示层来向用户提供用户接口服务,这一层可以用基于 portal的系统来构建。以上这5层都需要有一个集成的环境来支持它们的运行,第6层中的企业服务总线(ESB)提供了这个功能。第7层主要为整个 SOA 系统提供一些辅助的功能,例如服务质量管理,安全管理这一类的辅助功能。



回页首


2. SOA 架构中的非功能性服务级别(service-level)需求

除 了系统的业务需求,架构设计师还必须要保证构建出来的系统架构能够满足系统中的非功能性服务级别(service-level)需求以及服务质量 (QoS)方面的需求。在项目初始及细化阶段,架构设计师应该与系统所有涉及方(Stakeholders)一起,为每一个服务级别需求定义其相关的衡量 标准。构建出的系统架构必须要能满足以下几方面的服务水准要求:性能、可升级性、可靠性、可用性、可扩展性、可维护性、易管理性以及安全性。架构设计师在 设计架构过程中需要平衡所有的这些服务级别需求。例如,如果服务级别需求中最重要的是系统性能,架构设计师很有可能不得不在一定程度上牺牲系统的可维护性 及可扩展性,以确保满足系统性能上的要求。随着互联网的发展,新构建的系统对于服务级别需求也变得日益重要,现在基于互联网的企业系统的用户已经不仅仅局 限于是本企业的雇员,企业的外部客户也会成为企业系统的主要用户。

架构设计师的职责之一就是要尽可能地为提高系统设计人员和系统开发人 员的工作效率考虑。在构建整个企业系统架构的过程中,需要充分重视各种服务级别需求,从而避免在系统开发和运行的时候出现重大问题。一个企业级系统中的服 务级别需求往往是十分错综复杂的, SOA 架构设计师需要凭借丰富的专业经验和扎实的理论知识来分离和抽象系统中不同的服务级别需求,图3展示了这种分析的过程。


图3
图3

经过 SOA 架构设计师分析和抽象的服务级别需求主要分为以下几类:

  • 性能是指系统提供的服务要满足一定的性能衡量标准,这些标准可能包括系统反应时间以及处理交易量的能力等;
  • 可升级性是指当系统负荷加大时,能够确保所需的服务质量,而不需要更改整个系统的架构;
  • 可靠性是指确保各应用及其相关的所有交易的完整性和一致性的能力;
  • 可用性是指一个系统应确保一项服务或者资源永远都可以被访问到;
  • 可扩展性是指在不影响现有系统功能的基础上,为系统填加新的功能或修改现有功能的能力;
  • 可维护性是指在不影响系统其他部分的情况下修正现有功能中问题或缺陷,并对整个系统进行维护的能力;
  • 可管理性是指管理系统以确保系统的可升级性、可靠性、可用性、性能和安全性的能力;
  • 安全性是指确保系统安全不会被危及的能力。

1) 性能

我 们通常可以根据每个用户访问的系统响应时间来衡量系统的整体性能;另外,我们也可以通过系统能够处理的交易量(每秒)来衡量系统的性能。对于架构设计师来 说,无论采取哪种衡量系统性能的方法来构建系统架构,这些对于性能的考虑对系统设计开发人员来说都应该是透明的,也就是说对于系统整体架构性能的考虑应该 是架构设计师的工作,而不是系统设计开发人员应该关注的事情。在较传统的基于EJB或者XML-RPC的分布式计算模型中,它们的服务提供都是通过函数调 用的方式进行的,一个功能的完成往往需要通过客户端和服务器来回很多次的远程函数调用才能完成。在Intranet的环境下,这些调用给系统的响应速度和 稳定性带来的影响都可以忽略不计,但如果我们在基于 SOA 的架构中使用了很多Web Service来作为服务提供点的话,我们就需要考虑性能的影响,尤其是在Internet环境下,这些往往是决定整个系统是否能正常工作的一个关键决定 因素。因此在基于 SOA 的系统中,推荐采用大数据量低频率访问模式,也就是以大数据量的方式一次性进行信息交换。这样做可以在一定程度上提高系统的整体性能。

2) 可升级性

可 升级性是指当系统负荷加大时,仍能够确保所需的服务质量,而不需要更改整个系统的架构。当基于 SOA 的系统中负荷增大时,如果系统的响应时间仍能够在可接受的限度内,那么我们就可以认为这个系统是具有可升级性的。要想理解可升级性,我们必须首先了解系统 容量或系统的承受能力,也就是一个系统在保证正常运行质量的同时,所能够处理的最大进程数量或所能支持的最大用户数量。如果系统运转时已经不能在可接受时 间范围内反应,那么这个系统已经到达了它的最大可升级状态。要想升级已达到最大负载能力的系统,你必须增加新的硬件。新添加的硬件可以以垂直或水平的方式 加入。垂直升级包括为现在的机器增加处理器、内存或硬盘。水平升级包括在环境中添置新的机器,从而增加系统的整体处理能力。作为一个系统架构设计师所设计 出来的架构必须能够处理对硬件的垂直或者水平升级。基于 SOA 的系统架构可以很好地保证整体系统的可升级性,这主要是因为系统中的功能模块已经被抽象成不同的服务,所有的硬件以及底层平台的信息都被屏蔽在服务之下, 因此不管是对已有系统的水平升级还是垂直升级,都不会影响到系统整体的架构。

3) 可靠性

可靠性是指确保各应 用及其相关的所有交易的完整性和一致性的能力。当系统负荷增加时,你的系统必须能够持续处理需求访问,并确保系统能够象负荷未增加以前一样正确地处理各个 进程。可靠性可能会在一定程度上限制系统的可升级性。如果系统负荷增加时,不能维持它的可靠性,那么实际上这个系统也并不具备可升级性。因此,一个真正可 升级的系统必须是可靠的系统。在基于 SOA 来构建系统架构的时候,可靠性也是必须要着重考虑的问题。要在基于 SOA 架构的系统中保证一定的系统可靠性,就必须要首先保证分布在系统中的不同服务的可靠性。而不同服务的可靠性一般可以由其部署的应用服务器或Web服务器来 保证。只有确保每一个 SOA 系统中的服务都具有较高的可靠性,我们才能保证系统整体的可靠性能够得以保障。

4) 可用性

可 用性是指一个系统应确保一项服务或者资源应该总是可被访问到的。可靠性可以增加系统的整体可用性,但即使系统部件出错,有时却并不一定会影响系统的可用 性。通过在环境中设置冗余组件和错误恢复机制,虽然一个单独的组件的错误会对系统的可靠性产生不良的影响,但由于系统冗余的存在,使得整个系统服务仍然可 用。在基于 SOA 来构建系统架构的时候,对于关键性的服务需要更多地考虑其可用性需求,这可以由两个层次的技术实现来支持,第一种是利用不同服务的具体内部实现内部所基于 的框架的容错或者冗余机制来实现对服务可用性的支持;第二种是通过UDDI等动态查找匹配方式来支持系统整体的高可用性。在 SOA 架构设计师构建企业系统架构的时候,应该综合考虑这两个方面的内容,尽量保证所构建的 SOA 系统架构中的关键性业务能具有较高的可用性。

5) 可扩展性

可 扩展性是指在不影响现有系统功能的基础上,为系统添加新的功能或修改现有功能的能力。当系统刚配置好的时候,你很难衡量它的可扩展性,直到第一次你必须去 扩展系统已有功能的时候,你才能真正去衡量和检测整个系统的可扩展性。任何一个架构设计师在构建系统架构时,为了确保架构设计的可扩展性,都应该考虑下面 几个要素:低耦合,界面(interfaces)以及封装。当架构设计师基于 SOA 来构建企业系统架构时,就已经隐含地解决了这几个可扩展性方面的要素。这是因为 SOA 架构中的不同服务之间本身就保持了一种无依赖的低耦合关系;服务本身是通过统一的接口定义(可以是WSDL)语言来描述具体的服务内容,并且很好地封装了 底层的具体实现。在这里我们也可以从一个方面看到基于 SOA 来构架企业系统能为我们带来的好处。

6) 可维护性

可 维护性是指在不影响系统其他部分的情况下修改现有系统功能中问题或缺陷的能力。同系统的可扩展性相同,当系统刚被部署时,你很难判断一个系统是否已经具备 了很好的可维护性。当创建和设计系统架构时,要想提高系统的可维护性,你必须考虑下面几个要素:低耦合、模块性以及系统文档记录。在企业系统可扩展性中我 们已经提到了 SOA 架构能为系统中暴露出来的各个子功能模块也就是服务带来低耦合性和很好的模块性。关于系统文档纪录,除了底层子系统的相关文档外,基于 SOA 的系统还会引用到许多系统外部的由第三方提供的服务,因此如果人力资源准许的话,应该增加专职的文档管理员来专门负责有关整个企业系统所涉及的所有外部服 务相关文档的收集、归类和整理,这些相关的文档可能涉及到第三方服务的接口(可以是WSDL)、服务的质量和级别、具体性能测试结果等各种相关文档。基于 这些文档,就可以为 SOA 架构设计师构建企业 SOA 架构提供很好的文档参考和支持。

7) 可管理性

可 管理性是指管理系统以确保整个系统的可升级性、可靠性、可用性、性能和安全性的能力。具有可管理性的系统,应具备对服务质量需求(QoS)的系统监控能 力,通过改变系统的配置从而可以动态地改善服务质量,而不用改变整体系统架构。一个好的系统架构必须能够监控整个系统的运行情况并具备动态系统配置管理的 功能。在对复杂系统进行系统架构建模时, SOA 架构设计师应该尽量考虑利用将系统整体架构构建在已有的成熟的底层系统框架(Framework)上。对于 SOA 架构设计师来说,可以选择的底层系统框架有很多,可以选用基于MQ, MessageBorker,WebSphere Application Server等产品来构建企业服务总线(Enterprise Service Bus)以支持企业的 SOA 系统架构,也可以选用较新的基于WebSphere Application Server 6中内嵌的Sibus来构建企业的ESB以支持 SOA 系统架构。具体选择哪种底层框架来实施 SOA 系统架构要根据每个系统各自的特点来决定,但这些底层的框架都已经提供了较高的系统可管理性。因此,分析并选择不同的产品或底层框架来实现企业系统架构也 是架构设计师的主要职责之一。有关于如何利用已有底层架构来构建 SOA 系统,中国 SOA 设计中心已经发表了一系列相关的文章,大家可以在DeveloperWorks中的 SOA 专栏看到它们。

8) 安全性

安 全性是指确保系统安全不会被危及的能力。目前,安全性应该说是最困难的系统质量控制点。这是因为安全性不仅要求确保系统的保密和完整性,而且还要防止影响 可用性的服务拒绝(Denial-of-Service)攻击。这就要求当 SOA 架构设计师在构建一个架构时,应该把整体系统架构尽可能地分割成各个子功能模块,在将一些子功能模块暴露为外部用户可见的服务的时候,要围绕各个子模块构 建各自的安全区,这样更便于保证整体系统架构的安全。如果一个子模块受到了安全攻击,也可以保证其他模块相对安全。如果企业 SOA 架构中的一些服务是由Web Service实现的,在考虑这些服务安全性的时候也要同时考虑效率的问题,因为WS-Security会为Web Service带来一定的执行效率损耗。



回页首


3.结束语

本 系列两部分介绍了有关架构设计师以及 SOA 架构的知识,分析了 SOA 架构师在设计 SOA 系统架构时有哪些应该特别注意的地方并在最后简要介绍了在构建基于 SOA 架构的企业系统时应该怎样保证所构建的系统架构能够满足系统中不同的服务级别需求。从架构设计师的角度, SOA 是一种新的设计模式,方法学。因此, SOA 本身涵盖了很多的内容,也触及到了系统整体架构设计、实现、维护等各个方面。本文的内容只是涉及到了有关于架构方面的一部分内容,希望能对广大的 SOA 系统开发设计人员起到一定的帮助作用。



回页首


参考资料

  1. Patterns: Service-oriented Architecture and Web Services红皮书,SG24-6303-00,2004 年 4 月,作者Mark Endrei 等。
  2. DeveloperWorks 的 SOA 和 Web 服务专区有大量专题文章以及关于开发 Web 服务应用程序的入门级、中级和高级教程。
  3. 有关随需应变商务的更多信息,请参阅 Developer resources for an on demand world Web site
  4. Web 服务项目角色 描述了 Web 服务开发项目中所涉及到的各种不同的工作角色,包括各自的目标,任务以及彼此之间是如何协作的。
  5. 面向服务的分析与设计原理一文研究了OOAD、EA 和 BPM 中的适当原理。
  6. 企业服务总线解决方案剖析,第 1 部分 介绍了面向服务的体系结构(service-oriented architecture, SOA )和企业服务总线(Enterprise Service Bus,ESB)的基本知识,ESB的技术沿革,以及ESB与 SOA 之间的关系。


回页首


关于作者


王 强,IBM软件工程师,工作在IBM中国软件开发实验室 - SOA Design Center,从事Incubator及 SOA ,Grid项目的工作,对J2EE架构、 SOA 架构、MDA/MDD以及网格计算等技术有较深入的研究。 联系方式:wangq@cn.ibm.com

posted @ 2005-11-25 22:35 Dion 阅读(727) | 评论 (0)编辑 收藏

架构设计师与SOA, 第 1 部分

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

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


对此页的评价

帮助我们改进这些内容


王 强 , IBM中国软件开发实验室 - SOA Design Center

2005 年 11 月 17 日

SOA(Service-Oriented Architecture),即面向服务的架构,这是最近一两年出现在各种技术期刊上最多的词汇了。现在有很多架构设计师和设计开发人员简单的把SOA和 Web Services技术等同起来,认为SOA就是Web Service的一种实现。本质上来说,SOA体现的是一种新的系统架构,SOA的出现,将为整个企业级软件架构设计带来巨大的影响。本系列两部分文章将 根据作者自己的理解来帮助大家分析和了解什么是SOA架构,SOA将怎样对企业系统架构设计带来积极的影响,什么是SOA架构设计师的角色,以及SOA架 构师在设计SOA系统架构时有哪些应该特别注意的地方。

1. 什么是架构?什么是基于SOA的架构?

1.1 什么是架构

从架构设计师的角度来看,架构就是一套构建系统的准则。通过这套准则,我们可以把一个复杂的系统划分为一套更简单的子系统的集合,这些子系统之间应该保持相互独立,并与整个系统保持一致。而且每一个子系统还可以继续细分下去,从而构成一个复杂的企业级架构。

当 一名架构设计师在构建某个企业级的软件系统时,除了要考虑这个系统的架构以及其应具有的功能行为以外,还要关注整个架构的可用性,性能问题,容错能力,可 重用性,安全性,扩展性,可管理维护性,可靠性等各个相关方面。有的时候一名好的架构设计师甚至还需要考虑所构建的系统架构是否合乎美学要求。由此我们可 以看到,我们衡量一个好的架构设计并不能只从功能角度出发,还要考虑很多其他的因素,对任何一个方面的欠缺考虑都有可能为整个系统的构建埋下隐患。

1.2 什么是基于SOA的架构

SOA 本身就是一种面向企业级服务的系统架构,简单来说,SOA就是一种进行系统开发的新的体系架构,在基于SOA架构的系统中,具体应用程序的功能是由一些松 耦合并且具有统一接口定义方式的组件(也就是service)组合构建起来的。因此,基于SOA的架构也一定是从企业的具体需求开始构建的。但是,SOA 和其它企业架构的不同之处就在于SOA提供的业务灵活性。业务灵活性是指企业能对业务变更快速和有效地进行响应、并且利用业务变更来得到竞争优势的能力。 对企业级架构设计师来说,创建一个业务灵活的架构意味着创建一个可以满足当前还未知的业务需求的IT架构。

利用基于SOA的系统构建方法,如图1中所示的一样,一个基于SOA架构的系统中的所有的程序功能都被封装在一些功能模块中,我们就是利用这些已经封装好的功能模块组装构建我们所需要的程序或者系统,而这些功能模块就是SOA架构中的不同的服务(services)。


图1
图1

因此,SOA架构本质上来说体现了一种复合的概念:它不仅为一个企业中商业流程的组织和实现提供了一种指导模式,同时也为具体的底层service开发提供了指导。



回页首


2. SOA架构设计师的角色

2.1 SOA架构设计师应该具备什么?

谈 到SOA架构设计师的角色,我们首先要了解架构设计师应具有的能力。总体上来说,一个好的架构设计师不仅应该是一个成熟的,具有实际经验的并具有快速学习 能力的人,而且他还应该具有良好的管理能力和沟通能力。只有具备了必需的能力,架构设计师才能在关键的时刻作出困难的决定,这就是一名架构设计师应该承担 的责任。从角色上来看,SOA 架构师不仅会负责端到端的服务请求者和提供者的设计,并且会负责对系统中非功能服务请求的调研和表述。

对 于任何一名经验丰富的架构设计师来说,不论他是采用基于传统的架构设计方法(基于J2EE架构或者.NET架构)还是采用基于SOA的架构设计方法来构建 一个企业级的系统架构,具有相关商业领域的知识对于架构设计师来说都是必不可少的,架构设计师往往可以通过实际的工作经验积累以及接受相关的专项培训来获 得这些商业领域的知识。除了具有相关商业领域的知识以外,一名合格的架构设计师必须具有较广泛的技术背景,这可能包括软硬件,通信,安全等各个方面的知 识。但这并不是意味着要成为一名架构设计师就必须熟悉每一门具体技术的细节,架构设计师必须至少能对各种技术有一个整体上的了解,能够熟知每种技术的特点 以及优缺点,只有这样架构设计师才能在特定的应用场景下正确地选择各种技术来设计企业整体架构。

2.2 什么是SOA架构设计师的职责?

那 什么是企业级SOA架构设计师的具体角色呢?什么是SOA架构设计师与设计和开发人员之间的差别呢?相信这些都是使大家最容易产生迷惑的问题。举个实际的 例子来说,当构建一个基于SOA架构的系统的时候,针对一个具体的 service,系统设计人员主要应该关注的是这个service能够为外部用户提供什么样的服务,也就是说系统设计人员关注的是这个service所提 供的功能。而对于SOA架构设计师来说,他们更关心的可能是当有一千个用户同时调用这个 service的时候,什么会发生?也就是说架构设计师关注的应该是一些商业需求和服务级别(service-level)需求。所有的架构设计师的角色 都包含了在构建一个系统的一开始就应该尽量减少可能存在的技术风险。而技术风险一般指的是一切未知的、未经证明的或未经测试所带来的风险。这些风险通常与 服务级别(service-level)需求相关,偶尔也会与企业具体的业务需求相关。无论是哪种类型的风险,在项目初期设计整体系统架构的过程中更易于 发掘这些风险,如果等到架构实施时再发觉这些风险,那么很可能会致使大量的开发人员等在那里,直到这些风险被妥善解决。如果进一步的细化,我们可以看到 SOA架构设计师的主要任务包括对整个系统解决方案轮廓的构建,需求分析,对体系结构的整体决策,相关组件建模,相关操作建模,系统组件的逻辑和物理布局 设计。

作为SOA架构设计师必须要能够领导整个开发团队,这样才能保证设计和开发人员是按照构建好的系统架构来开发整个系统的,这一点十分 的重要。这就要求一名架构设计师不仅要有很好的技术洞察力,同时还要具有一定的项目管理和项目实施的能力。在系统开发的过程中,架构设计师必须要有良好的 沟通和表达能力,这就体现在由架构设计师构建的系统模型是否具有很好的可读性和易理解性。如果由架构设计师构造出的系统模型不是很清晰的话,就可能会影响 设计和开发人员对于整个系统架构的理解。为了避免这种情况的出现,定期由架构设计师主持的开发团队内部讨论是十分重要的。



回页首


3. 构建SOA架构时应该注意的问题

3.1 原有系统架构中的集成需求

当 架构师基于SOA来构建一个企业级的系统架构的时候,一定要注意对原有系统架构中的集成需求进行细致的分析和整理。我们都知道,面向服务的体系结构是当前 及未来应用程序系统开发的重点,面向服务的体系结构本质上来说是一种具有特殊性质的体系结构,它由具有互操作性和位置透明的组件集成构建并互连而成。基于 SOA的企业系统架构通常都是在现有系统架构投资的基础上发展起来的,我们并不需要彻底重新开发全部的子系统;SOA可以通过利用当前系统已有的资源(开 发人员、软件语言、硬件平台、数据库和应用程序)来重复利用系统中现有的系统和资源。SOA是一种可适应的、灵活的体系结构类型,基于SOA构建的系统架 构可以在系统的开发和维护中缩短产品上市时间,因而可以降低企业系统开发的成本和风险。因此,当SOA架构师遇到一个十分复杂的企业系统时,首先考虑的应 该是如何重用已有的投资而不是替换遗留系统,因为如果考虑到有限的预算,整体系统替换的成本是十分高昂的。

当SOA架构师分析原有系统中的 集成需求的时候,不应该只限定为基于组件构建的已有应用程序的集成,真正的集成比这要宽泛得多。在分析和评估一个已有系统体系结构的集成需求时,我们必须 考虑一些更加具体的集成的类型,这主要包括以下几个方面:应用程序集成的需求,终端用户界面集成的需求,流程集成的需求以及已有系统信息集成的需求。当 SOA架构师分析和评估现有系统中所有可能的集成需求的时候,我们可以发现实际上所有集成方式在任何种类的企业中都有一定程度的体现。针对不同的企业类 型,这些集成方式可能是简化的,或者没有明确地进行定义的。因而,SOA架构师在着手设计新的体系结构框架时,必须要全面的考虑所有可能的集成需求。例 如,在一些类型的企业系统环境中可能只有很少的数据源类型,因此,系统中对消息集成的需求就可能会很简单,但在一些特定的系统中,例如航运系统中的EDI (Electronic Data Interchange 电子数据交换)系统,会有大量的电子数据交换处理的需求,因此也就会存在很多不同的数据源类型,在这种情况下整个系统对于消息数据的集成需求就会比较复 杂。因此,如果SOA架构师希望所构建的系统架构能够随着企业的成长和变化成功地继续得以保持,则整个系统构架中的集成功能就应该由服务提供,而不是由特 定的应用程序来完成。

3.2 服务粒度的控制以及无状态服务的设计

当SOA架构师构建一个企业级的SOA系统架构的时候,关于系统中最重要的元素,也就是SOA系统中的服务的构建有两点需要特别注意的地方:首先是对于服务粒度的控制,另外就是对于无状态服务的设计。

服务粒度的控制

SOA 系统中的服务粒度的控制是一项十分重要的设计任务。通常来说,对于将暴露在整个系统外部的服务推荐使用粗粒度的接口,而相对较细粒度的服务接口通常用于企 业系统架构的内部。从技术上讲,粗粒度的服务接口可能是一个特定服务的完整执行,而细粒度的服务接口可能是实现这个粗粒度服务接口的具体的内部操作。 举个例子来说,对于一个基于SOA架构的网上商店来说,粗粒度的服务可能就是暴露给外部用户使用的提交购买表单的操作,而系统内部的细粒度的服务可能就是 实现这个提交购买表单服务的一系列的内部服务,比如说创建购买记录,设置客户地址,更新数据库等一系列的操作。虽然细粒度的接口能为服务请求者提供了更加 细化和更多的灵活性,但同时也意味着引入较难控制的交互模式易变性,也就是说服务的交互模式可能随着不同的服务请求者而不同。如果我们暴露这些易于变化的 服务接口给系统的外部用户,就可能造成外部服务请求者难于支持不断变化的服务提供者所暴露的细粒度服务接口。而粗粒度服务接口保证了服务请求者将以一致的 方式使用系统中所暴露出的服务。虽然面向服务的体系结构(SOA)并不强制要求一定要使用粗粒度的服务接口,但是建议使用它们作为外部集成的接口。通常架 构设计师可以使用BPEL来创建由细粒度操作组成的业务流程的粗粒度的服务接口。

无状态服务的设计

SOA系统 架构中的具体服务应该都是独立的、自包含的请求,在实现这些服务的时候不需要前一个请求的状态,也就是说服务不应该依赖于其他服务的上下文和状态,即 SOA架构中的服务应该是无状态的服务。当某一个服务需要依赖时,我们最好把它定义成具体的业务流程(BPEL)。在服务的具体实现机制上,我们可以通过 使用 EJB 组件来实现粗粒度的服务。我们通常会利用无状态的Session Bean来实现具体的服务,如果基于Web Service技术,我们就可以将无状态的Session Bean暴露为外部用户可以调用的到的Web服务,也就是把传统的Session Facade模型转化为了 EJB 的Web服务端点,这样,我们就可以向 Web 服务客户提供粗粒度的服务。

如果我们要在 J2EE的环境下(基于WebSphere)构建Web服务,Web 服务客户可以通过两种方式访问 J2EE 应用程序。客户可以访问用 JAX-RPC API 创建的 Web 服务(使用 Servlet 来实现);Web 服务客户也可以通过 EJB的服务端点接口访问无状态的Session Bean,但Web 服务客户不能访问其他类型的企业Bean,如有状态的Session Bean,实体Bean和消息驱动Bean。后一种选择(公开无状态 EJB 组件作为 Web 服务)有很多优势,基于已有的EJB组件,我们可以利用现有的业务逻辑和流程。在许多企业中,现有的业务逻辑可能已经使用 EJB 组件编写,通过 Web 服务公开它可能是实现从外界访问这些服务的最佳选择。EJB 端点是一种很好的选择,因为它使业务逻辑和端点位于同一层上。另外EJB容器会自动提供对并发的支持,作为无状态Session Bean实现的 EJB 服务端点不必担心多线程访问,因为 EJB 容器必须串行化对无状态会话 bean 任何特定实例的请求。 由于EJB容器都会提供对于Security和Transaction的支持,因此Bean的开发人员可以不需要编写安全代码以及事务处理代码。 性能问题对于Web服务来说一直都是一个问题,由于几乎所有 EJB 容器都提供了对无状态会话 Bean 群集的支持以及对无状态Session Bean 池与资源管理的支持,因此当负载增加时,可以向群集中增加机器,Web 服务请求可以定向到这些不同的服务器,同时由于无状态Session Bean 池改进了资源利用和内存管理,使 Web 服务能够有效地响应多个客户请求。由此我们可以看到,通过把 Web 服务模型化为 EJB 端点,可以使服务具有更强的可伸缩性,并增强了系统整体的可靠性。



回页首


4. 结束语

本文简要介绍了有关架构设计师以及SOA架构的知识,分析了SOA架构师在设计SOA系统架构时有哪些应该特别注意的地方。

本 文的第二部分将向您介绍在构建基于SOA架构的企业系统时应该怎样保证所构建的系统架构能够满足系统中不同的服务级别需求。从架构设计师的角度,SOA是 一种新的设计模式,方法学。因此,SOA本身涵盖了很多的内容,也触及到了系统整体架构设计、实现、维护等各个方面。本文的内容只是涉及到了有关于架构方 面的一部分内容,希望能对广大的SOA系统开发设计人员起到一定的帮助作用。



回页首


参考资料

  1. Patterns: Service-oriented Architecture and Web Services红皮书,SG24-6303-00,2004 年 4 月,作者Mark Endrei 等。
  2. DeveloperWorks 的SOA 和 Web 服务专区有大量专题文章以及关于开发 Web 服务应用程序的入门级、中级和高级教程。
  3. 有关随需应变商务的更多信息,请参阅 Developer resources for an on demand world Web site
  4. Web 服务项目角色 描述了 Web 服务开发项目中所涉及到的各种不同的工作角色,包括各自的目标,任务以及彼此之间是如何协作的。
  5. 面向服务的分析与设计原理一文研究了OOAD、EA 和 BPM 中的适当原理。
  6. 企业服务总线解决方案剖析,第 1 部分 介绍了面向服务的体系结构(service-oriented architecture,SOA)和企业服务总线(Enterprise Service Bus,ESB)的基本知识,ESB的技术沿革,以及ESB与SOA之间的关系。


回页首


关于作者


王 强,IBM软件工程师,工作在IBM中国软件开发实验室 - SOA Design Center,从事Incubator及SOA,Grid项目的工作,对J2EE架构、SOA架构、MDA/MDD以及网格计算等技术有较深入的研究。 联系方式:wangq@cn.ibm.com

posted @ 2005-11-25 22:34 Dion 阅读(948) | 评论 (0)编辑 收藏

J2EE中软件基础结构的瓶颈

作者:Deepak Goel

译者:xMatrix


版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Deepak Goel;xMatrix
原文地址:http://www.onjava.com/pub/a/onjava/2005/01/19/j2ee-bottlenecks.html
中文地址:http://www.matrix.org.cn/resource/article/43/43964_J2EE_bottlenecks.html
关键词: bottlenecks J2EE


可扩展性是系统中的一个非常重要的非功能性需求。但系统中可能有多个瓶颈会阻碍系统的扩展性。在这篇文章中,我们尝试分析软件基础结构成为瓶颈的案例,而不考虑硬件资源上的限制(如CPU、磁盘空间、网络速度等)。下面我们将探讨一下这个问题。

下面是一些整篇文章中用到的一些术语:
·吞吐量:系统支持的每秒能够处理的事务个数。
·服务请求:每个事务中特定硬件的使用率,等于硬件使用率除以吞吐量。
·硬件资源:指处理器、内存、磁盘和网络。
·软件资源:指WEB线程、执行线程、BEAN池及数据库连接池等。
·预计时间:用户预计两次并发提交请求之间的时间。
·短时间法则:一个验证测试及确信测试环境不是瓶颈的法则。
·响应时间:用户等待他提交的请求返回响应的时间。

理论基础

任何J2EE应用通常都有下面几个层次,如图1:
1、硬件基础结构资源(处理器、内存、磁盘和网络)
2、软件基础结构资源(JVM,WEB服务器、应用服务器、数据库服务器)
3、软件应用(J2EE应用)

imageFigure 1. Snapshot of a J2EE system

这儿有两个可能性导致瓶颈:硬件成为主要瓶颈或者软件成为主要瓶颈。在第一种情况,硬件资源不足够而软件资源很充分,如图2。随着负载的增加,硬件资源成为瓶颈,而软件可以继续扩展。减轻这个瓶颈的方案通常是扩大或者增加硬件。

image
Figure 2. The hardware pipe becomes a bottleneck


在第二种情况,硬件资源是足够的而软件资源相对有限。随着负载的增加,软件资源成为瓶颈,如图3。减轻这种瓶颈的方案通常是使用软件群集或优化软件。

image
Figure 3. Software pipe becomes a bottleneck

应用服务器如何工作?

来 考虑一下应用服务器的内部机制。应用服务器的基本功能包括事务管理、数据持久、对象池、SOCKET处理和请求处理。这些功能的流程如图4。有各种组件来 处理这些功能。这些组件需要同步应用服务器中的线程来维护数据的一致性并操作数据。虽然这种同步对应用服务器的功能正确性是必须而且有用的,但是也成为高 负载的限制,即使有足够的硬件资源。

image
Figure 4. Internals of an application server

实验

为 了理解瓶颈的状况,我们在基于Windows/Intel平台用流行的J2EE应用服务器来测试一下JAVA PETSTORE应用。一些测试用例如PetStore应用的浏览和购买周期来测试扩展性。我们确信整个测试环境(包括操作系统、JVM、应用服务器和应 用自身)已经尽可能优化了,而且J2EE应用没有任何瓶颈或同步问题。我们使用了多用户负载测试并观察了响应时间、吞吐量、资源利用率等指标。

环境如下:
1、        J2EE PetStore应用
2、        J2EE应用服务器
3、        Sun JVM 1.3
4、        Windows 2000高级服务器
5、        Intel Dell PowerEdge 8450 (8Intel至强800MHz处理器, 4GB RAM)
6、        100Mbps Cisco dedicated network
7、        负载测试工具WebLoad

image




在这个测试中我们看到即使有足够的硬件资源,应用服务器的实例个数限制了扩展的能力。在这里软件资源(如执行线程、BEAN池大小、数据库池和其他应用服务器参数)优化后即使这些资源不足也不会影响系统的扩展。下面我们来研究一下减轻这种问题的方案。
注:Sun J2EE PetStore可以被更多地优化来改善性能和可扩展性。


解决方案

同一机器上的群集

当吞吐量满载了应用服务器的一个实例时,需要增加一个实例来减轻这种问题。这个方案如图5。

image
Figure 5. Instance clusters on the same hardware box

当前机器的CPU使用率只有40%因而有足够空间来增加一个实例。我们可以发现在增加了实例后,吞吐量也增加了50%,如表2

image

在不同机器的群集
当吞吐量满载了应用服务器的一个实例时,机器的CPU使用率只有40%。因为8CPU的机器未完全利用,所以我们测试一下更低配置的机器。现在我们使用两个4CPU的机器,如图6。

image
Figure 6. Instance clusters on different hardware boxes


我们发现4CPU机器的CPU使用率已达到80%,再增加实例也没有什么用处了。因此我们又增加一台4CPU的机器来运行应用的实例。在增加机器后,吞吐量几乎翻了一倍。在这里我们确信数据库服务器不会成为瓶颈。

注:在上面的两台机器的测试中,负载平衡是通过一种不增加应用服务器实例功能负载的方式来处理的。但是在实际的生产环境中很难做到。

因为我们观察到8CPU的机器被没有被完全利用,所以我们使用4CPU的机器重新测试了一遍。测试的结果可以在下表中看到,分别对应配置1和2。4CPU的配置几乎被完全利用了,这意味着使用4CPU的配置比8CPU的配置更实际,因为前者花费更少。

image

小结

这 些实验指出了软件基础结构如应用服务器实例可能成为瓶颈,并给出了一些解决方案来减轻这种问题(包括在相同或不同机器上的群集)。这个问题需要在J2EE 应用的负载计划或大小确定时优先考虑,因为这直接影响到应用的扩展性。这种想法很重要,下面我通过一个情景对话来表达。

项目经理:你的意思是应用服务器(代表软件基础结构)会成为系统的瓶颈。
性能架构师:是的
项目经理:那为什么这种情况不是经常发生。
性能架构师:是的,因为有时候瓶颈首先发生在硬件或应用本身。这时候应用服务器还没有使用到所有的扩展性。
项目经理:这是事实。那么解决这种瓶颈的方法就是使用群集。如果有足够的硬件资源就在同一台机器上跑群集否则在多台机器中配置。
性能架构师:是的。这也是在这篇文章中所得到的。

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


Deepak Goel是Infosys技术有限公司软件工程实验室的技术架构师。

posted @ 2005-11-25 10:35 Dion 阅读(617) | 评论 (0)编辑 收藏

Quartz从入门到进阶

作者:Cavaness

译者:David_w_johnson


版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Cavaness;David_w_johnson
原文地址:http://www.onjava.com/pub/a/onjava/2005/09/28/what-is-quartz.html
中文地址:http://www.matrix.org.cn/resource/article/43/43968_Quartz.html
关键词: Quartz


Quartz

Quartz 是一个开源的作业调度框架,它完全由java写成,并设计用于J2SE和J2EE应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作 业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB作业预构建,JavaMail及其它,支持cron-like表达式等 等。

本文内容
1.        Quartz让任务调度简单
2.        Quartz的发展史
3.        上手Quartz
4.        Quartz内部架构
5.        作业
6.        作业管理和存储
7.        有效作业存储
8.        作业和触发器
9.        调度一个作业
10.        用调度器(Scheduler)调用你的作业
11.        编程调度同声明性调度
12.        有状态和无状态作业
13.        Quartz框架的其他特征
14.        Quartz下一步计划
15.        了解更多Quartz特征


你 曾经需要应用执行一个任务吗?这个任务每天或每周星期二晚上11:30,或许仅仅每个月的最后一天执行。一个自动执行而无须干预的任务在执行过程中如果发 生一个严重错误,应用能够知到其执行失败并尝试重新执行吗?你和你的团队是用java编程吗?如果这些问题中任何一个你回答是,那么你应该使用 Quartz调度器。

旁注:Matrix目前就大量使用到了Quartz。比如,排名统计功能的实现,在Jmatrix里通过Quartz定义了一个定时调度作业,在每天凌晨一点,作业开始工作,重新统计大家的Karma和排名等。
还有,RSS文件的生成,也是通过Quartz定义作业,每隔半个小时生成一次RSS XML文件。
所以Quartz使用的地方很多,本文无疑是一篇很好的入门和进阶的文章,在此,感谢David w Johnson的努力!


Quartz让作业调度简单

Quartz 是一个完全由java编写的开源作业调度框架。不要让作业调度这个术语吓着你。尽管Quartz框架整合了许多额外功能, 但就其简易形式看,你会发现它易用得简直让人受不了!。简单地创建一个实现org.quartz.Job接口的java类。Job接口包含唯一的方法:

public void execute(JobExecutionContext context) 
     throws JobExecutionException;


在 你的Job接口实现类里面,添加一些逻辑到execute()方法。一旦你配置好Job实现类并设定好调度时间表,Quartz将密切注意剩余时间。当调 度程序确定该是通知你的作业的时候,Quartz框架将调用你Job实现类(作业类)上的execute()方法并允许做它该做的事情。无需报告任何东西 给调度器或调用任何特定的东西。仅仅执行任务和结束任务即可。如果配置你的作业在随后再次被调用,Quartz框架将在恰当的时间再次调用它。

如 果你使用了其它流行的开源框架象struts,你会对Quartz的设计和部件感到舒适。虽然两个开源工程是解决完全不同的问题,还是有很多相似的之处, 就是开源软件用户每天感觉很舒适。Quartz能用在单机J2SE应用中,作为一个RMI服务器,也可以用在web应用中,甚至也可以用在J2EE应用服 务器中。

Quartz的发展史

尽 管Quartz今年开始受到人们注意,但还是暂时流行。Quartz由James House创建并最初于2001年春天被加入sourceforge工程。接下来的几年里,有许多新特征和版本出现,但是直到项目迁移到新的站点并成为 OpenSymphony项目家族的一员,才开始真正启动并受到应有的关注。
James House仍然和几个协助他的业余开发者参与大量开发工作。Quartz开发团队今年能发布几个新版本,包括当前正处在候选发布阶段的1.5版。

上手Quartz
Quartz工程驻留在OpenSymphony站点上。在Quartz站点上可以找到许多有用的资源:JavaDocs,包含指南的文档,CVS访问,用户和开发者论坛的连接,当然也有下载。
从 下载连接取得Quartz的发布版本,并且解压到到本地目录。这个下载文件包含了一个预先构建好的Quartz二进制文件(quartz.jar),你可 以将它放进自己的应用中。Quartz框架只需要少数的第三方库,并且这些三方库是必需的,你很可能已经在使用这些库了。

你要把 Quartz的安装目录的<quartz- install>/lib/core 和 <quartz-install>/lib/optional目录中的第三方库加进你自己的工程中。大多数第三方库是我们所熟知和喜欢的标准 Jakarta Commons库,像Commons Logging, Commons BeantUtils等等。
    
quartz.properties文件
Quartz 有一个叫做quartz.properties的配置文件,它允许你修改框架运行时环境。缺省是使用Quartz.jar里面的 quartz.properties文件。当然,你应该创建一个quartz.properties文件的副本并且把它放入你工程的classes目录中 以便类装载器找到它。quartz.properties样本文件如例1所示。

例1.quartz.properties文件允许修改Quartz运行环境:

#===============================================================
# Configure Main Scheduler Properties  
#===============================================================

org.quartz.scheduler.instanceName = QuartzScheduler
org.quartz.scheduler.instanceId = AUTO

#===============================================================
# Configure ThreadPool  
#===============================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount =  5
org.quartz.threadPool.threadPriority = 5

#===============================================================
# Configure JobStore  
#===============================================================

org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore


一旦将Quartz.jar文件和第三方库加到自己的工程里面并且quartz.properties文件在工程的classes目录中,就可以创建作业了。然而,在做这之前,我们暂且回避一下先简短讨论一下Quartz架构。

Quartz内部架构
在 规模方面,Quartz跟大多数开源框架类似。大约有300个java类和接口,并被组织到12个包中。这可以和Apache Struts把大约325个类和接口以及组织到11个包中相比。尽管规模几乎不会用来作为衡量框架质量的一个特性,但这里的关键是quarts内含很多功 能,这些功能和特性集是否成为、或者应该成为评判一个开源或非开源框架质量的因素。

Quartz调度器
Quartz 框架的核心是调度器。调度器负责管理Quartz应用运行时环境。调度器不是靠自己做所有的工作,而是依赖框架内一些非常重要的部件。Quartz不仅仅 是线程和线程管理。为确保可伸缩性,Quartz采用了基于多线程的架构。启动时,框架初始化一套worker线程,这套线程被调度器用来执行预定的作 业。这就是Quartz怎样能并发运行多个作业的原理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。本片文障中,我们会多次提到线程池管 理,但Quartz里面的每个对象是可配置的或者是可定制的。所以,例如,如果你想要插进自己线程池管理设施,我猜你一定能!

作业
用Quartz 的行话讲,作业是一个执行任务的简单java类。任务可以是任何java代码。只需你实现org.quartz.Job接口并且在出现严重错误情况下抛出 JobExecutionException异常即可。Job接口包含唯一的一个方法execute(),作业从这里开始执行。一旦实现了Job接口和 execute()方法,当Quartz确定该是作业运行的时候,它将调用你的作业。Execute()方法内就完全是你要做的事情。下面有一些你要在作 业里面做事情的例子:
·        用JavaMail(或者用其他的像Commons Net一样的邮件框架)发送邮件
·        创建远程接口并且调用在EJB上的方法
·        获取Hibernate Session,查询和更新关系数据库里的数据
·        使用OSWorkflow并且从作业调用一个工作流
·        使用FTP和到处移动文件
·        调用Ant构建脚本开始预定构建

这种可能性是无穷的,正事这种无限可能性使得框架功能如此强大。Quartz给你提供了一个机制来建立具有不同粒度的、可重复的调度表,于是,你只需创建一个java类,这个类被调用而执行任务。

作业管理和存储
作 业一旦被调度,调度器需要记住并且跟踪作业和它们的执行次数。如果你的作业是30分钟后或每30秒调用,这不是很有用。事实上,作业执行需要非常准确和即 时调用在被调度作业上的execute()方法。Quartz通过一个称之为作业存储(JobStore)的概念来做作业存储和管理。


有效作业存储

Quartz 提供两种基本作业存储类型。第一种类型叫做RAMJobStore,它利用通常的内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和运行。对 许多应用来说,这种作业存储已经足够了。然而,因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。 如果你需要在重新启动之间持久化调度信息,则将需要第二种类型的作业存储。

第二种类型的作业存储实际上提供两种不同的实现,但两种实现一 般都称为JDBC作业存储。两种JDBC作业存储都需要JDBC驱动程序和后台数据库来持久化调度程序信息。这两种类型的不同在于你是否想要控制数据库事 务或这释放控制给应用服务器例如BEA's WebLogic或Jboss。(这类似于J2EE领域中,Bean管理的事务和和容器管理事务之间的区别)
这两种JDBC作业存储是:

·        JobStoreTX:当你想要控制事务或工作在非应用服务器环境中是使用
·        JobStoreCMT:当你工作在应用服务器环境中和想要容器控制事务时使用。

JDBC作业存储为需要调度程序维护调度信息的用户而设计。

作业和触发器

Quartz 设计者做了一个设计选择来从调度分离开作业。Quartz中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是 SimpleTrigger和CronTrigger。SimpleTrigger为需要简单打火调度而设计。典型地,如果你需要在给定的时间和重复次数 或者两次打火之间等待的秒数打火一个作业,那么SimpleTrigger适合你。另一方面,如果你有许多复杂的作业调度,那么或许需要 CronTrigger。

CronTrigger是基于Calendar-like调度的。当你需要在除星期六和星期天外的每天上午10点半执行作业时,那么应该使用CronTrigger。正如它的名字所暗示的那样,CronTrigger是基于Unix克隆表达式的。

作为一个例子,下面的Quartz克隆表达式将在星期一到星期五的每天上午10点15分执行一个作业。
0 15 10 ? * MON-FRI

下面的表达式
0 15 10 ? * 6L 2002-2005
将在2002年到2005年的每个月的最后一个星期五上午10点15分执行作业。

你不可能用SimpleTrigger来做这些事情。你可以用两者之中的任何一个,但哪个跟合适则取决于你的调度需要。


调度一个作业

让 我们通过看一个例子来进入实际讨论。现假定你管理一个部门,无论何时候客户在它的FTP服务器上存储一个文件,都得用电子邮件通知它。我们的作业将用 FTP登陆到远程服务器并下载所有找到的文件。然后,它将发送一封含有找到和下载的文件数量的电子邮件。这个作业很容易就帮助人们整天从手工执行这个任务 中解脱出来,甚至连晚上都无须考虑。我们可以设置作业循环不断地每60秒检查一次,而且工作在7×24模式下。这就是Quartz框架完全的用途。

首先创建一个Job类,将执行FTP和Email逻辑。下例展示了Quartz的Job类,它实现了org.quartz.Job接口。

例2.从FTP站点下载文件和发送email的Quartz作业

public class ScanFTPSiteJob implements Job {
    private static Log logger = LogFactory.getLog(ScanFTPSiteJob.class);

    /*
     * Called the scheduler framework at the right time
     */
    public void execute(JobExecutionContext context)
            throws JobExecutionException {

        JobDataMap jobDataMap = context.getJobDataMap();

        try {
            // Check the ftp site for files
            File[] files = JobUtil.checkForFiles(jobDataMap);

            JobUtil.sendEmail(jobDataMap, files);
        } catch (Exception ex) {
            throw new JobExecutionException(ex.getMessage());
        }
    }
}


我 们故意让ScanFTPSiteJob保持很简单。我们为这个例子创建了一个叫做JobUtil的实用类。它不是Quartz的组成部分,但对构建各种作 业能重用的实用程序库来说是有意义的。我们可以轻易将那种代码组织进作业类中,quarts 调度器一样好用,因为我们一直在使用quarts,所以那些代码可继续重用。

JobUtil.checkForFiles() and JobUtil.sendEmail()方法使用的参数是Quartz创建的JobDataMap的实例。实例为每个作业的执行而创建,它是向作业类传递配置参数的方法。
这里并没有展示JobUtil的实现,但我们能用Jakarta上的Commons Net轻易地实现FTP和Email功能。

用调度器调用作业

首先创建一个作业,但为使作业能被调度器调用,你得向调度程序说明你的作业的调用时间和频率。这个事情由与作业相关的触发器来完成。因为我们仅仅对大约每60秒循环调用作业感兴趣,所以打算使用SimpleTrigger。

作业和触发器通过Quartz调度器接口而被调度。我们需要从调度器工厂类取得一个调度器的实例。最容易的办法是调用StdSchedulerFactory这个类上的静态方法getDefaultScheduler()。
使用Quartz框架,你需要调用start()方法来启动调度器。例3的代码遵循了大多数Quartz应用的一般模式:创建一个或多个作业,创建和设置触发器,用调度器调度作业和触发器,启动调度器。

例3.Quartz作业通过Quartz调度器而被调度

public class MyQuartzServer {

    public static void main(String[] args) {
        MyQuartzServer server = new MyQuartzServer();

        try {
            server.startScheduler();
        } catch (SchedulerException ex) {
            ex.printStackTrace();
        }
    }

    protected void startScheduler() throws SchedulerException {

        // Use the factory to create a Scheduler instance
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // JobDetail holds the definition for Jobs
        JobDetail jobDetail =
new JobDetail("ScanFTPJob", Scheduler.DEFAULT_GROUP,
                ScanFTPSiteJob.class);

// Store job parameters to be used within execute()
jobDetail.getJobDataMap().put(
"FTP_HOST",
"\\home\\cavaness\\inbound");

        // Other neccessary Job parameters here

        // Create a Trigger that fires every 60 seconds
        Trigger trigger = TriggerUtils.makeSecondlyTrigger(60);
        
        // Setup the Job and Trigger with the Scheduler
        scheduler.scheduleJob(jobDetail, trigger );
        
        // Start the Scheduler running
        scheduler.start();
    }
}



编程调度同声明性调度

例3中,我们通过编程的方法调度我们的ScanFTPSiteJob作业。就是说,我们用java代码来设置作业和触发器。Quartz框架也支持在xml文件里面申明性的设置作业调度。申明性方法允许我们更快速地修改哪个作业什么时候被执行。

Quartz 框架有一个插件,这个插件负责读取xml配置文件。xml配置文件包含了关于启动Quartz应用的作业和触发器信息。所有xml文件中的作业连同相关的 触发器都被加进调度器。你仍然需要编写作业类,但配置那些作业类的调度器则非常动态化。例4展示了一个用申明性方式执行与例3代码相同的逻辑的xml配置 文件。

例4.能使用xml文件调度的作业

<?xml version='1.0' encoding='utf-8'?>
<quartz>
    <job>
        <job-detail>
            <name>ScanFTPSiteJob</name>
            <group>DEFAULT</group>
            <description>
                A job that scans an ftp site for files
            </description>
            <job-class>ScanFTPSiteJob</job-class>

            <job-data-map allows-transient-data="true">
                <entry>
                    <key>FTP_HOST</key>
                    <value>\home\cavaness\inbound</value>
                </entry>
                
                <!--  Other neccessary Job parameters here -->

            </job-data-map>
        </job-detail>

        <trigger>
            <simple>
                <name>ScanFTPSiteJobTrigger</name>
                <group>DEFAULT</group>
                <job-name>ScanFTPSiteJob</job-name>
                <job-group>DEFAULT</job-group>
                <start-time>2005-09-11 6:10:00 PM</start-time>
                <!-- repeat indefinitely every 60 seconds -->
                <repeat-count>-1</repeat-count>
                <repeat-interval>60000</repeat-interval>
            </simple>
        </trigger>

    </job>
</quartz>


你可以将xml文件中的元素跟例3代码作个比较,它们从概念上来看是相同的。使用例4式的申明性方法的好处是维护变得极其简单,只需改变xml配置文件和重新启动Quartz应用即可。无须修改代码,无须重新编译,无须重新部署。

有状态和无状态作业

在本文中你所看到的作业到是无状态的。这意味着在两次作业执行之间,不会去维护作业执行时JobDataMap的状态改变。如果你需要能增、删,改JobDataMap的值,而且能让作业在下次执行时能看到这个状态改变,则需要用Quartz有状态作业。
如 果你是一个有经验的EJB开发者的话,深信你会立即退缩,因为有状态带有负面含义。这主要是由于EJB带来的伸缩性问题。Quartz有状态作业实现了 org.quartz.StatefulJob接口。无状态和有状态作业的关键不同是有状态作业在每次执行时只有一个实例。大多数情况下,有状态的作业不 回带来大的问题。然而,如果你有一个需要频繁执行的作业或者需要很长时间才能完成的作业,那么有状态作业可能给你带来伸缩性问题。

Quartz框架的其他特征

Quartz框架有一个丰富的特征集。事实上,quarts有太多特性以致不能在一种情况中全部领会,下面列出了一些有意思的特征,但没时间在此详细讨论。

监听器和插件

每 个人都喜欢监听和插件。今天,几乎下载任何开源框架,你必定会发现支持这两个概念。监听是你创建的java类,当关键事件发生时会收到框架的回调。例如, 当一个作业被调度、没有调度或触发器终止和不再打火时,这些都可以通过设置来来通知你的监听器。Quartz框架包含了调度器监听、作业和触发器监听。你 可以配置作业和触发器监听为全局监听或者是特定于作业和触发器的监听。

一旦你的一个具体监听被调用,你就能使用这个技术来做一些你想要在 监听类里面做的事情。例如,你如果想要在每次作业完成时发送一个电子邮件,你可以将这个逻辑写进作业里面,也可以JobListener里面。写进 JobListener的方式强制使用松耦合有利于设计上做到更好。

Quartz插件是一个新的功能特性,无须修改Quartz源码便可 被创建和添加进Quartz框架。他为想要扩展Quartz框架又没有时间提交改变给Quartz开发团队和等待新版本的开发人员而设计。如果你熟悉 Struts插件的话,那么完全可以理解Quartz插件的使用。

与其Quartz提供一个不能满足你需要的有限扩展点,还不如通过使用插件来拥有可修整的扩展点。

集群Quartz应用
Quartz应用能被集群,是水平集群还是垂直集群取决于你自己的需要。集群提供以下好处:
·        伸缩性
·        搞可用性
·        负载均衡
目前,Quartz只能借助关系数据库和JDBC作业存储支持集群。将来的版本这个制约将消失并且用RAMJobStore集群将是可能的而且将不需要数据库的支持。

Quartz web应用
使 用框架几个星期或几个月后,Quartz用户所显示的需求之一是需要集成Quartz到图形用户界面中。目前Quartz框架已经有一些工具允许你使用 Java servlet来初始化和启动Quartz。一旦你可以访问调度器实例,你就可以把它存储在web容器的servlet上下文中 (ServletContext中)并且可以通过调度器接口管理调度环境。

幸运的是一些开发者已正影响着单机Quartz web应用,它用来更好地管理调度器环境。构建在若干个流行开源框架如Struts和Spring之上的图形用户界面支持很多功能,这些功能都被包装进一个简单接口。GUI的一个画面如图1所示:


image
图1.Quartz Web应用允许比较容易地管理Quartz环境。

Quartz的下一步计划

Quartz是一个活动中的工程。Quartz开发团队明确表示不会停留在已有的荣誉上。Quartz下一个主要版本已经在启动中。你可以在OpenSymphony的 wiki上体验一下Quartz 2.0的设计和特征。
总之,Quartz用户每天都自由地添加特性建议和设计创意以便能被核心框架考虑(看重)。

了解更多Quartz特征

当你开始使用Quartz框架的更多特性时,User and Developer Forum论坛变成一个回答问题和跟其他Quartz用户沟通的极其有用的资源。经常去逛逛这个论坛时很有好处的,你也可以依靠James House来共享与你的需要相关的知识和意见。

这个论坛时免费的,你不必登陆便可以查找和查看归档文件。然而,如果你觉得这个论坛比较好而且需要向某人回复问题时,你必须得申请一个免费帐号并用该帐号登陆。

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

Chuck Cavaness毕业于Georgia Tech并获得计算机科学与技术学位。他在健康,银行,B2B领域开发过基于java的企业系统。同时也是O'Reilly出版的两本书 Programming Jakarta Struts 和 Jakarta Struts Pocket Reference的作者。

posted @ 2005-11-25 10:34 Dion 阅读(1143) | 评论 (0)编辑 收藏

作者:Brad Neuberg

译者:boool


版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Brad Neuberg;boool
原文地址:http://www.onjava.com/pub/a/onjava/2005/10/26/ajax-handling-bookmarks-and-back-button.html
中文地址:http://www.matrix.org.cn/resource/article/43/43972_AJAX.html
关键词: ajax;bookmarks;back button


这篇文章描述了一个支持AJAX应用书签和回退按钮的开源的javascript库。在这个指南的最后,开发者将会得出一个甚至不是Google Maps 或者 Gmail那样处理的AJAX的解决方案:健壮的,可用的书签和向前向后的动作能够象其他的web页面一样正确的工作。

AJAX:怎样去控制书签和回退按钮 这篇文章说明了一个重要的成果,AJAX应用目前面对着书签和回退按钮的应用,描述了非常简单的历史库(Really Simple History),一个开源的解决这类问题的框架,并提供了一些能够运行的例子。

这 篇文章描述的主要问题是双重的,一是一个隐藏的html 表单被用作一个大而短生命周期的客户端信息的session缓存,这个缓存对在这个页面上前进回退是强壮的。二是一个锚连接和隐藏的iframes的组合 用来截取和记录浏览器的历史事件,来实现前进和回退的按钮。这两个技术都被用一个简单的javascript库来封装,以利于开发者的使用。

存在的问题
书签和回退按钮在传统的多页面的web应用上能顺利的运行。当用户在网站上冲浪时,他们的浏览器地址栏能更新URL,这些URL可以被粘贴到的email或者添加到书签以备以后的使用。回退和前进按钮也可以正常运行,这可以使用户在他们访问的页面间移动。

AJAX应用是与众不同的,然而,他也是在单一web页面上成熟的程序。浏览器不是为AJAX而做的—AJAX他捕获过去的事件,当web应用在每个鼠标点击时刷新页面。

在象Gmail那样的AJAX软件里,浏览器的地址栏正确的停留就象用户在选择和改变应用的状态时,这使得作书签到特定的应用视图里变得不可能。此外,如果用户按下了他们的回退按钮去返回上一个操作,他们会惊奇的发现浏览器将完全离开原来他所在的应用的web页面。

解决方案
开 源的Really Simply History(RSH)框架解决了这些问题,他带来了AJAX应用的作书签和控制前进后退按钮的功能。RSH目前还是beta版,在 Firefox1.0上,Netscape7及以上,和IE6及以上运行。Safari现在还不支持(要得到更详细的说明,请看我的weblog中的文章Coding in Paradise: Safari: No DHTML History Possible).

目前存在的几个AJAX框架可以帮助我们做书签和发布历史,然而所有的框架都因为他们的实现而被几个重要的bug困扰(请看Coding in Paradise: AJAX History Libraries 得知详情)。此外,许多AJAX历史框架集成绑定到较大的库上,比如Backbase 和 Dojo,这些框架提供了与传统AJAX应用不同的编程模型,强迫开发者去采用一整套全新的方式去获得浏览器的历史相关的功能。

相应的,RSH是一个简单的模型,能被包含在已经存在的AJAX系统中。而且,Really Simple History库使用了一些技巧去避免影响到其他历史框架的bug.

Really Simple History框架由2个javascript类库组成,分别叫DhtmlHistory 和 HistoryStorage.

DhtmlHistory 类提供了一个对AJAX应用提取历史的功能。.AJAX页面add() 历史事件到浏览器里,指定新的地址和关联历史数据。DhtmlHistory 类用一个锚的hash表更新浏览器现在的URL,比如#new-location ,然后用这个新的URL关联历史数据。AJAX应用注册他们自己到历史监听器里,然后当用户用前进和后退按钮导航的时候,历史事件被激发,提供给浏览器新 的地址和调用add()持续保留数据。

第二个类HistoryStorage,允许开发者存储任意大小的历史数据。一般的页面,当一个用 户导航到一个新的网站,浏览器会卸载和清除所有这个页面的应用和javascript状态信息。如果用户用回退按钮返回过来了,所有的数据已经丢失了。 HistoryStorage 类解决了这个问题,他有一个api 包含简单的hashtable方法比如put(),get(),hasKey()。这些方法允许开发者在离开web页面时存储任意大小的数据,当用户点了 回退按钮返回时,数据可以通过HistoryStorage 类被访问。我们通过一个隐藏的表单域(a hidden form field),利用浏览器即使在用户离开web页面也会自动保存表单域值的这个特性,完成这个功能。

让我们立即进入一个简单的例子吧。

示例1
首先,任何一个想使用Really Simple History框架的页面必须包含(include)dhtmlHistory.js 脚本。

<!-- Load the Really Simple 
     History framework -->
<script type="text/javascript"
        src="../../framework/dhtmlHistory.js">
</script>


DHTML History 应用也必须在和AJAX web页面相同的目录下包含一个叫blank.html 的指定文件,这个文件被Really Simple History框架绑定而且对IE来说是必需的。另一方面,RSH使用一个hidden iframe 来追踪和加入IE历史的改变,为了正确的执行功能,这个iframe需要指向一个真正的地址,不需要blank.html。

RSH框架创建了一个叫dhtmlHistory 的全局对象,作为操作浏览器历史的入口。使用dhtmlHistory 的第一步需要在页面加载后初始化这个对象。

window.onload = initialize;
    
function initialize() {
  // initialize the DHTML History
  // framework
  dhtmlHistory.initialize();


然后,开发者使用dhtmlHistory.addListener()方法去订阅历史改变事件。这个方法获取一个javascript回调方法,当一个DHTML历史改变事件发生时他将收到2个自变量,新的页面地址,和任何可选的而且可以被关联到这个事件的历史数据。

indow.onload = initialize;
    
function initialize() {
  // initialize the DHTML History
  // framework
  dhtmlHistory.initialize();
  
  // subscribe to DHTML history change
  // events
  dhtmlHistory.addListener(historyChange);


historyChange()方法是简单易懂得,它是由一个用户导航到一个新地址后收到的新地址(newLocation)和一个关联到事件的可选的历史数据historyData 构成的。

/** Our callback to receive history change
     events. */
function historyChange(newLocation,
                       historyData) {
  debug("A history change has occurred: "
        + "newLocation="+newLocation
        + ", historyData="+historyData,
        true);
}


上面用到的debug()方法是例子代码中定义的一个工具函数,在完整的下载例子里有。debug()方法简单的在web页面上打一条消息,第2个Boolean变量,在代码里是true,控制一个新的debug消息打印前是否要清除以前存在的所有消息。

一个开发者使用add()方法加入历史事件。加入一个历史事件包括根据历史的改变指定一个新的地址,就像"edit:SomePage"标记, 还提供一个事件发生时可选的会被存储到历史数据historyData值.

window.onload = initialize;
    
function initialize() {
  // initialize the DHTML History
  // framework
  dhtmlHistory.initialize();
  
  // subscribe to DHTML history change
  // events
  dhtmlHistory.addListener(historyChange);
      
  // if this is the first time we have
  // loaded the page...
  if (dhtmlHistory.isFirstLoad()) {
    debug("Adding values to browser "
          + "history", false);
    // start adding history
    dhtmlHistory.add("helloworld",
                     "Hello World Data");
    dhtmlHistory.add("foobar", 33);
    dhtmlHistory.add("boobah", true);
      
    var complexObject = new Object();
    complexObject.value1 =
                  "This is the first value";
    complexObject.value2 =
                  "This is the second data";
    complexObject.value3 = new Array();
    complexObject.value3[0] = "array 1";
    complexObject.value3[1] = "array 2";
      
    dhtmlHistory.add("complexObject",
                     complexObject);


在add ()方法被调用后,新地址立刻被作为一个锚值显示在用户的浏览器的URL栏里。例如,一个AJAX web页面停留在http://codinginparadise.org/my_ajax_app,调用了dhtmlHistory.add ("helloworld", "Hello World Data" 后,用户将在浏览器的URL栏里看到下面的地址
http://codinginparadise.org/my_ajax_app#helloworld

然 后他们可以把这个页面做成书签,如果他们使用这个书签,你的AJAX应用可以读出#helloworld值然后使用她去初始化web页面。Hash里的地 址值被Really Simple History  框架显式的编码和解码(URL encoded and decoded) (这是为了解决字符的编码问题)

对当AJAX地址改变时保存更多的复杂的状态来说,historyData  比一个更容易的匹配一个 URL的东西更有用。他是一个可选的值,可以是任何javascript类型,比如Number, String, 或者 Object 类型。有一个例子是用这个在一个多文本编辑器(rich text editor)保存所有的文本,例如,如果用户从这个页面漂移(或者说从这个页面导航到其他页面,离开了这个页面)走。当一个用户再回到这个地址,浏览器 会把这个对象返回给历史改变侦听器(history change listener)。

开发者可以提供一个完全的 historyData 的javascript对象,用嵌套的对象objects和排列arrays来描绘复杂的状态。只要是JSON (JavaScript Object Notation)  允许的那么在历史数据里就是允许的,包括简单数据类型和null型。DOM的对象和可编程的浏览器对象比如 XMLHttpRequest ,不会被保存。注意historyData 不会被书签持久化,如果浏览器关掉,或者浏览器的缓存被清空,或者用户清除历史的时候,会消失掉。

使用dhtmlHistory 最后一步,是isFirstLoad() 方法。如果你导航到一个web页面,再跳到一个不同的页面,然后按下回退按钮返回起始的网站,第一页将完全重新装载,并激发onload事件。这样能产生 破坏性,当代码在第一次装载时想要用某种方式初始化页面的时候,不会再刷新页面。isFirstLoad() 方法让区别是最开始第一次装载页面,还是相对的,在用户导航回到他自己的浏览器历史中记录的网页时激发load事件,成为可能。

在例子代码中,我们只想在第一次页面装载的时候加入历史事件,如果用户在第一次装载后,按回退按钮返回页面,我们就不想重新加入任何历史事件。

window.onload = initialize;
    
function initialize() {
  // initialize the DHTML History
  // framework
  dhtmlHistory.initialize();
  
  // subscribe to DHTML history change
  // events
  dhtmlHistory.addListener(historyChange);
      
  // if this is the first time we have
  // loaded the page...
  if (dhtmlHistory.isFirstLoad()) {
    debug("Adding values to browser "
          + "history", false);
    // start adding history
    dhtmlHistory.add("helloworld",
                     "Hello World Data");
    dhtmlHistory.add("foobar", 33);
    dhtmlHistory.add("boobah", true);
      
    var complexObject = new Object();
    complexObject.value1 =
                  "This is the first value";
    complexObject.value2 =
                  "This is the second data";
    complexObject.value3 = new Array();
    complexObject.value3[0] = "array 1";
    complexObject.value3[1] = "array 2";
      
    dhtmlHistory.add("complexObject",
                     complexObject);


让 我们继续使用historyStorage 类。类似dhtmlHistory ,historyStorage通过一个叫historyStorage的单一全局对象来显示他的功能,这个对象有几个方法来伪装成一个hash table, 象put(keyName, keyValue), get(keyName), and hasKey(keyName).键名必须是字符,同时键值可以是复杂的javascript对象或者甚至是xml格式的字符。在我们源码source code的例子中,我们put() 简单的XML  到historyStorage 在页面第一次装载时。

window.onload = initialize;
    
function initialize() {
  // initialize the DHTML History
  // framework
  dhtmlHistory.initialize();
  
  // subscribe to DHTML history change
  // events
  dhtmlHistory.addListener(historyChange);
      
  // if this is the first time we have
  // loaded the page...
  if (dhtmlHistory.isFirstLoad()) {
    debug("Adding values to browser "
          + "history", false);
    // start adding history
    dhtmlHistory.add("helloworld",
                     "Hello World Data");
    dhtmlHistory.add("foobar", 33);
    dhtmlHistory.add("boobah", true);
      
    var complexObject = new Object();
    complexObject.value1 =
                  "This is the first value";
    complexObject.value2 =
                  "This is the second data";
    complexObject.value3 = new Array();
    complexObject.value3[0] = "array 1";
    complexObject.value3[1] = "array 2";
      
    dhtmlHistory.add("complexObject",
                     complexObject);
                    
    // cache some values in the history
    // storage
    debug("Storing key 'fakeXML' into "
          + "history storage", false);
    var fakeXML =
      '<?xml version="1.0" '
      +      'encoding="ISO-8859-1"?>'
      +      '<foobar>'
      +         '<foo-entry/>'
      +      '</foobar>';
    historyStorage.put("fakeXML", fakeXML);
  }


然后,如果用户从这个页面漂移走(导航走)又通过返回按钮返回了,我们可以用get()提出我们存储的值或者用haskey()检查他是否存在。

window.onload = initialize;
    
function initialize() {
  // initialize the DHTML History
  // framework
  dhtmlHistory.initialize();
  
  // subscribe to DHTML history change
  // events
  dhtmlHistory.addListener(historyChange);
      
  // if this is the first time we have
  // loaded the page...
  if (dhtmlHistory.isFirstLoad()) {
    debug("Adding values to browser "
          + "history", false);
    // start adding history
    dhtmlHistory.add("helloworld",
                     "Hello World Data");
    dhtmlHistory.add("foobar", 33);
    dhtmlHistory.add("boobah", true);
      
    var complexObject = new Object();
    complexObject.value1 =
                  "This is the first value";
    complexObject.value2 =
                  "This is the second data";
    complexObject.value3 = new Array();
    complexObject.value3[0] = "array 1";
    complexObject.value3[1] = "array 2";
      
    dhtmlHistory.add("complexObject",
                     complexObject);
                    
    // cache some values in the history
    // storage
    debug("Storing key 'fakeXML' into "
          + "history storage", false);
    var fakeXML =
      '<?xml version="1.0" '
      +      'encoding="ISO-8859-1"?>'
      +      '<foobar>'
      +         '<foo-entry/>'
      +      '</foobar>';
    historyStorage.put("fakeXML", fakeXML);
  }
  
  // retrieve our values from the history
  // storage
  var savedXML =
              historyStorage.get("fakeXML");
  savedXML = prettyPrintXml(savedXML);
  var hasKey =
           historyStorage.hasKey("fakeXML");
  var message =
    "historyStorage.hasKey('fakeXML')="
    + hasKey + "<br>"
    + "historyStorage.get('fakeXML')=<br>"
    + savedXML;
  debug(message, false);
}


prettyPrintXml() 是一个第一在例子源码full example source code中的工具方法。这个方法准备简单的xml显示在web page ,方便调试。

注 意数据只是在使用页面的历史时被持久化,如果浏览器关闭了,或者用户打开一个新的窗口又再次键入了ajax应用的地址,历史数据对这些新的web页面是不 可用的。历史数据只有在用前进或回退按钮时才被持久化,而且在用户关闭浏览器或清空缓存的时候会消失掉。想真正的长时间的持久化,请看Ajax MAssive Storage System (AMASS).
我们的简单示例已经完成。演示他(Demo it)或者下载全部的源代码(download the full source code.)

示例2
我 们的第2个例子是一个简单的模拟ajax email  应用的示例,叫O'Reilly Mail,类似Gmail. O'Reilly Mail描述了怎样使用dhtmlHistory类去控制浏览器的历史,和怎样使用historyStorage对象去缓存历史数据。

O'Reilly Mail 用户接口(user interface)有两部分。在页面的左边是一个有不同email文件夹和选项的菜单,例如 收件箱,草稿,等等。当一个用户选择了一个菜单项,比如收件箱,我们用这个菜单项的内容更新右边的页面。在一个实际应用中,我们会远程取得和显示选择的信 箱内容,不过在O'Reilly Mail里,我们简单的显示选择的选项。

O'Reilly Mail使用Really Simple History 框架向浏览器历史里加入菜单变化和更新地址栏,允许用户利用浏览器的回退和前进按钮对应用做书签和跳到上一个变化的菜单。

我 们加入一个特别的菜单项,地址簿,来描绘historyStorage 能够怎样被使用。地址簿是一个由联系的名字电子邮件和地址组成的javascript数组,在一个真实的应用里我们会取得他从一个远程的服务器。不过,在 O'Reilly Mail里,我们在本地创建这个数组,加入几个名字电子邮件和地址,然后把他们存储在historyStorage 对象里。如果用户离开了这个web页面以后又返回的话,O'Reilly Mail应用重新从缓存里得到地址簿,胜过(不得不)再次访问远程服务器。

地址簿是在我们的初始化initialize()方法里存储和重新取得的

/** Our function that initializes when the page
    is finished loading. */
function initialize() {
   // initialize the DHTML History framework
   dhtmlHistory.initialize();
  
   // add ourselves as a DHTML History listener
   dhtmlHistory.addListener(handleHistoryChange);

   // if we haven't retrieved the address book
   // yet, grab it and then cache it into our
   // history storage
   if (window.addressBook == undefined) {
      // Store the address book as a global
      // object.
      // In a real application we would remotely
      // fetch this from a server in the
      // background.
      window.addressBook =
         ["Brad Neuberg 'bkn3@columbia.edu'",
          "John Doe 'johndoe@example.com'",
          "Deanna Neuberg 'mom@mom.com'"];
          
      // cache the address book so it exists
      // even if the user leaves the page and
      // then returns with the back button
      historyStorage.put("addressBook",
                         addressBook);
   }
   else {
      // fetch the cached address book from
      // the history storage
      window.addressBook =
               historyStorage.get("addressBook");
   }


处 理历史变化的代码是简单的。在下面的代码中,当用户不论按下回退还是前进按钮handleHistoryChange 都被调用。我们得到新的地址(newLocation) 使用他更新我们的用户接口来改变状态,通过使用一个叫displayLocation的O'Reilly Mail的工具方法。

/** Handles history change events. */
function handleHistoryChange(newLocation,
                             historyData) {
   // if there is no location then display
   // the default, which is the inbox
   if (newLocation == "") {
      newLocation = "section:inbox";
   }
  
   // extract the section to display from
   // the location change; newLocation will
   // begin with the word "section:"
   newLocation =
         newLocation.replace(/section\:/, "");
  
   // update the browser to respond to this
   // DHTML history change
   displayLocation(newLocation, historyData);
}

/** Displays the given location in the
    right-hand side content area. */
function displayLocation(newLocation,
                         sectionData) {
   // get the menu element that was selected
   var selectedElement =
            document.getElementById(newLocation);
            
   // clear out the old selected menu item
   var menu = document.getElementById("menu");
   for (var i = 0; i < menu.childNodes.length;
                                          i++) {
      var currentElement = menu.childNodes[i];
      // see if this is a DOM Element node
      if (currentElement.nodeType == 1) {
         // clear any class name
         currentElement.className = "";
      }                                      
   }
  
   // cause the new selected menu item to
   // appear differently in the UI
   selectedElement.className = "selected";
  
   // display the new section in the right-hand
   // side of the screen; determine what
   // our sectionData is
  
   // display the address book differently by
   // using our local address data we cached
   // earlier
   if (newLocation == "addressbook") {
      // format and display the address book
      sectionData = "<p>Your addressbook:</p>";
      sectionData += "<ul>";
      
      // fetch the address book from the cache
      // if we don't have it yet
      if (window.addressBook == undefined) {
         window.addressBook =
               historyStorage.get("addressBook");
      }
      
      // format the address book for display
      for (var i = 0;
               i < window.addressBook.length;
                     i++) {
         sectionData += "<li>"
                        + window.addressBook[i]
                        + "</li>";                  
      }
      
      sectionData += "</ul>";
   }
  
   // If there is no sectionData, then
   // remotely retrieve it; in this example
   // we use fake data for everything but the
   // address book
   if (sectionData == null) {
      // in a real application we would remotely
      // fetch this section's content
      sectionData = "<p>This is section: "
         + selectedElement.innerHTML + "</p>";  
   }
  
   // update the content's title and main text
   var contentTitle =
         document.getElementById("content-title");
   var contentValue =
         document.getElementById("content-value");
   contentTitle.innerHTML =
                        selectedElement.innerHTML;
   contentValue.innerHTML = sectionData;
}


演示(Demo)O'Reilly Mail或者下载(download)O'Reilly Mail的源代码。

结束语
你现在已经学习了使用Really Simple History API 让你的AJAX应用响应书签和前进回退按钮,而且有代码可以作为创建你自己的应用的素材。我热切地期待你利用书签和历史的支持完成你的AJAX创造。

资源
·onjava.com:onjava.com
·Matrix-Java开发者社区:http://www.matrix.org.cn/
·Download all sample code for this article.
·Download the Really Simple History framework.
·Demo O'Reilly Mail or download the O'Reilly Mail source code. The full example download also includes more examples for you to play with.
·Coding in Paradise: The author's weblog, covering AJAX, DHTML, and Java techniques and new developments in collaborative technologies, such as WikiWikis.


感谢
特别的要感谢每个检阅这篇文章的the Really Simple History框架的人:
Michael Eakes, Jeremy Sevareid, David Barrett, Brendon Wilson, Dylan Parker, Erik Arvidsson, Alex Russell, Adam Fisk, Alex Lynch, Joseph Hoang Do, Richard MacManus, Garret Wilson, Ray Baxter, Chris Messina, and David Weekly.
posted @ 2005-11-25 10:33 Dion 阅读(1025) | 评论 (0)编辑 收藏

方法一:

调用Windows的DOS命令,从输出结果中读取MAC地址:

public static String getMACAddress() {

String address = "";
String os = System.getProperty("os.name");
if ( os != null && os.startsWith("Windows")) {
try {
String command = "cmd.exe /c ipconfig /all";
Process p = Runtime.getRuntime().exec(command);
BufferedReader br =
new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
if (line.indexOf("Physical Address") > 0) {
int index = line.indexOf(":");
index += 2;
address = line.substring(index);
break;
}
}
br.close();
return address.trim();
}
catch (IOException e) { }
}
return address;
}

We can replace the "ipconfig" to "ping x.x.x.x" and "arp -a"...We can get the mac list...haha!!

缺点:只能取得服务器端MAC地址.如果要取得客户端的MAC地址,需用Applet.只针对MS-WIN系统.

 

方法二:

可以用JS或vbscript来调用WMI接口来获取Client端的MAC地址.


 
 
 
 
 
 
 
  
  

  
  

  


  

   
   
   

  
 

忘了附上原文的出处了:
How to get IP address of the browser when its operating behind a proxy/firewall? (applets...activex....??)
http://www.faqts.com/knowledge_base/view.phtml/aid/9005/fid/125

关于WMI的详细信息可以参看MSDN:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/wmi_tasks_for_scripts_and_applications.asp

平心而论,WMI的很强大的。原先需要动用重量级编程工具才能做到的事,现在用js/vbscript就可以做了。

获取多块网卡的MAC地址:

if(objObject.MACAddress != null && objObject.MACAddress != "undefined"){
                         MACAddr = objObject.MACAddress;
                         alert( MACAddr );
                   }

缺点:需要ActiveX支持.对MS-WIN系统有效.

方法三:

想137口发送UDP查询:

WINDOWS平台的客户端(当获取时它转换为服务端角色),NETBIOS协议在137口上,我们只要向它的137口发送UDP查询,获取它的返回值就可以获取到它所有的网卡地址.

以上内容来自dev2dev的讨论帖:

http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=121&threadID=12941&tstart=0

 

posted on 2005-01-20 08:50 eamoi 阅读(636) 评论(1)  编辑 收藏 收藏至365Key 所属分类: Java

评论: # Java系统如何获取客户端的MAC地址? [TrackBack] 2005-01-20 11:01 | eamoi
Ping Back来自:blog.csdn.net
eamoi引用了该文章,地址:http://blog.csdn.net/eamoi/archive/2005/01/20/260611.aspx
posted @ 2005-11-23 20:33 Dion 阅读(9755) | 评论 (3)编辑 收藏

一个用于J2EE应用程序的Backbase Ajax前端

时间:2005-11-03
作者:Mark Schiefelbein
浏览次数: 628
本文关键字:AjaxRIARich Internet ApplicationbackbaseDev Toolbox Eclipse WebLogic
文章工具
推荐给朋友 推荐给朋友
打印文章 打印文章

  动态HTML技术已经出现了多年。最近,Google的最新Web应用程序GMail、Google Suggests和Google Maps,在前端页面中重新引入了基于标准的DHTML开发模型。Google证明了,DHTML开发模型能够让开发人员创建具有可视化吸引力和高度交互 式的Rich Internet Application(丰富网络应用程序,RIA)。

  Adaptive Path公司的Jesse James Garrett为这个基于标准的RIA开发模型创造了术语Ajax (Asynchronous JavaScript + XML)。与传统的基于页面的Web应用程序模型相比,Ajax有3点不同之处:

  • 有一个客户端引擎担任用户界面(UI)和服务器之间的中介。
  • 用户行为由客户端引擎处理,而不是生成发往服务器的页面请求。
  • XML数据在客户端引擎和服务器之间传输。

  换言之,Ajax解决方案包括一个客户端引擎,它用于呈现用户界面,并使用XML格式与服务器通信。这个引擎由很多JavaScript函数组成,位于Web浏览器中,它不需要插件,也不需要用户安装。

  基于Ajax的RIA正在迅速成为Web应用程序前端的基准,因为它可以同时提供二者的优点:丰富性和可达性。Ajax应用程序和桌面应用程序 一样丰富,响应高度灵敏,并且可以在一个页面上提供所有数据,无需刷新页面。它们还拥有基于标准的浏览器应用程序的可达性特点,这类应用程序可以在不具备 浏览器插件或客户端applet的情况下进行部署。

  Backbase所提供的Ajax软件具有以下特点:基于标准、功能全面且易于使用。Backbase Presentation Client (BPC)基于Ajax技术,它使用称为Backbase XML (BXML)的附加标签扩展了DHTML。Backbase XML Server Edition for J2EE (BXS)包含了一些服务器端的组件,利用这些组件,J2EE开发人员可以快速开发J2EE应用程序的Ajax前端。

  在本文中,我使用Backbase为Java Pet Store开发了一个基于Ajax的前端。该案例分析说明了如何使用Backbase技术作为J2EE应用程序的Ajax表示层。您可以查看文中所描述的应用程序的在线演示,网址是http://www.backbase.com/xmlserver

Backbase Ajax表示层

  Web开发人员应该能够轻松创建具有以下特点的Rich Internet Application (RIA):完全基于HTML标准(W3C),不需要最终用户安装插件,速度超快,能够在所有浏览器上进行操作,并与J2EE运行时和开发环境完全集成。 RIA利用客户端(Web浏览器)资源创建和管理用户界面,从而为最终用户提供一个响应灵敏而且具有应用程序风格的用户界面。

  这种方法最近被称为Ajax。Ajax这个术语的灵感来源于Gmail、Google Maps和Google Suggests这类应用程序,它把现有的浏览器技术提高到了一个新的水平上。RIA从根本上改进了在线应用程序的可用性和有效性。Ajax RIA只使用标准的浏览器技术(如JavaScript、XHTML和XMLHttpRequest对象)就做到了这一点。通过使用 XMLHttpRequest,在将数据异步加载到界面中时就无需刷新页面。

  Backbase在J2EE架构中提供一个Ajax表示层,它结合了目前的J2EE服务器和先进的富客户端技术的优点。Backbase表示层 控制了富用户界面的每个方面:与最终用户的交互模型,与后端系统的集成,以及整个客户端-服务器通信。Backbase直接提供了用于聚合来自任意位置的 XML的下一个范型,将数据绑定到先进的富用户界面控件,并在一个统一的富用户界面中交付组合应用程序。

  Backbase表示层由一个客户机和一个服务器组成。Backbase Presentation Client (BPC)是一个基于Ajax的GUI引擎,它允许开发人员以声明性的方式快速构建RIA。Backbase XML(BXML)是对XHTML的扩展。它为开发人员提供了交付富前端功能的附加标签(B tag)。Backbase XML Server (BXS)提供一种XML流水线架构,利用它可以从Web服务、数据库或Java对象获取数据,可以聚合和转换这些数据,并将其绑定到BPC中的UI元 素。BPC和BXS相结合,可以在Web浏览器和应用服务器之间搭建一座功能强大的桥梁,并提供一个分布在客户端和服务器上的完整的富Internet表 示层。

  图1说明了在逻辑和物理应用程序架构中,Backbase所处的位置。应用程序由一个J2EE后端和一个基于Ajax的RIA前端组成。从逻辑 上说,Backbase提供了表示层,而J2EE提供了业务逻辑和数据层。从物理上说,表示层分布在客户端和服务器上。在客户端上,Backbase使用 BPC扩展了浏览器。在服务器上,Backbase使用BXS扩展了应用服务器。

图1. Backbase富Internet表示层

Pet Store案例分析

  我们将使用Java Pet Store作为案例来分析如何为J2EE应用程序添加Backbase RIA前端。Java Pet Store Demo是Sun Microsystems提供的一个示例应用程序,其目的是为了演示如何使用Java 2 Platform, Enterprise Edition(J2EE)构建Web应用程序(详情请参见http://java.sun.com/developer/releases/petstore)。

  Java Pet Store是业内一个著名的参考应用程序(pet store还有.NET和Flash版本)。由于以下两个原因,它成为为J2EE应用程序添加基于Ajax的RIA前端的完美案例:

  • Java Pet Store是一个完整的Web应用程序。
  • Sun设计Pet Store的目的是演示所有常见的Web应用程序功能。通过使用Pet Store作为案例,我可以说明为J2EE应用程序添加RIA层的所有方面。

    作为一个典型的在线商店,它包含以下功能:

    • 浏览产品类别。
    • 在购物车中添加和删除物品。
    • 填写订单表单。
    • 提交订单。
  • Java Pet Store有一个传统的HTML前端。
  • 使用RIA前端的目的是提供更简单和响应更灵敏的GUI,以及通常更为丰富的Web用户体验。我将说明,如何通过Backbase RIA技术极大地改进应用程序的前端,同时无需对后端和总体系统需求做任何修改。

    Pet Store的RIA前端将通过以下方式改善可用性:

  • 把前端变为一个单页面的界面(SPI)。
  • 提供更先进的UI控件(如模态弹出式菜单)。
  • 使用可视化效果(例如,把宠物放入购物车)。
  • 更加有效地利用电脑屏幕的操作区域。

RIA Pet Store前端

  在这一节中,我将讨论经过改进的新Pet Store RIA前端。

  下面的两个屏幕快照演示了前端的改进。要获得对Backbase RIA前端更直观的感受,请访问http://www.backbase.com/xmlserver上的在线演示,或者到http://www.backbase.com/download下载Backbase社区版本。

下面两个图对两个前端进行了可视化的比较。图2显示的是原来静态的多页面HTML前端。图3显示的是新的Backbase SPI前端:

图2. 原始HTML前端

图3. 新Backbase前端

  Backbase为创建丰富的单页面Web界面提供了许多可能性。下面列出了一些Pet Store所使用的例子。

  • 选项卡式的单页面浏览
  • 在Web界面上,不同的动物种类(狗、猫等等)被表示为不同的选项卡。点击一个选项卡就会打开相应的类别,显示可供出售的宠物。

    在Backbase SPI中,无需刷新页面就可以打开选项卡。BPC只从服务器请求所需的数据,然后更新客户端的视图。SPI机制可以极大地缩短响应时间,让客户随心所欲地在类别之间来回穿梭。

  • 活动的多功能界面
  • 界面有三个主要功能——类别浏览、购物车和页面引导历史记录,它们在界面上都是一直可见的。因此,购物者总是能够查看购物车的当前内容或最近看过的宠物的记录。

    这些功能是高度同步的:浏览一个宠物时,历史记录将自动更新为在记录中显示该宠物。定购一个宠物时,它将被添加到购物车中。上述一切都发生在客户端的一个页面上(例如,无需重新加载页面就可以更新界面的各个部分)。

  • 界面变化的流畅可视化效果
  • 进行浏览时,客户将会看到不断变化的界面视图。例如,他可以按照价格和名称对宠物进行排序。界面需要根据新的排列顺序显示更新以后的宠物清单。

    在Backbase RIA前端中,以前的视图被使用可视化效果的新视图所代替,新视图向最终用户显示什么正在改变。图4说明了如何通过流畅的定位效果,把按名称排列的顺序转变为按价格排列的顺序:

    图4.类别视图的排列顺序转换

  • 用于提高转换速度的信息栏验证

  为了执行购买,购买者必须在一份表单中填入个人详细信息。Backbase极大地简化了这个购买过程,通过客户端的信息栏验证提供即时的反馈,并在提供所有数据的过程中提供逐步的指南和概述。

  图5显示了在填写表单的第一个步骤中,对于e-mail地址信息栏的验证。当购买者填写下一栏时,就会提供即时的反馈。

图5. 信息栏验证—e-mail栏

Backbase RIA Pet Store的架构

  增强Pet Store(或其他任何Web应用程序)的前端时,我们将继续依赖于以下两条架构基本原则:

  • 最终用户仍然使用标准的Web浏览器访问Pet Store,无需添加任何插件。
  • 由J2EE业务逻辑和数据组成的整个后端保持不变。

  现有的后端在开发期间是完全孤立的,而且不会改变,这个事实对于架构师和IT管理人员十分有利。通过一个规整的、模块化的架构,他们将能够控制风险和成本,同时显著提高Web应用程序的用户友好性。

  Backbase的富表示层技术由两个模块组成,它们将被加入到架构中。在客户端,BPC管理着SPI,并通过异步响应事件来处理与最终用户之 间的交互。在服务器端,Backbase XML Server这个灵活的XML管道可以连接到任意服务器端的数据源,包括Web服务、文件、数据库或本地Java对象。图6说明了BPC和BXS如何共同 为RIA提供一个声明式的、基于XML的端到端表示层。

图6. 声明式的端到端表示层

Backbase表示客户端

  BPC是一个基于Ajax的GUI引擎,它运行在标准的Web浏览器中。运行时,BPC被加载到浏览器中,然后它会接收BXML代码,构造对应的B树,并不断地把这种表示转换为浏览器所呈现的DOM树。图7说明了运行时转换过程。

图7. BPC运行时

Backbase XML

  Backbase XML (BXML)是XHTML的扩展。开发人员通过创建BXML应用程序来开发富前端,包括BXML标签、标准的XHTML和CSS。BXML是一种声明性语言,它包含了XHTML中所没有的标签(B标签)

  BXML包含用于下列用途的标签:

  • 定义屏幕分区(<b:panel>)
  • 交互式客户端控制(<b:menu>)
  • 处理标准的用户交互事件(onClick)
  • 处理高级的用户交互事件(拖放和调整大小)
  • 管理客户端状态
  • 处理可视化效果(使修改任意CSS属性的过程动画化)
  • 数据绑定
  • 使用XSLT的一个子集进行客户端转换

用于J2EE的Backbase XML Server

  Backbase XML Server (BXS)是一个服务器端的引擎,用于把BPC链接到任意J2EE后端。和BPC一样,BXS是完全基于XML的,其编程是声明性的。它使用一种XML管道架构,提供功能强大的服务器端转换和聚合。

  BXS附带一些用于访问最常用的数据源(包括Web服务、数据库、文件系统和本地Java对象)的开箱即用任务。我们使用Backbase标签对从这些源获得的数据进行聚合,然后使用XSLT进行转换。结果以无格式XML数据或BXML表示代码的形式返回给BPC。

  BXS还提供一些应用服务,包括身份验证、授权、日志记录和用户跟踪。图8显示了BXS的总体架构。

图8. BXS架构

Eclipse开发工具

  为了让J2EE开发人员可以只使用一种开发工具就能创建完整的Web应用程序,包括富前端,Backbase提供了一个Eclipse插件。如图9所示,该插件提供了在Eclipse中突出显示语法和Backbase标签代码自动完成的功能。

图9. Backbase Eclipse插件

  注意:Eclipse的可视化拖放开发插件还处在开发阶段。

部署到BEA WebLogic

  BXS是一个与标准兼容的J2EE应用程序,可以将其部署到任何J2EE应用服务器上。图10显示了如何使用WebLogic控制台把BXS部署到BEA WebLogic Server

图10. 把BXS部署到BEA WebLogic

实现Backbase RIA Pet Store

  下面的顺序图包括更多详细信息,可以帮助您更好地理解如何实现Backbase pet store。该顺序图显示了在应用程序的初始化加载期间BPC与BXS之间的交互,如图11所示,它包括以下4个步骤:

  • 初始化:用户在浏览器中输入宠物商店的URL;对BPC进行初始化。
  • 应用程序布局:触发正在构造的事件;BPC构建整体应用程序布局;宠物类别被加载并显示在选项卡中。
  • 默认数据:默认情况下加载狗的类别;最初显示8张狗的图片,并带有向前/向后和排序功能。

  用户交互:用户点击Next按钮便可显示编号从9到16的狗图片。

图11.顺序图:富商店前端

  • 初始化
  • 从用户在浏览器中输入宠物商店的URL开始,这将导致从Web服务器请求一个索引页面。

    索引页面包含用于实例化BPC的代码。索引页面是XHTML和BXML标签的结合,包含负责启动富前端的初始化事件处理程序。

    BPC初始化代码:

    <...><body onload="bpc.boot('/Backbase/')">

    <...>

    <xmp b:backbase="true"

    style="display:none;height:100%;">

    <s:loading>

    <div style="position:absolute;width:20%;

    top: 50px;left: 35%;">

    <center>Please wait while loading...

    </center>

    </div>

    </s:loading>

    <...>

    <!-- Include petshop specific behaviors -->

    <s:include b:url="petshop.xml"/>
  • 应用程序布局
  • 加载页面之后,BPC就会处理正在构造的事件,以便开始构建总体的应用程序布局。

    应用程序布局由几个面板组成,它们将屏幕划分为几个部分。顶行有一个固定高度的宠物商店徽标,接下来的主行是实际的商店,大小可以调整。主行分为两列,左边一列是产品类别,右边一列是购物车和历史记录。

    产品类别使用选项卡式的导航,每个宠物类别一个选项卡。这些选项卡是动态构造的,具体过程是通过BXS从一个XML文件加载类别,然后通过一个客户端模板把这些类别转换为选项卡,该转换模板的BPC代码如下:
    <s:task b:action="transform"

    b:stylesheet="b:xml('categories')"

    b:xmldatasource="b:url('categories.xml')"

    b:destination="id('main-content')"

    b:mode="aslastchild" />

    下面是用于从文件系统把类别加载为XML的BXS代码:

    <bsd:pipeline equals="categories.xml"

    access="public">

    <bsd:readxml input="file:/categories.xml"/>

    </bsd:pipeline>

    下面是用于创建选项卡式导航的BPC客户端模板:

    <b:tabrow>

    <s:for-each b:select="categories/category">

    <b:tab>

    <s:attribute b:name="b:followstate">

    id('<s:value-of b:select="name"/>')

    </s:attribute>

    <s:value-of b:select="name"/>

    </b:tab>

    </s:for-each>

    </b:tabrow>

    所有BPC代码(用蓝色表示)都在客户端执行,而所有BXS代码(用红色表示)都在服务器端执行。注意,在本例中,我选择了在客户端进行转换,因为 数据集很小。下面我会给出一个在服务器端转换的例子。两种转换都要用到XSLT语法。Backbase的一个强大功能就是,前端开发人员可以根据情况选择 在客户端还是服务器端处理表示逻辑。语法似乎允许轻松地把代码从客户端移到服务器端,或者反之。

    以上的代码示例应该可以使您了解到,借助于Backbase,Ajax编程变得多么轻松。结合了DHTML的声明性方法则更容易上手。使用附加的B 标签不仅可以使界面更加丰富,而且可以使开发人员的效率更高。诸如<b:tab>之类的单个标签可以代替多行HTML和JavaScript 代码,而且保证可以用于各种浏览器。

  • 默认数据
  • 显示商店前端时,默认情况下显示的是狗的类别。对于本案例,BXS负责此项操作。BXS从一个Web服务获得数据,将其放入缓存,然后生成BXML 表示代码,再把这些表示代码发回给BPC。服务器还通过一项配置设置确定一个页面上可以显示的动物数量,并根据需要加入了Next和Previous按 钮。最后,服务器还提供了按照名称或价格进行排序的功能。

    下面的代码片断演示了服务器功能。外部管道products-overview.xml首先调用catalog.xml子管道。该子管道要么返回缓 存中的宠物信息,要么调用另一个子管道catalog.ws。在缓存没有命中的情况下,内部管道catalog.ws会从Web服务获取宠物信息。

    外部管道获得宠物信息,然后进行XSLT转换,从而以4x2表格显示这些信息,并带有Next和Previouse按钮,然后把BXML格式的代码发回给BPC。BPC呈现它接收到的BXML。

    有3个嵌套的BXS管道分别用于从Web服务获取数据、将其放入缓存,以及通过XSLT转换创建BXML输出:

    <bsd:pipeline equals="products-overview.xml"

    access="public"/>

    <bsd:callpipe pipe="catalog.xml"/>
    <bsd:pipeline equals="catalog.xml" access="private">

    <bsd:exist field="{global:petstore-catalog}">

    <bsd:readxml>{global:petstore-catalog}

    </bsd:readxml>

    <bsd:otherwise>

    <bsd:callpipe pipe="catalog.ws"/>
    <bsd:pipeline equals="catalog.ws"

    access="private">

    <bsd:try>

    <bsd:callws wsdl="PetstoreCatalog.wsdl"

    method="getAll"/>

    <bsd:callpipe pipe="strip-root-ns"/>

    <bsd:catch>

    <bsd:xslt xslt="error.xslt">

    <bsd:param name="errormsg">{error:message}

    </bsd:param>

    <bsd:param name="errorsrc">{error:source}

    </bsd:param>

    </bsd:xslt>

    </bsd:catch>

    </bsd:try>

    </bsd:pipeline>
    <bsd:writexml>{global:petstore-catalog}

    </bsd:writexml>

    </bsd:otherwise>

    </bsd:exist>

    </bsd:pipeline>
    <bsd:extractfilter xpath=

    "category[name/text()='{requestparam:category}']"/>

    <bsd:xslt xslt="products/products-overview.xslt">

    <bsd:param name="category">

    {requestparam:category}

    </bsd:param>

    <bsd:param name="stepsize">

    {global:stepsize}

    </bsd:param>

    <bsd:param name="sortorder">

    {requestparam:sortorder}

    </bsd:param>

    <bsd:param name="sortfield">

    {requestparam:sortfield}

    </bsd:param>

    </bsd:xslt>

    </bsd:pipeline>

    代码示例再次清楚地说明了,借助于Backbase,以声明性的方式创建Ajax前端是多么容易的事情。例如,只要使用带有一个WSDL引用作为属性的<bsd:callws>标签,就可以调用一个Web服务。

  • 用户交互
  • 现在,最终用户可以与宠物商店类别进行交互。可以使用Next或Previous按钮或者排序功能在动物类别中进行浏览。或者,只要点击一下相应的选项卡,就可以转到另一个类别中。

    BPC和BXS对这种交互进行了无缝处理。显示已经在客户端上的数据时,无需与服务器进行任何通信。例如,购物者已经从狗类别转到了猫类别,然后再 回到狗类别。客户端仍然拥有狗类别的数据,所以可以马上显示出来,这使得购物体验变得更完美。其他的类别需要从BXS获取。BXS要么立即从其缓存返回它 们,要们访问Web服务来获得新数据。

  为了详细说明Backbase Ajax宠物商店的实现,我把重点放在了初始化的步骤上。完整的宠物商店(可以从http://www.backbase.com/xmlserver下载)还包括以下功能:

    • 商店前端
      • 初始化。
      • 使用从文件加载的宠物类别创建选项卡。
      • 默认情况下从Web服务加载Dog选项卡。
      • 通过缓存浏览Dog并对其进行排序。
    • 宠物详细情况
      • 使用跟踪聚合来自缓存和数据库的宠物详细情况。
      • 创建可视化历史记录。
    • 购物车
      • 使用跟踪添加到购物车。
    • 登录
      • 登录和身份验证。
    • 退出
      • 退出和授权。
      • 确认。

结束语

  最近有很多人都在研究Ajax。Ajax的优点已经在实践中得到了证明。定制Ajax的缺点在于它的复杂性和不兼容性。大量客户端 JavaScript的出现意味着开发人员很可能陷入到浏览器实现差别的泥潭中去。另外,JavaScript这种语言不适用于复杂的应用程序。

  为了开发易于管理的、可伸缩的和适应未来变化的Ajax解决方案,开发人员所需使用的工具应该具有比定制部件开发更多的功能。Backbase Ajax软件提供了一个功能全面的客户端GUI管理引擎(Backbase Presentation Client)、一个灵活的服务器端XML管道(Backbase XML Server)和一种声明性的基于标签的UI语言,BXML(Backbase eXtensible Markup Language)。该方法具有几个优点。

  首先,Backbae易于使用。它的声明性语言水平地扩展了DHTML;它完全对开发人员隐藏了浏览器兼容性的问题;而且它带有一套开发和调试工具。

  其次,Backbase是一个功能全面的Ajax GUI管理系统。Backbase的先进性大大超过了其他Ajax框架,它完全把重点放在提供一个部件库或客户端-服务器通信(如DWR)上。在控件和客 户端-服务器通信的基础上,Backbase提供了用于如下用途的标签:提供电影效果,随需应变的数据加载,数据绑定和客户端的数据转换,对于Back和 Forward按钮的支持,完善的GUI状态管理,等等。所有这些功能对于目前的Ajax Web应用程序来说都是必需的。

  最后,Backbase是以兼容的方式提供所有客户端和服务器端的功能。用户可以使用富Ajax前端扩展现有的应用程序,同时无需修改后端。对于整个表示层来说,它的架构是时新的、模块化的,而且它基于XML。

参考资料

原文出处

A Backbase Ajax Front-end for J2EE Applications

http://dev2dev.bea.com/pub/a/2005/08/backbase_ajax.html

 作者简介

Mark Schiefelbein自2005年2月以来一直担任Backbase的产品管理主管。Mark极大地推动了Backbase Rich Internet Application的全球推广。
posted @ 2005-11-23 20:31 Dion 阅读(905) | 评论 (0)编辑 收藏

     摘要: 原文:http://www.blogjava.net/eamoi/archive/2005/11/07/18566.html (续前一篇)AJAX开发简略 在前一篇文章《AJAX开发简略》中,我们讲述了如何用AJAX来改进设计的用户体验。接下来,我们将讲述如何用AJAX来更新文档,以及处理服务器返回的XML文档。我们的最终目的是接收服务器的返回信息,修改当前文档的内容。是时候让...  阅读全文
posted @ 2005-11-23 20:28 Dion 阅读(1222) | 评论 (0)编辑 收藏

原文:http://www.blogjava.net/eamoi/archive/2005/11/01/17639.html

有网友反映说《AJAX开发简略》配文代码不全。其实应该是全的,只是要把包括框架和两个示例的程序都整合起来看。这里把全部的代码贴出来,需要的朋友可以看看。
sample1_1.jsp:
<%@ page contentType="text/html; charset=gb2312" language="java" errorPage="" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>无标题文档</title>
<script language="javascript">
 var http_request = false;
 function send_request(url) {//初始化、指定处理函数、发送请求的函数
  http_request = false;
  //开始初始化XMLHttpRequest对象
  if(window.XMLHttpRequest) { //Mozilla 浏览器
   http_request = new XMLHttpRequest();
   if (http_request.overrideMimeType) {//设置MiME类别
    http_request.overrideMimeType('text/xml');
   }
  }
  else if (window.ActiveXObject) { // IE浏览器
   try {
    http_request = new ActiveXObject("Msxml2.XMLHTTP");
   } catch (e) {
    try {
     http_request = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (e) {}
   }
  }
  if (!http_request) { // 异常,创建对象实例失败
   window.alert("不能创建XMLHttpRequest对象实例.");
   return false;
  }
  http_request.onreadystatechange = processRequest;
  // 确定发送请求的方式和URL以及是否同步执行下段代码
  http_request.open("GET", url, true);
  http_request.send(null);
 }
 // 处理返回信息的函数
    function processRequest() {
        if (http_request.readyState == 4) { // 判断对象状态
            if (http_request.status == 200) { // 信息已经成功返回,开始处理信息
                alert(http_request.responseText);
            } else { //页面不正常
                alert("您所请求的页面有异常。");
            }
        }
    }
 function userCheck() {
  var f = document.form1;
  var username = f.username.value;
  if(username=="") {
   window.alert("用户名不能为空。");
   f.username.focus();
   return false;
  }
  else {
   send_request('sample1_2.jsp?username='+username);
  }
 }
</script>
<link href="css/style.css" rel="stylesheet" type="text/css">
</head>

<body>
<form name="form1" action="" method="post">
用户名:<input type="text" name="username" value="">&nbsp;
<input type="button" name="check" value="唯一性检查" onClick="userCheck()">
<input type="submit" name="submit" value="提交">
</form>
<!--span style="cursor: pointer; text-decoration: underline" onclick="send_request('2.jsp?username=educhina')">Send a request</span-->
</body>
</html>

sample1_2.jsp:
<%@ page contentType="text/html; charset=gb2312" language="java" errorPage="" %>
<%
String playPos = request.getParameter("playPos");
if("pos_1".equals(playPos)) out.print("用户名已经被注册,请更换一个用户名。");
else out.print("用户名尚未被使用,您可以继续。");
%>

sample2_1.jsp:
<%@ page contentType="text/html; charset=gb2312" language="java" errorPage="" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>无标题文档</title>
<script language="javascript">
 var http_request = false;
 var currentPos = null;
 function send_request(url) {//初始化、指定处理函数、发送请求的函数
  http_request = false;
  //开始初始化XMLHttpRequest对象
  if(window.XMLHttpRequest) { //Mozilla 浏览器
   http_request = new XMLHttpRequest();
   if (http_request.overrideMimeType) {//设置MiME类别
    http_request.overrideMimeType('text/xml');
   }
  }
  else if (window.ActiveXObject) { // IE浏览器
   try {
    http_request = new ActiveXObject("Msxml2.XMLHTTP");
   } catch (e) {
    try {
     http_request = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (e) {}
   }
  }
  if (!http_request) { // 异常,创建对象实例失败
   window.alert("不能创建XMLHttpRequest对象实例.");
   return false;
  }
  http_request.onreadystatechange = processRequest;
  // 确定发送请求的方式和URL以及是否同步执行下段代码
  http_request.open("GET", url, true);
  http_request.send(null);
 }
 // 处理返回信息的函数
    function processRequest() {
        if (http_request.readyState == 4) { // 判断对象状态
            if (http_request.status == 200) { // 信息已经成功返回,开始处理信息
                //alert(http_request.responseText);
    document.getElementById(currentPos).innerHTML = http_request.responseText;
            } else { //页面不正常
                alert("您所请求的页面有异常。");
            }
        }
    }
 //显示部门下的岗位
 function showRoles(obj) {
  document.getElementById(obj).parentNode.style.display = "";
  document.getElementById(obj).innerHTML = "正在读取数据..."
  currentPos = obj;
  send_request("sample2_2.jsp?playPos="+obj);
 }
</script>
<link href="css/style.css" rel="stylesheet" type="text/css">
</head>

<body>
<table width="200" border="0" cellspacing="0" cellpadding="0">
    <tr>
        <td height="20"><a href="javascript:void(0)" onClick="showRoles('pos_1')">经理室</a></td>
    </tr>
    <tr style="display:none">
        <td height="20" id="pos_1">&nbsp;</td>
    </tr>
    <tr>
        <td height="20"><a href="javascript:void(0)" onClick="showRoles('pos_2')">开发部</a></td>
    </tr>
    <tr style="display:none ">
        <td id="pos_2" height="20">&nbsp;</td>
    </tr>
</table>
<!--a href="javascript:void(0)" onClick="showRoles('pos_1')">测试</a-->
<!--span style="cursor: pointer; text-decoration: underline" onclick="send_request('2.jsp?username=educhina')">Send a request</span-->
</body>
</html>

sample2_2.jsp:
<%@ page contentType="text/html; charset=gb2312" language="java" errorPage="" %>
<%
String playPos = request.getParameter("playPos");
if("pos_1".equals(playPos)) out.print("&nbsp;&nbsp;总经理<br>&nbsp;&nbsp;副总经理");
else if("pos_2".equals(playPos)) out.println("&nbsp;&nbsp;总工程师<br>&nbsp;&nbsp;软件工程师");
%>

posted @ 2005-11-23 20:26 Dion 阅读(778) | 评论 (0)编辑 收藏

     摘要: http://www.blogjava.net/eamoi/archive/2005/10/31/17489.html AJAX开发简略   文档说明   参与人员:   作者 网名 联络 柯自聪 eamoi   educhina ...  阅读全文
posted @ 2005-11-23 20:25 Dion 阅读(567) | 评论 (0)编辑 收藏

     摘要: ASP服务 ASP是英文Application Service Provider的缩写,通常中文译为应用服务提供商,它是指配置、租赁和管理应用解决方案,为商业、个人提供服务的专业化服务公司。通俗地说,ASP是一种业务租赁模式,企业用户可以直接租用ASP的计算机及软件系统进行自己的业务管理,从而节省一大笔用于IT产品技术购买和运行的资金。 在国外,ASP最早出现于1998年,是伴随着互联...  阅读全文
posted @ 2005-11-23 20:23 Dion 阅读(621) | 评论 (0)编辑 收藏

     摘要: (转)ASP的行业介绍   一、ASP的概念     1、 几个比喻    想吃鸡蛋,用不着买只母鸡回家,想喝牛奶,也不需要牵一头奶牛回家。同理,在互联网时代,通过互联网,企业日常的生产、经营、管理过程中使用的应用服务都可以让别人帮你打点,你可以节省时间和人力,他们向你收取租金,他们就是ASP...  阅读全文
posted @ 2005-11-23 20:22 Dion 阅读(683) | 评论 (0)编辑 收藏

ASP英文全称为Application Service Provider,中文译为应用服务供应商。 这是随着Internet革命将人类从工业经济时代推向电子商务时代,并重塑信息技术(Information Technology,简称IT)产业的未来发展方向所应运而生的一种崭新的产品及服务模式。

ASP的本质在于:这种应用,从所需的硬件平台到应用软件、企业内部资源管理和业务流程的处理,不是发生在企业本地的实施上,而是由特定的供应商提供,并由供应商进行维护、管理及更新,企业(ASP的用户)通过租赁、承包等方式获得服务。

因此,ASP产生和发展的前提条件主要基于以下IT技术的成熟:
 
Internet
的普及:Internet逐步深入人们生活,大量的基于Web的解决方案不断涌现,这些都使得远程的基于主机的应用方案成为可能。
 
带宽不断增大和价格不断下降:增长的通信性能和持续减少的带宽费用,使主机上的应用程序可以通过Internet和瘦客户机来访问。
 
客户机/服务器环境下的可共享应用:在ASP概念中的远程访问,用户早已经在客户机/服务器环境下习以为常,这使得远程访问和共享应用成为可接受的一种业务模式
 
浏览器成为广泛接受的图形界面应用程序:浏览器技术的的广泛应用及开发为基于Web的计算和瘦客户机的计算聚集了足够的技术及人力资源,以及良好的用户基础。
 
电子商务解决方案的潜力:先进的电子商务解决方案,与ASP的概念一起分享了许多棘手的业务和技术问题,例如系统的安全性和可靠性。所以,电子商务的巨大动力也在推动ASP的前进。

几年前,当应用服务提供商(ASP)刚刚出现时,它似乎是帮助企业投身数字时代的最佳选择:ASP们开发、运行和维护商用软件,企业只需按月或按年缴纳租费,就可以获得需要的应用软件。然而,互联网泡沫的破裂使ASP的成功如昙花一现,当大多数Start-up公司倒下后,ASP们似乎也偃旗息鼓了。

究其原因,技术不成熟是关键因素。Yankee Group的分析家认为,“5年前,ASP技术还不成熟,它不能定制、不具备离线能力、不能与其他系统集成、缺少这样或那样的功能,因此ASP们难以逃生也就成为必然。”但现在情况不同了,一方面是技术的进步使客户通过Internet访问商用软件变得更安全和容易;另一方面是全球经济的不景气促使企业再次把目光投向ASP,毕竟,与购买许可证和高昂的维护费用相比,按月或按年付费的软件使用方式更能节省成本,至少在短时间内是如此。通常情况下,诸如OracleSAP等公司的大型软件系统都需要有相当大的初期投资,用于软件的购买、安装和培训,一旦投入使用,还必须支付维护和软件升级费用。与此相反,如果向ASP租用软件,所有这些投资都可以节省下来,企业所需要付出的不过是每月数百美元的租金而已,况且这笔钱完全可以纳入企业的日常开支。

目前的ASP主要针对企业市场,采用远程租用的方式,所提供的服务可以是集成硬件、软件和网络技术来为大中小各类企业提供应用解决方案;也可以是安装、配置、定做和管理定制的封装应用软件;有些ASP甚至可以提供商务处理咨询和外包服务。  
ASP
不再试图通过Internet提供为企业网设计的传统客户机/服务器应用,而是创建适合Internet传送的应用。因此它能够更好地提供可伸缩和定制的应用,更容易与其他系统集成。除了最初的标准软件以一对多的方式租赁使用之外,新的ASP应用模式还增加了个性化的服务。个性化服务从某种意义上说更接近于传统的应用软件系统开发,用户仍然要为软件开发支付一笔可观的开发费用,所不同的是系统赖以传输数据的载体是开放的Internet。目前国外有观点认为,ASP最适合的应用领域是财务、电子商务和客户关系管理;国内专家则认为,在人力资源管理上ASP也可以有所发挥。此外,新的信息传输技术的应用也为ASP弥补了Internet带来的一些不足,比如VPN可以保证数据的安全性,有利于吸引一些对安全性有更高要求的用户;宽带技术则使更快的速度和更丰富的功能成为可能。
从未来发展来看,影响ASP发展的主要有以下几个因素
   
1
)通讯频宽的限制
   
ASP
商业模式需要有充足的带宽资源支持,目前我国的通讯基础设施有了很大的发展,但是带宽资源还没到富余的程度,因此,频宽资源可能会成为制约ASP发展的一个重要因素。
   
2
)网络安全性
   
网络的安全性包括了两层含义:一是技术上能够抵御黑客的非法侵入,另一层更重要的含义是ASP商本身的职业操守达到一定的层次,顾客的商业秘密不会因ASP自身的原因而泄露。
   
3
)社会信用体系
   
我们看到美国的ASP业发展迅速,应该看到他们多年积累的信用体系其实是ASP发展的关键动力。然而,反观国内的企业,普遍缺乏信用观念,这极大地增加了ASP用户的交易成本和投资风险。
   
4
)品牌因素
  
由于ASP这一商业模式本身需要很高的技术要求、安全要求和信用要求,ASP商的品牌因素也特别重要。 

虽然技术是ASP成功的原动力之一,但是ASP的成功关键不仅在于先进技术和人力资源的掌握,也依赖于对相关业务流程和信息管理的行业经验,因此目前的少数ASP所提供的功能远不能满足企业用户的需要,无法达到真正的ASP所提供的功能。

从市场前景来看,ASP已经占有了一席之地,它从根本上降低了客户的TCO。对客户来说,向ASP租用软件的风险很小,按照Enterprise Applications Consulting公司的分析家的话来说“你不必购买永久许可而只在某些时候才使用软件,你所买的就是你所需要的”。除此之外,租用软件可以减少企业内部对IT人员的需求,甚至有可能取消企业的IT部门,因为有ASP替您维护和升级应用程序。尽管如此,仍然有相当的潜在客户会担心租用软件可能面临的问题:诸如无法让第三方来替它处理有价值的数据;或者是担心托管系统的安全问题以及被托管的系统能否与来自其他厂商的应用系统协同工作。这些技术上的问题都需要被妥善解决,同时也会成ASP技术继续发展的原动力。但无论怎样,随着商用软件复杂度的增加以及企业降低成本的需求,ASP模式将成为企业IT应用发展的大趋势。

以下是软件公司为某电器有限公司定制的分销体系解决方案,也许可以从中更深的了解到基于ASP的解决方案的魅力。  

(1)信息化动因分析  
   
公司成立于1994年,现有员工近千名,目前的产品主要有食品搅拌机、榨汁机、全自动豆浆机等,2000年其销售额达1亿元。作为一家制造型的企业,产品技术和研发是基础,产品销售则是最终目标,所以企业信息化的核心目标是:理顺企业管理流程为销售服务。  
   
目前公司在全国有近1000个 销售网点。以前在销售环节上出的问题比较多,给公司造成了很大的损失。因此,公司制订出一套规范的业务流程,来监督管理下属的各地办事处。改进流程的结果 是实行“两级管理一级核算”的体制,总公司进行独立核算。在各地的办事处只负责销售,记录销售费用,客户与总公司直接结算。  
   
这 种方式堵住了管理上漏洞,只是手工重复的劳动比较多。总公司和办事处之间的信息交流,以前是通过电话、传真、电子邮件方式实现的,各办事处手工录入的大量 数据传到总公司后,汇总处理时仍然需要再次重复录入,工作量极其繁重,处理效率非常低。同时,由于人工汇总存在较大的计算出错可能性,因此上报给企业管理 层的销售汇总数据的可靠性和及时性都比较差。而且在手工管理模式下,每周只能进行12次销售汇总数据的统计和报告,无法实现对办事处存货、客户应收帐款等明细数据的实时额度控制。  
 
(2)
分销管理系统解决方案  
   
针对目前存在的问题,软件公司提出分销系统的ASP模 式,即所有数据都集中在总公司的数据库中,而办事处只需要登录进系统,在客户端将所发生的数据按照系统模块的要求输入进去,系统将自动生成各种数据,并允 许相应级别的人实时查询其相应权限下的所有相关数据。这种模式正好满足其对众多分子公司和办事处的管理要求。同时在实施整个系统时,先将数据存放在软件公 司的服务器上。在实施完成后,可以选择两种方式:一是自己建服务器和数据中心并承担系统维护工作;二是租用软件公司提供的网站和数据库维护增值服务,完全 不再投入硬件和人员。  
  
实 施系统管理之后,该公司的整体业务流程系统由业务管理子系统和财务管理子系统两部分构成。业务管理子系统主要功能包括:客户端电子商务功能、销售过程管 理、库存管理、应收帐款管理、各类计划管理、商品档案管理、客户档案管理、统计查询、系统管理等;财务管理子系统主要功能为日常费用管理。该分销管理系统 在 “用友伟库企业分销管理软件”的基础上进行了适量的二次开发。  
 
(3)
分销管理系统效果评析  
  
实 施了这套系统以后,以往许多无法解决的问题就迎刃而解。各地办事处只要正确输入货物出入库数据,系统平台的总数据累计自动生成,与以往用手工合计的速度相 比有了很大的提升。各办事处可以进入公司网站,在分销系统平台中本办事处的界面进行操作,减少了运营费用。各地分公司和办事处均可通过系统提供的业务处理 平台将每日订单、销售、存货等数据输入系统,自动处理/生成各类销售统计报表;管理层可根据不同的权限随时了解销售、产品库存及资金情况,浏览系统提供的部分数据分析报告,为管理决策层提供更准确和及时的量化依据。  
   
由于采用ASP运行模式,实现了异地商务集中管理。海菱公司不需要单独构建昂贵的数据处理中心;分支机构无需安装专用的客户端软件,采用标准浏览器上网即可使用该系统的全部功能,数据处理全部在数据中心完成和存储,数据安全性高,操作简单,系统维护成本低。

posted @ 2005-11-23 20:22 Dion 阅读(594) | 评论 (0)编辑 收藏

转自http://dev2dev.bea.com.cn/techdoc/2005110103.html


Ajax简介

时间:2005-11-01
作者:David Teare
浏览次数: 1786
本文关键字:ajaxdhtmldwr javascript
文章工具
推荐给朋友 推荐给朋友
打印文章 打印文章

  作为J2EE开发人员,我们似乎经常关注“后端机制(backend mechanics)”。我们通常会忘记,J2EE的主要成功之处在Web应用程序方面;许多原因使得人们喜欢利用Web开发应用程序,但主要还是因为其 易于部署的特点允许站点以尽可能低的成本拥有上百万的用户。遗憾的是,在过去几年中,我们在后端投入了太多的时间,而在使我们的Web用户界面对用户自然 和响应灵敏方面却投入不足。

  本文介绍一种方法,Ajax,使用它可以构建更为动态和响应更灵敏的Web应用程序。该方法的关键在于对浏览器端的JavaScript、 DHTML和与服务器异步通信的组合。本文也演示了启用这种方法是多么简单:利用一个Ajax框架(指DWR)构造一个应用程序,它直接从浏览器与后端服 务进行通信。如果使用得当,这种强大的力量可以使应用程序更加自然和响应灵敏,从而提升用户的浏览体验。

  该应用程序中所使用的示例代码已打包为单独的WAR文件,可供下载。

简介

  术语Ajax用来描述一组技术,它使浏览器可以为用户提供更为自然的浏览体验。在Ajax之前,Web站点强制用户进入提交/等待/重新显示范 例,用户的动作总是与服务器的“思考时间”同步。Ajax提供与服务器异步通信的能力,从而使用户从请求/响应的循环中解脱出来。借助于Ajax,可以在 用户单击按钮时,使用JavaScript和DHTML立即更新UI,并向服务器发出异步请求,以执行更新或查询数据库。当请求返回时,就可以使用 JavaScript和CSS来相应地更新UI,而不是刷新整个页面。最重要的是,用户甚至不知道浏览器正在与服务器通信:Web站点看起来是即时响应 的。

  虽然Ajax所需的基础架构已经出现了一段时间,但直到最近异步请求的真正威力才得到利用。能够拥有一个响应极其灵敏的Web站点确实激动人 心,因为它最终允许开发人员和设计人员使用标准的HTML/CSS/JavaScript堆栈创建“桌面风格的(desktop-like)”可用性。

  通常,在J2EE中,开发人员过于关注服务和持久性层的开发,以至于用户界面的可用性已经落后。在一个典型的J2EE开发周期中,常常会听到这样的话,“我们没有可投入UI的时间”或“不能用HTML实现”。但是,以下Web站点证明,这些理由再也站不住脚了:

  所有这些Web站点都告诉我们,Web应用程序不必完全依赖于从服务器重新载入页面来向用户呈现更改。一切似乎就在瞬间发生。简而言之,在涉及到用户界面的响应灵敏度时,基准设得更高了。

定义Ajax

  Adaptive Path公司的Jesse James Garrett这样定义Ajax

  Ajax不是一种技术。实际上,它由几种蓬勃发展的技术以新的强大方式组合而成。Ajax包含:

  • 基于XHTMLCSS标准的表示;
  • 使用Document Object Model进行动态显示和交互;
  • 使用XMLHttpRequest与服务器进行异步通信;
  • 使用JavaScript绑定一切。

  这非常好,但为什么要以Ajax命名呢?其实术语Ajax是由Jesse James Garrett创造的,他说它是“Asynchronous JavaScript + XML的简写”。

Ajax的工作原理

  Ajax的核心是JavaScript对象XmlHttpRequest。该对象在Internet Explorer 5中首次引入,它是一种支持异步请求的技术。简而言之,XmlHttpRequest使您可以使用JavaScript向服务器提出请求并处理响应,而不 阻塞用户。

  在创建Web站点时,在客户端执行屏幕更新为用户提供了很大的灵活性。下面是使用Ajax可以完成的功能:

  • 动态更新购物车的物品总数,无需用户单击Update并等待服务器重新发送整个页面。
  • 提升站点的性 能,这是通过减少从服务器下载的数据量而实现的。例如,在Amazon的购物车页面,当更新篮子中的一项物品的数量时,会重新载入整个页面,这必须下载 32K的数据。如果使用Ajax计算新的总量,服务器只会返回新的总量值,因此所需的带宽仅为原来的百分之一。
  • 消除了每次用户输入时的页面刷新。例如,在Ajax中,如果用户在分页列表上单击Next,则服务器数据只刷新列表而不是整个页面。
  • 直接编辑表格数据,而不是要求用户导航到新的页面来编辑数据。对于Ajax,当用户单击Edit时,可以将静态表格刷新为内容可编辑的表格。用户单击Done之后,就可以发出一个Ajax请求来更新服务器,并刷新表格,使其包含静态、只读的数据。

  一切皆有可能!但愿它能够激发您开始开发自己的基于Ajax的站点。然而,在开始之前,让我们介绍一个现有的Web站点,它遵循传统的提交/等待/重新显示的范例,我们还将讨论Ajax如何提升用户体验。

Ajax可用于那些场景?——一个例子:MSN Money页面

  前几天,在浏览MSN Money页面的时候,有一篇关于房地产投资的文章引起了我的好奇心。我决定使用站点的“Rate this article”(评价本文)功能,鼓励其他的用户花一点时间来阅读这篇文章。在我单击vote按钮并等待了一会儿之后,整个页面被刷新,在原来投票问题所在的地方出现了一个漂亮的感谢画面。

  而Ajax能够使用户的体验更加愉快,它可以提供响应更加灵敏的UI,并消除页面刷新所带来的闪烁。目前,由于要刷新整个页面,需要传送大量的 数据,因为必须重新发送整个页面。如果使用Ajax,服务器可以返回一个包含了感谢信息的500字节的消息,而不是发送26,813字节的消息来刷新整个 页面。即使使用的是高速Internet,传送26K和1/2K的差别也非常大。同样重要的是,只需要刷新与投票相关的一小节,而不是刷新整个屏幕。

  让我们利用Ajax实现自己的基本投票系统。

原始的Ajax:直接使用XmlHttpRequest

  如上所述,Ajax的核心是JavaScript对象XmlHttpRequest。下面的示例文章评价系统将带您熟悉Ajax的底层基本知识:http://tearesolutions.com/ajax-demo/raw-ajax.html。注:如果您已经在本地WebLogic容器中安装了ajax-demo.war,可以导航到http://localhost:7001/ajax-demo/raw-ajax.html

  浏览应用程序,参与投票,并亲眼看它如何运转。熟悉了该应用程序之后,继续阅读,进一步了解其工作原理细节。

  首先,您拥有一些简单的定位点标记,它连接到一个JavaScriptcastVote(rank)函数。
function castVote(rank) {
var url = "/ajax-demo/static-article-ranking.html";
var callback = processAjaxResponse;
executeXhr(callback, url);
}

  该函数为您想要与之通信的服务器资源创建一个URL并调用内部函数executeXhr,提供一个回调JavaScript函数,一旦服务器响 应可用,该函数就被执行。由于我希望它运行在一个简单的Apache环境中,“cast vote URL”只是一个简单的HTML页面。在实际情况中,被调用的URL将记录票数并动态地呈现包含投票总数的响应。

  下一步是发出一个XmlHttpRequest请求:
function executeXhr(callback, url) {
// branch for native XMLHttpRequest object
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
req.onreadystatechange = callback;
req.open("GET", url, true);
req.send(null);
} // branch for IE/Windows ActiveX version
else if (window.ActiveXObject) {
req = new ActiveXObject("Microsoft.XMLHTTP");
if (req) {
req.onreadystatechange = callback;
req.open("GET", url, true);
req.send();
}
}
}

  如您所见,执行一个XmlHttpRequest并不简单,但非常直观。和平常一样,在JavaScript领域,大部分的工作量都花在确保浏 览器兼容方面。在这种情况下,首先要确定XmlHttpRequest是否可用。如果不能用,很可能要使用Internet Explorer,这样就要使用所提供的ActiveX实现。

executeXhr()方法中最关键的部分是这两行:

req.onreadystatechange = callback;
req.open("GET", url, true);

  第一行定义了JavaScript回调函数,您希望一旦响应就绪它就自动执行,而req.open()方法中所指定的“true”标志说明您想要异步执行该请求。

  一旦服务器处理完XmlHttpRequest并返回给浏览器,使用req.onreadystatechange指派所设置的回调方法将被自动调用。
function processAjaxResponse() {
// only if req shows "loaded"
if (req.readyState == 4) {
// only if "OK"
if (req.status == 200) {
502 502'votes').innerHTML = req.responseText;
} else {
alert("There was a problem retrieving the XML data:
" +
req.statusText);
}
}
}

  该代码相当简洁,并且使用了几个幻数,这使得难以一下子看出发生了什么。为了弄清楚这一点,下面的表格(引用自http://developer.apple.com/internet/webcontent/xmlhttpreq.html)列举了常用的XmlHttpRequest对象属性。

属性

描述

onreadystatechange

每次状态改变所触发事件的事件处理程序

readyState

对象状态值:

  • 0 = 未初始化(uninitialized)
  • 1 = 正在加载(loading)
  • 2 = 加载完毕(loaded)
  • 3 = 交互(interactive)
  • 4 = 完成(complete)

responseText

从服务器进程返回的数据的字符串形式

responseXML

从服务器进程返回的DOM兼容的文档数据对象

status

从服务器返回的数字代码,比如404(未找到)或200(就绪)

statusText

伴随状态码的字符串信息

  现在processVoteResponse()函数开始显示出其意义了。它首先检查XmlHttpRequest的整体状态以保证它已经完成 (readyStatus == 4),然后根据服务器的设定询问请求状态。如果一切正常(status == 200),就使用innerHTML属性重写DOM的“votes”节点的内容。

  既然您亲眼看到了XmlHttpRequest对象是如何工作的,就让我们利用一个旨在简化JavaScript与Java应用程序之间的异步通信的框架来对具体的细节进行抽象。

Ajax: DWR方式

  按照与文章评价系统相同的流程,我们将使用Direct Web Remoting(DWR)框架实现同样的功能。

  假定文章和投票结果存储在一个数据库中,使用某种对象/关系映射技术来完成抽取工作。为了部署起来尽可能地简单,我们不会使用数据库进行持久性 存储。此外,为使应用程序尽可能通用,也不使用Web框架。相反,应用程序将从一个静态HTML文件开始,可以认为它由服务器动态地呈现。除了这些简化措 施,应用程序还应该使用Spring Framework关联一切,以便轻松看出如何在一个“真实的”应用程序中使用DWR。

  现在应该下载示例应用程序并熟悉它。该应用程序被压缩为标准的WAR文件,因此您可以把它放置到任何一个Web容器中——无需进行配置。部署完毕之后,就可以导航到http://localhost:7001/ajax_demo/dwr-ajax.html来运行程序。

  可以查看HTML 源代码,了解它如何工作。给人印象最深的是,代码如此简单——所有与服务器的交互都隐藏在JavaScript对象ajaxSampleSvc的后面。更加令人惊讶的是,ajaxSampleSvc服务不是由手工编写而是完全自动生成的!让我们继续,看看这是如何做到的。

引入DWR

  如同在“原始的Ajax”一节所演示的那样,直接使用XmlHttpRequest创建异步请求非常麻烦。不仅JavaScript代码冗长,而且必须考虑服务器端为定位Ajax请求到适当的服务所需做的工作,并将结果封送到浏览器。

  设计DWR的目的是要处理将Web页面安装到后端服务上所需的所有信息管道。它是一个Java框架,可以很轻松地将它插入到Web应用程序中, 以便JavaScript代码可以调用服务器上的服务。它甚至直接与Spring Framework集成,从而允许用户直接向Web客户机公开bean。

  DWR真正的巧妙之处是,在用户配置了要向客户机公开的服务之后,它使用反射来生成JavaScript对象,以便Web页面能够使用这些对象 来访问该服务。然后Web页面只需接合到生成的JavaScript对象,就像它们是直接使用服务一样;DWR无缝地处理所有有关Ajax和请求定位的琐 碎细节。

  让我们仔细分析一下示例代码,弄清它是如何工作的。

应用程序细节:DWR分析

  关于应用程序,首先要注意的是,它是一个标准的Java应用程序,使用分层架构(Layered Architecture)设计模式。使用DWR通过JavaScript公开一些服务并不影响您的设计。

  下面是一个简单的Java服务,我们将使用DWR框架直接将其向JavaScript代码公开:

package com.tearesolutions.service;

public interface AjaxSampleSvc {
Article castVote(int rank);
}

  这是一个被简化到几乎不可能的程度的例子,其中只有一篇文章可以投票。该服务由Spring管理,它使用的bean名是ajaxSampleSvc,它的持久性需求则依赖于ArticleDao。详情请参见applicationContext.xml。

  为了把该服务公开为JavaScript对象,需要配置DWR,添加dwr.xml文件到WEB-INF目录下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 0.4//EN"
"http://www.getahead.ltd.uk/dwr/dwr.dtd">

<dwr>
<allow>
<create creator="spring" javascript="ajaxSampleSvc">
<param name="beanName" value="ajaxSampleSvc" />
</create>
<convert converter="bean" match="com.tearesolutions.model.Article"/>
<exclude method="toString"/>
<exclude method="setArticleDao"/>
</allow>
</dwr>

  dwr.xml文件告诉DWR哪些服务是要直接向JavaScript代码公开的。注意,已经要求公开Spring bean ajaxSampleSvc。DWR将自动找到由应用程序设置的SpringApplicationContext。为此,必须使用标准的servlet 过滤器ContextLoaderListener来初始化Spring ApplicationContext。

  DWR被设置为一个servlet,所以把它的定义添加到web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD
Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<display-name>Ajax Examples</display-name>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<servlet>
<servlet-name>ajax_sample</servlet-name>
<servlet-class>com.tearesolutions.web.AjaxSampleServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet>
<servlet-name>dwr-invoker</servlet-name>
<display-name>DWR Servlet</display-name>
<description>Direct Web Remoter Servlet</description>
<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>ajax_sample</servlet-name>
<url-pattern>/ajax_sample</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
</web-app>

  做完这些之后,可以加载http://localhost:7001/ajax-demo/dwr,看看哪些服务可用。结果如下:

图3. 可用的服务

  单击ajaxSampleSvc链接,查看有关如何在HTML页面内直接使用服务的示例实现。其中包含的两个JavaScript文件完成了大部分的功能:
<script type='text/javascript' 
src='/ajax-demo/dwr/interface/ajaxSampleSvc.js'></script>
<script type='text/javascript'
src='/ajax-demo/dwr/engine.js'></script>

ajaxSampleSvc.js是动态生成的:

function ajaxSampleSvc() { }

ajaxSampleSvc.castVote = function(callback, p0)
{
DWREngine._execute(callback, '/ajax-demo/dwr',
'ajaxSampleSvc', 'castVote', p0);
}

  现在可以使用JavaScript对象ajaxSampleSvc替换所有的XmlHttpRequest代码,从而重构raw-ajax.html文件。可以在dwr-ajax.html文件中看到改动的结果;下面是新的JavaScript函数:

function castVote(rank) {
ajaxSampleSvc.castVote(processResponse, rank);
}
function processResponse(data) {
var voteText = "

Thanks for Voting!

"
+ "

Current ranking: " + data.voteAverage
+ " out of 5

"
+ "

Number of votes placed: "
+ data.numberOfVotes + "

";
502 502'votes').innerHTML = voteText;
}

  惊人地简单,不是吗?由ajaxSampleSvc对象返回的Article域对象序列化为一个JavaScript对象,允许在它上面调用诸 如numberOfVotes()和voteAverage()之类的方法。在动态生成并插入到DIV元素“votes”中的HTML代码内使用这些数 据。

下一步工作

   在后续文章中,我将继续有关Ajax的话题,涉及下面这些方面:

  • Ajax最佳实践

  像许多技术一样,Ajax是一把双刃剑。对于一些用例,其应用程序其实没有必要使用Ajax,使用了反而有损可用性。我将介绍一些不适合使用的模式,突出说明Ajax的一些消极方面,并展示一些有助于缓和这些消极方面的机制。例如,对Netflix电影浏览器来说,Ajax是合适的解决方案吗?或者,如何提示用户确实出了一些问题,而再次单击按钮也无济于事?

  • 管理跨请求的状态

  在使用Ajax时,最初的文档DOM会发生一些变化,并且有大量的页面状态信息存储在客户端变量中。当用户跟踪一个链接到应用程序中的另一个页面时,状态就丢失了。当用户按照惯例单击Back按钮时,呈现给他们的是缓存中的初始页面。这会使用户感到非常迷惑!

  • 调试技巧

  使用JavaScript在客户端执行更多的工作时,如果事情不按预期方式进行,就需要一些调试工具来帮助弄清出现了什么问题。

结束语

  本文介绍了Ajax方法,并展示了如何使用它来创建一个动态且响应灵敏的Web应用程序。通过使用DWR框架,可以轻松地把Ajax融合到站点中,而无需担心所有必须执行的实际管道工作。

  特别感谢Getahead IT咨询公司的Joe Walker和他的团队开发出DWR这样神奇的工具。感谢你们与世界共享它!

下载

  本文中演示的应用程序源代码可供下载:ajax-demo.war(1.52 MB)。

参考资料

原文出处

An Introduction To Ajax

http://dev2dev.bea.com/pub/a/2005/08/ajax_introduction.html


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

     摘要: 在Java2平台企业版中应用异步JavaScript技术和XML(AJAX) ...  阅读全文
posted @ 2005-11-23 11:43 Dion 阅读(1875) | 评论 (4)编辑 收藏