posts - 0, comments - 0, trackbacks - 0, articles - 23
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

java小记

Posted on 2006-05-24 10:56 叶舟 阅读(1385) 评论(0)  编辑  收藏 所属分类: Java技术文档
SimpleDateFormat函数使用的例子

public class SimpleDateFormat extends DateFormat

SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类。 它允许格式化 (date -> text)、语法分析 (text -> date)和标准化。

SimpleDateFormat 允许以为日期-时间格式化选择任何用户指定的方式启动。 但是,希望用 DateFormat 中的 getTimeInstancegetDateInstancegetDateTimeInstance 创建一个日期-时间格式化程序。 每个类方法返回一个以缺省格式化方式初始化的日期/时间格式化程序。 可以根据需要用 applyPattern 方法修改格式化方式。

SimpleDateFormat函数的继承关系:
java.lang.Object
   |
   +----java.text.Format
           |
           +----java.text.DateFormat
                   |
                   +----java.text.SimpleDateFormat

下面是个小例子:
import java.text.*;
import java.util.Date;

/**
  SimpleDateFormat函数语法:
 
  G 年代标志符
  y 年
  M 月
  d 日
  h 时 在上午或下午 (1~12)
  H 时 在一天中 (0~23)
  m 分
  s 秒
  S 毫秒
  E 星期
  D 一年中的第几天
  F 一月中第几个星期几
  w 一年中第几个星期
  W 一月中第几个星期
  a 上午 / 下午 标记符
  k 时 在一天中 (1~24)
  K 时 在上午或下午 (0~11)
  z 时区
 */
public class FormatDateTime {

    public static void main(String[] args) {
        SimpleDateFormat myFmt=new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        SimpleDateFormat myFmt1=new SimpleDateFormat("yy/MM/dd HH:mm");
        SimpleDateFormat myFmt2=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//等价于now.toLocaleString()
        SimpleDateFormat myFmt3=new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 E ");
        SimpleDateFormat myFmt4=new SimpleDateFormat(
                "一年中的第 D 天 一年中第w个星期 一月中第W个星期 在一天中k时 z时区");
        Date now=new Date();
        System.out.println(myFmt.format(now));
        System.out.println(myFmt1.format(now));
        System.out.println(myFmt2.format(now));
        System.out.println(myFmt3.format(now));
        System.out.println(myFmt4.format(now));
        System.out.println(now.toGMTString());
        System.out.println(now.toLocaleString());
        System.out.println(now.toString());
    }   
   
}

效果:
2004年12月16日 17时24分27秒
04/12/16 17:24
2004-12-16 17:24:27
2004年12月16日 17时24分27秒 星期四
一年中的第 351 天 一年中第51个星期 一月中第3个星期 在一天中17时 CST时区
16 Dec 2004 09:24:27 GMT
2004-12-16 17:24:27
Thu Dec 16 17:24:27 CST 2004

下面是个JavaBean:
public class FormatDateTime {
   
    public static String toLongDateString(Date dt){
        SimpleDateFormat myFmt=new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 E ");       
        return myFmt.format(dt);
    }
   
    public static String toShortDateString(Date dt){
        SimpleDateFormat myFmt=new SimpleDateFormat("yy年MM月dd日 HH时mm分");       
        return myFmt.format(dt);
    }   
   
    public static String toLongTimeString(Date dt){
        SimpleDateFormat myFmt=new SimpleDateFormat("HH mm ss SSSS");       
        return myFmt.format(dt);
    }
    public static String toShortTimeString(Date dt){
        SimpleDateFormat myFmt=new SimpleDateFormat("yy/MM/dd HH:mm");       
        return myFmt.format(dt);
    }
   
    public static void main(String[] args) {

        Date now=new Date();

        System.out.println(FormatDateTime.toLongDateString(now));
        System.out.println(FormatDateTime.toShortDateString(now));
        System.out.println(FormatDateTime.toLongTimeString(now));
        System.out.println(FormatDateTime.toShortTimeString(now));
    }   
   
}
调用的main 测试结果:
2004年12月16日 17时38分26秒 星期四
04年12月16日 17时38分
17 38 26 0965
04/12/16 17:38

15:40:47  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2006-3-11
了解 Tapestry,第 2 部分

2006 年 2 月 20 日

Tapestry 框架允许 Java™ 和 Web 开发人员开发动态的、轻量级的和富于响应性的基于 servlet 的 Web 应用程序。在本文中,Brett McLaughlin 继续介绍 Tapestry,即如何规划 Tapestry 应用程序的开发,创建有用的、健壮的 Tapestry 组件。

这个简短系列的第一篇文章 是 Tapestry 的介绍 —— Tapestry 是一个构建 Web 应用程序的轻量级框架。如果曾经阅读过那篇文章,应当对 Tapestry 的工作方式有了基本的理解,并了解了如何把它的核心 API 组合在一起。您可能还不敢确定如何从头开始实际开发 Tapestry 应用程序,但是在这篇文章中,我将解决这个问题。

我要从一种简单的应用程序规划方式开始,在使用 Tapestry 时,这个方式特别重要。然后,我将讨论 HTML 原型在 Tapestry 开发中的角色,并解释在编写 Tapestry 组件之前需要具备的元素。最后,我将介绍如何开发 Tapestry 组件,并把它们与 HTML 页面链接起来。您还会学到一些技巧,以确保您的规划能够适应使用应用程序的人,这是成功开发的关键,并介绍如何为了重用而规划和开发 Tapestry 组件。在开始之前请参阅 参考资料 一节下载 Tapestry。

规划应用程序

如果您属于某类开发人员,您可能会憎恨规划 这个词,认为应该把时间花在做些实际的事情上!但是,规划是开始构建 Tapestry 应用程序(或者其他类型的应用程序)的最好途径,所以我将从介绍如何尽可能没有痛苦地进行规划开始。

Tapestry 框架使用实际的 HTML 页面,把它们当作模板,并把这些模板与 Tapestry 组件链接在一起。然后通过部署描述符把所有这些捆绑在一起,形成耦合紧密且相当复杂的文件集。Tapestry 应用程序有以下的典型组件:

  • HTML 页面(被 Tapestry 当作模板)
  • Tapestry 类
  • Java bean 和工具类
  • servlet 部署描述符 (web.xml)
  • Tapestry 的应用程序描述符(app.application)

如果一个猛子扎进去开始开发应用程序代码,眼前很快就会充满杂乱的注释、难以寻找的 bug,以及有时已更新有时没更新的模板。规划是真正有效利用 Tapestry 的惟一途径,所以请尝试尽可能无痛苦地做规划的这个三要点方式。





回页首


从问题开始

规划的第一部分是询问一个简单但是非常重要的问题:这个应用程序要做什么? 虽然看起来可能微不足道 —— 甚至陈腐 —— 但是,这是对应用程序所能提出的最重要的问题。而且,令人惊讶的是,它又是最经常被忽略的。下面是在开发过程开始时一些比较常见的问题的列表(听起来挺熟?):

践踏客户和规范!

不,不是真的。即使您憎恨规划,如果没有与应用程序的利益相关者(stakeholder)沟通,那么就要准备失败了。不论代码有多漂亮,都必须按照客户的期望工作 —— 哪怕您对客户的期望不以为然,也要如此。所以,现在请保留对规划的判断(即,它有多讨厌),并尝试我在这里演示的方式。每次都会形成更好的应用程序!

  • 要使用什么技术?
  • 什么样的客户机(Web 浏览器、移动电话、PDA)要访问应用程序?
  • 应用程序要在哪个平台上运行(Windows、Linux、Mac OS X、Sun Solaris,等等)?
  • 什么时候必须完成?

这些都是有用的问题,但是如果应用程序实际做的工作不是利益相关者所期望的,那么所有这些都无关紧要。

把这应用于 Tapestry,如果不知道应用程序的基本目的,就不会写出好的 Tapestry 代码 —— 不论代码的技术有多漂亮。Tapestry 代码用于两个基本目的:

  • 与数据源交互并实现业务逻辑。
  • 向表示组件(像 HTML 模板)提供数据。

因为编写 Tapestry 代码不是为了做炫耀的显示或视觉效果,所有的 Tapestry 代码都应当处理应用程序的核心任务。如果不知道核心任务是什么,那么就等着浪费大量时间开始一个空白屏幕,然后迷惑不解地想知道接下来要做什么吧。





回页首


描绘出业务过程

一旦理解了应用程序的基本目的,就可以开始列出实现这个过程所包含的全部具体业务过程。业务过程与应用程序的目的不同。应用程序的目的 通常是简洁的、高层的描述,概括应用程序做的每件事。业务过程 则是小得多的工作单位,通常以各种方式和其他业务过程一起重用,共同实现特定的任务。

例如,假设要编写一个应用程序,管理在 BurgerDome 的厨房。基本目的可能只是 “让客户得到想要的,按单烹饪”。但是这个目的自然有许多独立的业务过程;比如下面这些:

  • 下新单
  • 做一个汉堡
  • 向汉堡加蔬菜和调料
  • 将汉堡打包,递送给客户

当然,可能会发现这些过程还可以进一步细分:

  • 做汉堡
    • 做汉堡馅
    • 加热饼胚
    • 烤饼胚
  • 给汉堡加材料
    • 加蔬菜
    • 加调料
    • 加调味品
    • 加芝士

这些过程可能有点傻,但是很快就会看到即使一个相对简单的应用程序也会有 50 或 100 个不同的业务过程,可以用它们或者把它们与其他过程组合在一起,实现一个基本目的(在这个示例中,是制作客户点的汉堡)。

把业务过程转换成组件

关于 Tapestry 中的业务过程,有趣的是,实际上只有很少的被绑定到 Tapestry 类。例如,BurgerDome 应用程序的大多数任务根本没有绑定到显示,而且构建一个 Tapestry 组件去给汉堡加芥茉没有太大意义。在这个应用程序的规划阶段,描述出所有过程,会有助于确定实际需要使用 Tapestry 创建什么业务组件,以及哪些组件要传递给其他类型的组件。例如,可能创建了一个 Tapestry 组件,接受汉堡订单做为输入,但是可以把它在添加调味品的请求中传递给另一个非 Tapestry 组件。

在规划阶段的末尾,应当已经描绘出一套处理业务的每个具体细节的业务对象。除此之外,应当有一套 Tapestry 对象,把这些业务对象组织成有意义的工作单元。在清单 1 中可以看到这个编程模型的实际作用:


清单 1. 简单的订单类
																								
																										package com.burgerdome.display;

import org.apache.tapestry.annotations.Persist;
import org.apache.tapestry.html.BasePage;

import com.burgerdome.order.*;

public abstract class Order extends BasePage
{
    private Order order;

    public void placeOrder(int burgerType, int doneness)
    {
        Burger burger = new Burger(burgerType, doneness);
        Order order = new Order();
        order.addBurger(burger);
        setOrder(order);

        OrderQueue queue = OrderQueue.getInstance();
        queue.add(order);
    }

    public void cancelOrder()
    {
        OrderQueue queue = OrderQueue.getInstance();
        queue.remove(order);
    }

    protected void setOrder(Order order) {
        this.order = order;
        order.setID(OrderUtils.newOrderID());
    }

    protected Order getOrder() {
        return order;
    }
}

																								
																						

从清单 1 可以看到,Tapestry 组件用 BurgerOrder 类与屏幕交互(假设允许客户下订单),这两个类都是 BurgerDome 业务组件库的组成部分。注意,BurgerOrder不是 Tapestry 类,而且实际上根本不知道 Tapestry。另外,OrderQueue 类既不是 Tapestry 类也不是 业务组件,它处理许多业务方面:接受订单并用有用的方式组织它们。

这里的要点是,如果没有仔细的规划,这些过程就不会成为一个整体。关于确定哪个类特定于 Tapestry、哪个特定于业务,以及它们如何交互,所有这些都最好是在代码编辑器中面对一个空白类之前做完。





回页首


创建导航图表

一旦理解了拥有什么 Tapestry 组件,以及基本业务组件如何协作,那么开发应用程序的导航就相对容易了。这个时候请记住,可能还不需要编写 HTML 页面或者四处乱放 href。用两页纸画好页面,并用箭头把页面连接起来,应当就够了。或者用另一种方法,即采用简单的流程图,图上的每个方形或矩形代表一个屏幕,箭头代表用户在应用程序中可能经历的各种 “路径”。

不论如何做,规划应用程序的导航实际上是一个简单但却会大大有利于开发过程的实践。一旦开始手动构建 HTML 页面(下一节将详细讨论),就不必再花时间考虑在页面上有什么链接、应当链接到哪,因为所有这些都已经规划好了。

描绘导航的一个重要的附带作用就是可以迅速地发现 “孤儿页面”。孤儿页面是这样一些页面,它们要么是没有链接到其他页面,要么是没有应该那么多的页面链接到它们。这些页面代表的特性是,用户需要与之交互,但是如果不纠正导航设置,这些特性就可能无法(或者容易地)访问到。在应用程序开发过程中,由于没有正确地把页面连接到其他页面,所以孤儿页面很常见。在开始编码之前规划应用程序的导航,是尽早隔离并纠正问题的好方式,这会形成更平滑的开发周期。

反复规划

关于规划,需要知道的最后一件事就是,规划不可能完美,也不应当一成不变。虽然规划很重要、很有用,但是肯定不可能考虑好每件事,甚至已经做了的也会改变。不论花多少时间考虑应用程序的目的、规划它的导航,都会遗忘一些事情。而且,即使什么也不忘记,也必须面对后来的特性请求或者在最想不到的地方出现 bug 或者生活本身的不可见因素;所以请保持灵活。

形成健壮的计划,然后愿意根据需要调整它,那么在应用程序开发中就占了先机 —— 特别是在使用 Tapestry 开发应用程序时。





回页首


编写 HTML 页面

应用程序规划好后,就可以编写 HTML 页面了。虽然从严格意义上讲,这不是真正的 “编码”,而且可能被当作可以留在最后再做的事情,但是应当一直从编写 HTML 代码开始应用程序的开发。

什么时候开发业务对象

编写业务对象的时机,对于具体的项目和公司是各不同的。对于许多项目来说,将使用现有的业务对象,所以根本不必编写代码(或者可能只是编写少量代码)。在某些情况下,编写的业务对象会用于多个应用程序,所以最好在编写具体的应用程序代码之前开发这些对象。而在其他情况下,可以在编写应用程序的剩余部分时开发业务对象。简而言之,开发业务对象的正确时机主要依个人偏好和项目要求而定。

从 HTML 开始的最大理由是:客户、最终用户、营销团队、经理以及 alpha 和 beta 测试人员会看到这些页面。虽然可以给 Java 类添加 main() 方法,并在命令行测试它们,但是多数用户会发现 Web 浏览器最适合测试 Web(特别是 Tapestry)应用程序。

更重要的是,Tapestry 用 HTML 文件作为它的页面模板,所以如果没有基本的 HTML 页面,开发 Tapestry 组件的压力会很大。在许多情况下,页面设计实际上会指明您为 Tapestry 组件所做的决策。

这一节介绍编写应用程序 HTML 的基础。





回页首


从原型开始

在开始拼凑出成百行的 CSS 样式表和复杂的流动布局之前,请认识到最好的应用程序原型是简单的,有时甚至是粗陋的。最好是从一个非常基本的页面开始,就像清单 2 所示的那样:


清单 2. 销售报表的原型
																								
																										<html>
 <head><title>Sales Report Prototype</title></head>
 <body>
  <h1>Prototype Sales Report</h1>
  <table>
   <tr><th>Total Sold</th><td>1012</td></tr>
   <tr><th>Sales Price</th><td>$29.95</td></tr>
   <tr><th>Manufacturing Cost</th><td>$8.22</td></tr>
  </table>
  <h2>Net Profit: $167718.76</h2>
  <form method="GET">
   <input value="Get Updated Sales" type="button" />
  </form>
 </body>
</html>

																								
																						

图 1 显示了这个页面出现在 Web 浏览器中的效果:


图 1. Web 页面原型
好的原型非常简单,而且标记清楚

这看起来不是很像,但这就是关键!松散的、简单的页面,可以容易地移动或修改事物。添加表格行或把标题移动到顶部,做起来会很简单,不会弄乱精心调整的布局或颜色方案。另一方面,请想像一下,如果花了几个小时处理样式表,才能让标题就位,让打印效果和颜色方案满意,然后进入会议室,却听到下面这些评论,您会有什么感受:

“这个菜单项需要再向下一点”

“我讨厌橙色的阴影;公司的 logo 用海蓝色。”

“我们不能用 serif 字体么?”

关于这类评论,最糟的是,它们并不是您想得到的应用程序资金提供者、销售人员或用户的评论。但是,需要指出的重要的一点是,这些评论没有一个与页面应当做什么、它要传递的基本信息是什么有关。在原型阶段保持事物简单,可以让应用程序的利益相关者对于应用程序的功能拥有基本的认识,而不会把大量时间浪费在颜色和设计这些可能会改变的问题上。如果有人抱怨原型中事物看起来的样子,您可以向他们保证最后完成的 Tapestry 应用程序看起来会很棒!

这是一项未完过程

就像我在清单 1 和图 1 中所做的那样,请一直认真地把原型标记为原型。在 HTML 的标题上(在 title 元素中)和实际的页面中都要放上单词 “prototype”(h1h2 元素通常可以胜任这个工作)。清楚地标记原型过程的每一部分的好处很明显:

  • 在向销售人员和最终用户介绍原型时,他们不会认为自己正在看的是完成的产品。这会挡住那些让大多数开发人员发疯的评论,例如 “为什么它看起来这么差?” 和 “我们能不能让表格的文本用紫红色?”

  • 如果有些勤奋的销售经理把您的工作展示给总经理,他们也会 认识到工作还在进行当中,而不会闯入您的办公室,质问您为什么付给您那么多,您却只拿出这么可怕的 Web 页面。

  • 在开始开发 Tapestry 组件时,这些 “prototype” 标题可以提醒您页面什么时候已经真正完成而什么时候仍然在进行当中。请确保在页面编码完成,可以部署和测试时,删除 “prototype” 标题是所做的最后一件事。

虽然原型法一直是个好的开发实践,但是 Tapestry 还进一步让它成为特别有益的 实践。在某些开发环境下,原型模板在进入实际开发时,有时会失效,但是使用 Tapestry,可以方便地用原型作为开发应用程序的模板。例如,在使用独立的 Java servlet 时,最后必须抛开原型模板,把 HTML 拷贝粘贴到 out.println() 语句或 JSP 标记中。但是使用 Tapestry,可以把原型属性有效地应用到开发工作中。如果要证明在 HTML 模型上花的时间没白花,Tapestry 可以证明!





回页首


使用真实数据

基本页面准备好之后,需要用真实数据填充模型。对于多数开发人员来说,很容易用简单的金额(例如 $99.95)代表销售额,后面再用一些傻乎乎的文本值,例如 “foo” 或者一些长长的拉丁字符串(对于我和其他要查看模型的人来说,这种做法毫无意义),但是请不要这么做!由于两个原因,使用不真实的占位符是个坏主意:

  • “假” 数据不能代表真实数据在页面上会占据的空间。

  • 页面包含一两个单词时的样子,与包含多行文本时的样子,会有显著不同。同样,只有二、三位数字的数值与有五、六位数字的值看起来也不同。

我不是在建议您需要去做无数的调研,然后在页面中使用正确的、实时的数据。只需要对于数据最后在页面上会是什么样子有良好的认识就可以。例如,如果销售价格的范围预计是在 $50.00 和 $5,000.00 之间,那么这对开发真实的原型就是足够的信息。对于文本长度来说,也是一样,不过,可能需要更具体一些的范围;如果文本要在 1,500 和 3,000 字之间,可以规划得很好。如果文本会在 1,500 和 30,000 字之间,那么就很难编写出能容纳两端范围之间的标记(所以也应当让利益相关者知道这一点)。

对于数据的范围和类型有了主意之后,可以开发能够显示范围两端的原型;例如,对一个页面,使用 $50.00 的值,另一个页面用 $5,000.00 的值。这样可以保证所有可能的值都会适合所分配的空间,按照预期形式换行,格式化也正确。对于文本、标题、表单字段等,也是一样的。在页面上出现的任何包含数据的内容,都应当用真实数据而不是一堆 “程序员” 值进行测试。





回页首


添加结构元素

现在,可以添加 JavaScript、图片和活动导航了。在许多 Web 开发框架下,可能就是在这里抛弃原型,开始把标记转换成 servlet、JSP 或视图组件的。但是,使用 Tapestry,可能已经得到了有用的模板,只需要添加一些 “分隔和修饰”,好让原型更适合使用。

可以从为页面添加 CSS 样式表开始,与 divspan 元素一起,区分页面不同区域并设置区域的样式。现在开始要认真了,可能还需要改变一些页面结构,例如从占位符列表转换成表格,或者反过来。所有这些变化,都会给页面添加新的结构层和样式,使它们更加可用于应用程序中。

如果想用 JavaScript 给页面添加动态值或处理图片交换,现在可以把这个代码加到页面里了。也可以给页面添加图片。不论做什么,请记住,正在处理的页面,会实际地 由 Tapestry 用来驱动应用程序,所以正在执行的是有用的工作,而不再是浪费时间在完善模型上。

添加结构到模板

在这个阶段所做的全部工作是创建更好的模板。而且,对于 divspan 元素,实际上是在准备 Tapestry 交互。在清单 3 中可以看出这点,清单 3 是清单 2 所示的原型的 HTML,只是新添加了一些 divspans


清单 3. 添加销售报表的结构
																								
																										<html>
 <head><title>Sales Report Prototype</title></head>
 <body>
  <h1>Prototype Sales Report</h1>
  <div id="sales">
   <table>
    <tr><th>Total Sold</th><td><span id="total-sold">1012</span></td></tr>
    <tr><th>Sales Price</th><td>$<span id="board-cost">29.95</span></td></tr>
    <tr><th>Manufacturing Cost</th><td>$<span id="man-cost">8.22</span></td></tr>
   </table>
   <h2>Net Profit: $<span id="net-profit">167718.76</span></h2>
   <form method="GET">
    <input value="Get Updated Sales" type="button" />
   </form>
  </div>
 </body>
</html>

																								
																						

虽然变化不大,但是可以看出页面中的每个值现在有了一个 span 设置,还有一个 ID 标记。所以,页面可以容易地转化成 Tapestry 组件,下一节就会看到。页面中的数据是真实数据(基于项目的规范),结构也准备好了,可以转入 Tapestry 应用程序了。





回页首


导航性注释

惟一剩下要做的就是创建页面间的导航路径。现在,可以用普通的 HTML a 元素加 href 属性做这件事,但是在创建最终应用程序时,这些链接可能必须变化。如果在 Tapestry 中想避免大多数页面间的直接链接,可以访问 “即时的” Tapestry 页面,它只使用 HTML 文件作为模板。

即使有这个秘诀,也要花相当的时间构建一套页面间的链接。理解这些链接将去向哪里,它们如何呈现给用户,是构建 HTML 框架的重要部分。

结构元素、图片和导航链接都设置好之后,几乎就可以开始编写 Tapestry 组件了。在这之前,最好是让团队、经理、销售人员,可能还有 alpha 测试人员,把所有内容都运行一下。这可以确保在开始编码之前,应用程序的观感符合利益相关者的需要。





回页首


构建 Tapestry 组件

对于大多数开发人员,第一次使用 Tapestry 的最大惊讶就是几乎没什么事可做。如果在应用程序规划和 HTML 页面上的工作做得很好,那么编写 Tapestry 代码就变得非常简单。Tapestry 主要作为粘合剂,将应用程序的表示和驱动应用程序的逻辑粘合起来,所以实际上在 Tapestry 中上花在编写复杂代码的时间惊人地少,更多的时间花在把 HTML 页面连接到业务组件上。

实际上,这是 Tapestry 的一个真正亮点:替您铺好路。因为编写了标准化的 HTML,并添加了一些特定于 Tapestry 的元素,所以几乎没有影响 HTML 的 Tapestry 代码。更好的是,Tapestry 标记不会影响应用程序的显示,所以您的标记可以包含这些标记,您的设计什么也不包含。换句话说,在使用 Tapestry 和不使用它的页面之间,永远不会看出视觉上的差异。而且,业务逻辑也根本不会被 Tapestry 影响。惟一真正属于 Tapestry 的代码就是一套简单的类,用来把应用程序的所有片段连接起来。在使用其他许多框架时,从只有 servlet 或 JSP 的框架到诸如 Struts 或 Spring 之类更复杂的框架,通常要编写许多特定于框架的和与框架有关的代码。幸运的是,使用 Tapestry 时不需要这样。

处理业务逻辑

在开始编写 Tapestry 组件之前,还有最后一件必须要做的事:必须确保没有业务逻辑终止于 Tapestry 代码中。这意味着应当精心定义了(最好已经编写了)所有业务任务的类。如果没有做这个工作,就会被引诱着(通常是被迫)把一些逻辑放在 Tapestry 页面中。有时,这么做会是让应用程序运行起来的 “最快” 途径,但是在这类情况下,“最快” 通常是名不符实的,因为业务逻辑更改时,要花很多时间对应用程序做修改(如果因为订购鞋的方式或冰箱送货的方式变化,就必须修改驱动显示的代码,那么这可不是好的应用程序设计!)

所以,请在应用程序的表示(已经开发的 HTML 页面)、业务逻辑(已经准备好的 Java 类,通常在另一个 JVM 或服务器上)和把这些部分连接到一起的胶水(Tapestry 代码)之间保持清晰的界限。遵守这个简单的原则,会让 Tapestry 代码编写起来更快,因为只是调用业务对象并用调用的结果更新表示而已。





回页首


从对象开始

开始开发 Tapestry 组件时,可以对需要至少一段动态数据或需要与业务对象交互的 HTML 页面做一个列表。这个列表应当只有文件的名称和页面目的的简短描述,例如:

  • Home.html:应用程序主页。
  • Order.html:新订单的主订单页面。
  • Status.html:检查订单状态的页面。
  • Comments.html:留言页面。

对于列表中的每个页面,都要创建一个新的 Java 类,可以用与页面相同的名称作为类名。例如,清单 4 是驱动 Comments.HTML 页面的类的骨架代码:


清单 4. 检查订单状态的简单 Tapestry 类
																								
																										package com.burgerdome.display;

import org.apache.tapestry.annotations.*;
import org.apache.tapestry.html.BasePage;

public abstract class Status extends BasePage {

  @Persist
  public abstract int getOrderNumber();
  public abstract void setOrderNumber(int orderNumber);

  // Methods go here
}

																								
																						

可以从这些行开始每个 Tapestry 类:类名与它交互的文件的名称(Status.java 用于 Status.html、Comments.java 用于 Comments.html,等等),确保类扩展自 org.apache.tapestry.html.BasePage 类。确保给 Tapestry 类提供了一个包;通常在同一包中找到所有页面最容易。还需要导入 BasePage,而且前进一步并导入 Tapestry 注释也是一个好主意;在开发的几乎每个 Tapestry 组件中都会使用它们。

最后,前进一步,设置可能需要的持久变量;清单 4 中的示例保存一个订单号,该订单号用于在应用程序的业务对象区查询订单。请了解 @Persist 并不代表 Tapestry 要在数据库或其他永久存储中持久化或保存变量;它只表明变量在重复调用对象实例期间一直可用。这意味着可以允许用户只输入值(在这个示例中代表订单)一次,然后反复使用这个值,而不需要用户每次返回状态页面都输入这个值。还请注意,没有为持久变量声明类型;只是提供了 “getter” 和 “setter” 方法,而 Tapestry 负责剩下的处理。类本身被标记成抽象的,这允许 Tapestry 负责设置类的实例,并把实例挂接到 Tapestry 引擎。

清单 4 中的简单示例可以充当所有 Tapestry 对象的起点。只要修改名称和任何需要的持久变量,让页面对象投入使用的工作就完成了一半(有时甚至更多)。





回页首


添加操作

下面考虑没有绑定到页面上的简单值的操作。例如,在状态页面中,可能让用户输入订单号,然后让另一个按钮或链接向用户提供他们的状态。第一个操作被紧密地绑定到清单 4 所示的 setOrderNumber() 方法,第二个操作则需要查询订单号。清单 5 展示了处理这个任务的简单代码:


清单 5. 添加订单处理
																								
																										package com.burgerdome.display;

import org.apache.tapestry.annotations.*;
import org.apache.tapestry.html.BasePage;

import com.burgerdome.order.*;

public abstract class Status extends BasePage {

  
																										
																												
																														
																																public abstract Order getOrder();
  public abstract void setOrder(order);
																														
																												

  @Persist
  public abstract int getOrderNumber();
  public abstract void setOrderNumber(int orderNumber);

  
																										
																												
																														
																																public void getStatus() {
    OrderQueue queue = OrderQueue.getInstance();
    Order order = getOrder(getOrderNumber());
    setOrder(order);
  }
																														
																												
}

																								
																						

您会注意到这个代码中的几个新部分。首先,导入了一些业务对象;在这个示例中,这些对象在 com.burgerdome.order 包中。其次,我添加了两个新方法:getOrder()setOrder()。这两个方法被标记为抽象的,这样 Tapestry 会把它们实现为简单的 “getter” 和 “setter” 方法,并为这个类创建类型为 Order 的新变量。除非确实有好的理由不这么做,否则最好是让 Tapestry 替您管理这些变量。

还请注意这两个新方法被放在 @Persist 注释上面。这意味着订单在请求或会话间不会被持久存储。因为订单在变化,所以在每次请求的时候查询它并检查它的状态会更容易。还请记住,因为业务对象通常在独立的 JVM 中运行,订单可能在另一个 JVM 中在变化,所以本地保留的对象拷贝可能会过时。

一般来说,只持久存储对客户来说不改变的条目(比如订单号本身,它在请求之间不会改变)或者不由业务对象使用的条目。例如,可能有一个分配给客户的表编号,业务对象不会在上面操作,所以让它持久存储在 Tapestry 中是可以接受的。

这个类中的最后一件事是添加了 getStatus() 方法,用于把订单号(已持久存储的)连接到订单,该订单是在每次请求时都要查询的。用户查找到他们的订单之后,就可以容易地通过订单的方法访问订单了;可以调用 order.getRemainingCookTime()order.change() 方法,还可以在需要的时候让 Tapestry 把这些请求发送回业务层。

在您自己的应用程序中,这是惟一真正需要编写许多特定于 Tapestry 的代码的地方:当与显示有关的事件发生时,必须修改业务对象。用这种方式,Tapestry 把用户的动作连接到后端代码,后端代码对这些动作作出响应。





回页首


回顾 HTML 链接

Tapestry 代码就绪之后,就需要回到 HTML,把 HTML 与刚才编写的代码连接起来。实际上,要做的全部工作只是找到页面中的所有 aspan 元素。对于 a 元素,先找出哪个链接到外部页面 —— 在其他站点上、不是应用程序的一部分的页面 —— 并 “丢弃” 它们(换句话说,不用考虑它们)。剩下的应当具有像这样的链接:

																								
																										<a href="Home.html">Return to main screen</a>

																								
																						

添加另一个属性,叫做 “jwcid”,它的值为 “@PageLink”。这让 Tapestry 知道正在创建到其他 Tapestry 页面的链接。现在的链接看起来像这样:

																								
																										<a href="Home.html" jwcid="@PageLink">Return to main screen</a>

																								
																						

然后,把 href 属性的名称改成 “page”。page 属性让 Tapestry 知道要连接到其他哪个由 Tapestry 控制的页面(这就是为什么可以忽略外部链接的原因:它们在那呆着就很好)。然后,删除 “.html” 扩展名。现在的链接看起来像这样:

																								
																										<a page="Home" jwcid="@PageLink">Return to main screen</a>

																								
																						

最后,a 元素要求 href 属性,所以把它添加回去,但是值为 “#”。这告诉 HTML 把它连接回同一页面,然后 Tapestry 处理实际的链接。最后的链接看起来像这样:

																								
																										<a page="Home" jwcid="@PageLink" href="#">Return to main screen</a>

																								
																						

用这种方式把每个 HTML 页面中的每个链接进行转换,然后应用程序的导航会在 Tapestry 和动态页面之间开始工作,而不是在静态的 HTML 模板之间。





回页首


添加动态数据

现在需要把假日期更新成即时值。通过 @Insert 注释做这件事最容易。作为示例,请看清单 6,它把第一个 span 标记转换成使用来自与这个页面相关的 Tapestry 类的值:


清单 6. 添加真实数据到 HTML 模板
																								
																										<html>
 <head><title>Sales Report Prototype</title></head>
 <body>
  <h1>Prototype Sales Report</h1>
  <div id="sales">
   <table>
    <tr><th>Total Sold</th><td><span jwcid="@Insert" value="ognl:totalSales" 
      id="total-sold">1012</span></td></tr>
    <tr><th>Sales Price</th><td>$<span id="board-cost">29.95</span></td></tr>
    <tr><th>Manufacturing Cost</th><td>$<span id="man-cost">8.22</span></td></tr>
   </table>
   <h2>Net Profit: $<span id="net-profit">167718.76</span></h2>
   <form method="GET">
    <input value="Get Updated Sales" type="button" />
   </form>
  </div>
 </body>
</html>

																								
																						

@Insert 告诉 Tapestry 插入数据,而 value 属性则用来指定插入什么数据。在这个示例中,“ognl” 是 Tapestry 页面可以使用的库,用它可以调用 Tapestry 页面;“totalSales” 是页面对象中的变量名称。添加的这段代码的结果是页面显示时有了真实数据,而不是 1012 这个假值。

还需要处理所有的页面,做类似的修改,这样所有的假数据就被真实数据替换了。而且,因为花了时间来确保示例数据的长度是真实长度,所以在真实数据插入页面时,不会造成任何显示问题。





回页首


一点配置……

所有后台工作完成之后,再与几个配置文件连接起来就是小事情了。由于使用相同的名称,页面和类之间已经创建了隐式的映射。现在,需要通过显式化,让 Tapestry 引擎知道这个隐式映射。请在 servlet 上下文的 WEB-INF/ 目录中创建一个新文件,叫做 app.application。然后添加一个叫做 “org.apache.tapestry.page-class-packages” 的新键,并用 Tapestry 类所在的包的名称作为键值。清单 7 展示了一个示例 app.application 文件:


清单 7. app.application 文件
																								
																										<?xml version="1.0"?>

<!DOCTYPE application PUBLIC
  "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">

<application>
  <meta key="org.apache.tapestry.page-class-packages" 
    value="tutorials.directlink.pages"/>
</application>

																								
																						

由于已经做了很多工作,所以这一步很简单。另外,Tapestry 框架已经设置好,所以这也很容易;简单的文件和类名称已经准备好,所以这只不过是连接两套组件而已。

还需要用标准的 Web.xml 部署描述符让 servlet 引擎知道,应用程序也在 WEB-INF/ 中。清单 8 展示了需要的内容:负责核心 Tapestry servlet 的 servlet 入口,以及这个 servlet 的 URL 映射:


清单 8. 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>BurgerDome Ordering System</display-name>

  <servlet>

    <servlet-name>app</servlet-name>

    <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>

    <load-on-startup>0</load-on-startup>

  </servlet>

  <servlet-mapping>

    <servlet-name>app</servlet-name>

    <url-pattern>/app</url-pattern>

  </servlet-mapping>

</Web-app>

																								
																						

现在已经有了 HTML 页面、Tapestry 组件、业务对象,以及把所有这些部分连接在一起的描述符。应用程序可以运行了!





回页首


结束语

在这篇文章中,我从 Tapestry 的初始概述(来自 了解 Tapestry,第 1 部分)转移到介绍如何用 Tapestry 框架实际地开发应用程序。我没有强调许多代码,而是集中在使用 Tapestry 开发应用程序的过程以及如何做好上面。虽然多数 Java 程序员可以容易地学会一个新 API,但是许多人更困难的是弄清楚如何最好地使用这个 API。我希望您从这篇文章懂得的是 Tapestry 开发中规划的重要性,以及良好的规划如何会有利于最终的实现。我强调的 Tapestry 开发过程的另一个好方面是重用。通过在编写组件之前仔细考虑,最终会得到可重用的组件工具箱,而不必编写不能共享组件的许多不同的应用程序。

虽然这篇文章的主题是 Tapestry,但是我讨论的许多想法可以应用于任何编程框架,特别是基于 Web 的框架。您会发现,大多数好的应用程序都涉及到精心的规划,为了避免不断的重新设计,不能草草规划。如果花时间仔细规划,然后设计原型,那么最后差不多总会得到更精致的、响应更好的应用程序,也会极大地提高客户和最终用户对最终产品满意度。





回页首


参考资料

学习

获得产品和技术

讨论




回页首


关于作者

Brett McLaughlin 的照片

Brett McLaughlin 从 Logo 时代就开始使用计算机(还记得那个小三角么?)。最近几年,他成为 Java 和 XML 阵营最知名的作者和程序员。他曾在 Nextel Communications 工作,实现复杂的企业系统;也曾在 Lutris Technologies 编写应用程序服务器;最近是在 O'Reilly Media 公司工作,在这里他继续撰写和编辑相关书籍。他的最新力作 Java 5.0 Tiger: A Developer's Notebook,是关于 Java 最新版本的第一本书,而他经典的 Java and XML 仍然是在 Java 中使用 XML 技术的绝对经典之一。


18:38:16  |  固定链接 | 引用通告 (0) | 记录它
了解 Tapestry,第 1 部分

2006 年 2 月 06 日

在这篇由两部分构成的文章的前一部分中,作者兼 developerWorks 的热心贡献者 Brett McLaughlin 对 Tapestry 进行介绍,从它的安装一直到文件结构。您可以看到 Tapestry 怎样帮助使用 HTML 和模板标记进行基于 servlet 的 Web 应用程序开发。

如果想出售产品,那么拥有在线设施是很重要的,不论是运作一家数百万美元的公司,还是只是想在假日处理几千个线手镯。在某些情况下,修补一些 Web 页面,并采用一些像 PayPal 或 eBay 这样的预先打包好的支持系统处理销售事务,也可以做得足够好。但是一个全面的、设计良好的 Web 应用程序应当能把在线销售从小打小闹提升到专业的、动态的在线商店的层次。每个人都知道,消费者在商店中买的东西比从跳蚤市场买的东西多。

问题在于,在进入大多数 Web 开发框架时 —— 特别是在使用 Java™ 作为编程语言的时候 —— 会发现复杂性太高。Struts、JSF 和 Spring 都是优秀的 Web 开发框架,但是它们中没有一个是适合胆小的人。(如果您曾经试着向新 Java 开发人员解释控制反转,您就会知道我谈的是什么了!)幸运的是,还有更容易的选择。

Tapestry 是在 Apache 软件许可下发布的一个开放源码的、基于 Java 的框架,它是专门为了简化 Web 开发设计的。它有以下关键特性:

  • 易于安装:不需要是 Web 应用程序高手,就可以让 Tapestry 启动并运行。

  • 易于使用:只需要基本的 Java 和 HTML(是的,确实是 HTML,而不是 servlet)技能就可以编写 Tapestry 应用程序。

  • 易于扩大规模。当站点增长超过 10、50 或 100 个页面时,也不必抛弃 Tapestry。对于多数 Web 应用程序来说,Tapestry 都足够健壮。

在这篇文章中(系列的前半部分),我会带您入 Tapestry 的世界。我将带您一起经历下载和安装这个框架的每一步,然后运行一些示例应用程序,让您理解它的工作方式。在下一篇文章中,我将介绍更高级的示例,让您更好地掌握 Tapestry 的核心框架。在了解核心框架之前,您将完全适应了 Tapestry 环境。

简化的安装

您很快就会了解到 Tapestry 的口号是简单。这种简单不代表原始不成熟,而是代表易于理解易用直观。因为安装是任何一项新技术的入门,所以毫不夸张地说,安装 Tapestry 非常容易。对于入门者来说,只有很少的前提要求,而这些要求对于 Web 开发人员来说,都是相当标准的,所以它们可能已经安装了。(如果没有,那么请参阅 参考资料 一节获得技术下载。)

Tapestry 中的标注

Java 5.0 中的一项新特性是标注语法。使用标注,可以用一种与内联注释非常相似的样式对代码进行标记,标注以 @ 符号开始。可以使用标注为类添加元数据,然后编译器或 Tapestry 这样的框架可以使用元数据。具体来说,Tapestry 允许使用标注,直接在代码中描述原本需要在额外页面或组件中定义的行为。请参阅 参考资料 学习关于 Java 5.0 中标注的更多内容。

前提条件 1:Java 5.0

显然,使用 Tapestry 的第一个要求是,在机器上要有 Java 平台正在运行。虽然 Tapestry 几乎可以和任何相对较新的 Java 版本合作(Java 1.3 及以上版本),但最好是使用 Java 5.0。Tapestry 的几个可选特性只能在 Java 5 下工作。而且,现在真的是没有什么理由 转换到 Java 5:它是稳定并经过良好测试的,已经经过了最初始的发展痛苦。

前提条件 2:构建工具

有了 JVM 和 servlet 引擎,还需要 Apache Ant 这样的构建工具。虽然下载 Ant 很容易,但需要确定在路径中有 Ant 的二进制代码(对于 Linux/Mac OS X 系统是 ant,对于 Windows 系统是 ant.bat)。如果在空目录中输入 ant,可能会看到下面这样的输出:

																								
																										Buildfile: build.xml does not exist!
Build failed

																								
																						

前提条件 3:微内核

最后需要安装一个微内核,叫作 HiveMind,用它为 Tapestry 应用程序登录对象。我不会在这篇文章中详细讨论 HiveMind —— 它本身就值得用几篇文章讨论!—— 但是您会看到 Tapestry 如何使用它。在从 Jakarta Web 站点下载了 HiveMind 之后,请把它放在与 Tapestry 相同的目录中;例如,可能使用 /usr/local/java/hivemind-1.1 或 C:/java/hivemind-1.1。任何位置都可以,只要记住把它放在哪儿了!

Tapestry 实际上还有许多依赖项,但是安装了 HiveMind、Ant 和 Java 5.0 之后,就能让 Tapestry 自己处理剩下的依赖项。因为 Tapestry 是一个 Web 应用程序框架,所以在准备运行应用程序时,还需要一个 servlet 引擎,例如 Apache Tomcat(请参阅 参考资料)。Tapestry 应用程序打包成 WAR 文件(Web 应用程序档案),这样就可以把它们放进 servlet 引擎的 Web 应用程序目录中。这篇文章的所有示例都使用 Tomcat,但是可以很容易地把 Tapestry 用于您选择的其他 servlet 引擎。





回页首


下载 Tapestry 4.0

所有的前提条件就绪之后,请前往 Jakarta Tapestry 的主页(请参阅 参考资料),从 Apache 的镜像站点下载 Tapestry。先选择最新的 Tapestry 4.0 版,编写这篇文章时是 tapestry-4.0-beta-12.tar.gz(Windows 用户请选择 .zip 文件)。一般来说,最好是用稳定版来学习一个新框架,而不要使用更新的 beta 代码。但是,Tapestry 4.0 在 2005 年 11 月已经接近 beta 周期的末尾,相对于 Tapestry 3.x 来说提供了显著的 改进。(在阅读这篇文章的时候,Tapestry 4.0 可能已经能够使用了。)

在 Tapestry 的下载站点上时,您可能还想下载文档集。在编写这篇文章的时候,文档集文件叫作 tapestry-4.0-beta-12-docs.tar.gz。展开这个档案,应当得到名为 tapestry-4.0-beta-12 的新文件夹,但是如果下载了更新版的 Tapestry,文件夹名称可能会有变化。

请把这个文件夹移动到自己喜欢的保存 Java 项目安装的地方;例如 /usr/local/java 这样的位置就很好,或者也可以考虑 C:/java。在每种情况下,都要新建一个 Tapestry 目录。在这个目录中,有许多信息;请参考图 1 了解顶级目录结构:


图 1. Tapestry 目录结构
Tapestry 拥有预先构建好的 JAR 文件和许多源代码

Tapestry 已经就位!

术语安装 对于 Tapestry 来说,有点儿名不符实!Tapestry 与在后台运行的 Java 应用程序或者在网络上提供内容的 servlet 引擎不同。相反,它是一组可以在应用程序中使用的实用工具 —— 从类到标记。所以不是 “安装” Tapestry,而是让 Web 应用程序可以使用它,并把 Tapestry 和它的依赖项与 Web 应用程序绑在一起。

变化、变化、变化

JAR 文件的名称可能略有不同,这与下载的 Tapestry 版本对应。但是,不管 Tapestry 的主版本号是多少,所有 Tapestry 安装中都应当有四个 JAR 文件。

设置 Tapestry 主要是把正确的类组(打包到 Java JAR 文件中)放在正确的目录中。首先,需要找到核心的 Tapestry JAR 文件。这些文件包含 Web 应用程序中要使用的全部 Tapestry 代码,它们位于 Tapestry 发行目录的根目录中:

  • tapestry-4.0-beta-12.jar 包含 Tapestry 的框架部分。
  • tapestry-annotations-4.0-beta-12.jar 包含在 Java 5.0 上支持 Tapestry 标注所需要的类。
  • tapestry-contrib-4.0-beta-12.jar 包含那些发布到 Tapestry 中,但是对操作来说并非必需的组件。
  • tapestry-portlet-4.0-beta-12.jar 包含的类让 Tapestry 可以支持 JSR-168 —— portlet JSR。

请把这四个 JAR 文件放在使用 Tapestry 的 Web 应用程序的 WEB-INF/lib 目录中,就准备好了 —— 差不多是这样。在启动应用程序之前,只需要再做一点儿 设置。





回页首


设置依赖项

Tapestry 试图为 Web 应用程序提供一种简化的编程模型和易于使用的环境。它的做法是抽象出重复的、常见的任务的细节,所以它使用许多 实用工具。不论是支持表达式,还是登记对象,还是创建漂亮的用户界面组件,Tapestry 都依赖第三方库让 Web 开发对于开发人员尽可能容易。

幸运的是,Tapestry 会自己进行大多数依赖项的下载和安装工作。唯一的麻烦就是跟踪这些额外的 JAR 文件,但是正如在这一节中会看到的,这不是个大问题。

因为 Tapestry 用 HiveMind 来确定和下载依赖项,所以第一步是让 Tapestry 知道 HiveMind 文件在哪儿。请转到 Tapestry 目录 —— 可能是 /usr/local/java/tapestry-4.0-beta-12 —— 然后进入 config 目录。应当看到一个 build.properties 文件。打开它的时候,会看到类似清单 1 的内容。在这里列出了 Tapestry 的几个核心库和依赖项,以及它们的位置。这个列表并不反映 Tapestry 需要的全部依赖项,但是反映了编译基本的 Tapestry 应用程序需要的依赖项集合。


清单 1. 默认的 Tapestry build.properties 文件
																								
																										# Copyright 2005 The Apache Software Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

jboss.dir = c:/Work/jboss-3.0.6

jython.dir = c:/Work/Jython21

jetty.dir = c:/Work/Jetty-4.2.22

tomcat.dir = C:/Program Files/Apache Group/Tomcat 4.1

hivebuild.dir=c:/workspace/jakarta-hivemind/hivebuild

clover.dir=c:/Work/clover-1.0

jboss.dir=C:/jboss-4.0.2

																								
																						

如果这些项目和分配给它们的位置看起来比较怪,那就对了。实际上应当清除掉属性文件中的全部内容,除了以 hivebuild.dirtomcat.dir 开始的之外,因为这些依赖项应当已经就绪。请修改这些依赖项的位置,使其与 HiveMind 和 Tomcat 的安装位置匹配,build.properties 的最终版本看起来应当像清单 2 这样:


清单 2. 修改后的 build.properties 文件
																								
																										# Copyright 2005 The Apache Software Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

tomcat.dir=/usr/local/java/jakarta-tomcat-5.5.9

hivebuild.dir=c:/workspace/jakarta-hivemind/hivebuild

																								
																						

保存这个文件,并返回 Tapestry 的根目录,设置 Ant。

设置 Ant

Ant 的安装非常简单,只是需要些时间。首先在 Tapestry 的根目录下输入 ant install。可以忽略所有文本,直到看到 像这样的文本 为止。

不必担心奇怪的格式;请输入 “continue” 并按下回车,就开始下载 Tapestry 的额外依赖项。这一步骤要花些时间,所以如果构建过程看起来已经执行了一段时间,请不要担心:要下载多个库,还要编译许多代码。甚至还会看到安装了一些测试,然后可能得到 以下(明显的)错误

对于高级开发人员

如果想在 Tapestry 开发中使用额外项目,只需修改 build.properties,反映这些项目的位置即可。然后,只需删除没有 的项目对应的行,并让 Tapestry 下载实际的依赖项。

不要被错误消息吓倒:一定要阅读各种 echo 语句的输出;它们表明 JUnit 已经下载并安装,只需要重新启动构建过程。

构建和更多构建

这是对的,现在还没有完。在提示符下,再次输入 ant install,继续构建。安装了 JUnit 之后,构建会运行许多 JUnit 测试,以确保 Tapestry 的设置正确。因为这个构建过程要花许多时间,所以可以去喝杯咖啡,或者到另一台机器上编写一些 XHTML。如果事情进展顺利,构建过程的末尾 看起来会像这样

这些库都是什么?

构建完成之后,请进入 ext-package/lib 目录,列出目录列表。应当看到像这样的列表:

																								
																										[bmclaugh:/usr/local/java/tapestry-4.0-beta-12/ext-package/lib]$ ls
cglib-full-2.0.1.jar                    jdom-1.0.jar
cglib-full-2.0.2.jar                    junit-3.8.1.jar
commons-codec-1.3.jar                   log4j-1.2.8.jar
commons-fileupload-1.0.jar              ognl-2.6.7.jar
commons-logging-1.0.4.jar               oro-2.0.8.jar
easymock-1.1.jar                        portlet-api-1.0.jar
easymockclassextension-1.1.jar          servletapi-2.3.jar
hivemind-1.1-rc-1.jar                   spring-1.1.jar
hivemind-lib-1.1-rc-1.jar               tapestry-4.0-beta-12.jar
javassist-3.0.jar                       tapestry-annotations-4.0-beta-12.jar
jboss-j2ee-3.2.1.jar                    tapestry-contrib-4.0-beta-12.jar
jboss-j2ee-4.0.0DR4.jar                 tapestry-portlet-4.0-beta-12.jar
jcharts-0.6.0.jar                       vlibbeans-4.0-beta-12.jar

																								
																						

可以看到,构建过程下载了许多 额外的库。其中有许多是在构建 Tapestry 时使用的,其他一些是在编译时使用的。但是,对于运行时,需要把下面这些 Tapestry JAR 文件放在 Web 应用程序的 WEB-INF/lib 目录中:

  • tapestry-4.0-beta-12.jar
  • tapestry-annotations-4.0-beta-12.jar
  • tapestry-contrib-4.0-beta-12.jar
  • tapestry-portlet-4.0-beta-12.jar

另外,还需要把以下依赖项文件放在 Web 应用程序的 WEB-INF/lib 目录中:

  • commons-codec-1.3.jar:URL 编码和解码工具的文件。
  • commons-fileupload-1.0.jar:处理多部分文件上载的库。
  • commons-logging-1.0.4.jar:Tapestry 用来记录信息和错误的框架。
  • hivemind-1.1-rc-1.jar:Tapestry Java 对象的服务注册表。
  • hivemind-lib-1.1-rc-1.jar:HiveMind 库。
  • javassist-3.0.jar:运行时代码增强库。
  • ognl-2.6.7.jar:导航对象图的表达式语言。
  • oro-2.0.8.jar:模式匹配的表达式库。

看起来可能有许多文件,但是对于 Java 库来说,还是很常见的;只要把它们放在正确的位置,就不用再照顾它们了。关于安装的最好的消息就是:现在已经完成了!前提软件安装完成、Tapestry 和全部依赖项设置好之后,就可以看看一些 Tapestry Web 应用程序是什么样了。





回页首


Tapestry 的示例应用程序

比起从头开始构建 Tapestry 应用程序,先看看与 Tapestry 捆绑在一起的示例应用程序是个好主意。这样,在开发自己的 Tapestry 应用程序之前,对于 Tapestry 应用程序能做什么,就会有良好的认识。

要做的第一件事是进入 Tapestry 目录,然后进入 Examples 目录,每个示例都有自己的一组文件和子目录:

  • Workbench (在 Workbench/ 中)是多个 Tapestry 用户界面组件的示例,例如表格、标签、调色板以及验证。
  • 虚拟库 (在 Vlib/ 中),是一个完整的 J2EE 应用程序,使用实体 bean 和 CMP(容器管理的持久性)、会话 bean,当然还有 servlet。
  • VlibBeans (在 VlibBeans/ 中)是虚拟库应用程序使用的 bean。

可以用 Ant 编译这些文件,但是其中涉及一个数据库的设置,然后把 Tapestry 设置成与这个数据库通信。这项工作的量很大,所以可以使用一个不错的快捷方法。请导航到 Howard Lewis Ship 的主页 (顺便说一下,这是 Tapestry 的创建者),在上面会看到有几个文件可以下载。请选择 tapestry-examples-4.0-beta-12.tar.gz(具体的版本可能会有不同),开始下载,然后坐下来休息一会儿,因为需要花上一些时间。

Workbench 应用程序

快速入门档案是一套预先配置好的示例,包括需要的全部依赖项,包括做好运行准备的 JBoss 应用程序服务器。要运行 Workbench 应用程序,请展开档案,并在生成的文件夹中找到可以运行示例的目录;例如 C:/java/tapestry/jboss-tapestry-examples-4.0-beta-12/。然后,进入这个新目录,再进入 bin 子目录。如果在 Windows 系统上,请输入命令 run;在 Linux、Unix 或 Mac OS X 上,请输入 sh run.sh(或者用来运行 shell 脚本的其他变体)。在控制台上会出现许多内容,结尾的内容 与这个类似

两个 Tomcat?

要运行 Tapestry 应用程序,Tomcat(或类似的 servlet 引擎)是必需的,所以在第 2 部分中从头开始构建应用程序时,如果机器上已经安装了 Tomcat 会很方便。但是,如果现在机器上已经安装了 Tomcat,那么在运行预先配置的示例之前,需要先关闭 Tomcat,因为快速入门档案中包含一个 Tomcat 安装。如果在启动示例时,原有的 Tomcat 正在运行,那么会出现端口冲突,因为两个 Tomcat 都会试图使用端口 8080。

这个脚本在 http://localhost:8080 上启动 JBoss 应用服务器预先配置好的示例(JBoss 使用 Tomcat 作为 servlet 引擎)。打开 Workbench 应用程序后,应当看到类似图 2 所示的内容:


图 2. Workbench 应用程序
Workbench 应用程序演示了 Tapestry 的用户界面组件

继续使用这个示例应用程序。Workbench 可以让您看到 Tapestry 如何处理国际化(I18N)、字段、图片(如图 3 所示)、日期、颜色调色板、文件上载等。使用 Tapestry,只需要一两行代码就可以构建这些组件。


图 3. Tapestry 的图表组件,jCharts
Tapestry 使用像 jCharts 这样的辅助库提供精彩的显示

虚拟库应用程序

接下来看虚拟库应用程序。如果已经停止了 JBoss,请再启动它,然后导航到虚拟库应用程序。这个应用程序的前端不像 Workbench 那么漂亮,但是通过它可以体会到 Tapestry 如何用 EJB 容器(在这个示例中,显然是 JBoss)访问数据库。

如果想对这个应用程序有所体验,请从图书查询开始:输入标题、作者或者选择出版者(请参见图 4 的示例):


图 4. Tapestry 虚拟库中的图书查询
虚拟库演示了 Tapestry 的数据库交互

请单击左侧的 Login 链接并输入用户名 “ringbearer@bagend.shire” 和口令 “secret”。虽然这个用户没有书,但是他被分配了管理员权限,所以在屏幕的左侧可以看到出现几个新选项(请参见图 5)。请使用这些选项,体验 Tapestry 的运行方式。当您认识到 Tapestry 是一个简单的、模板驱动的框架,要让应用程序运行只需要很少的硬编码编程,那么就会开始认识到这些示例应用程序的复杂性。


图 5. 管理员用户的额外选项
Tapestry 处理权限和角色很容易




回页首


方便的示例

除了让您自己查看 Tapestry 应用程序的样子以及它们的运行方式之外,快速入门档案还包括更多的方便示例,可以让您体会 Tapestry 应用程序的结构。请导航回档案 并找到名为 tapestry-tutorials.tar.gz 的文件。像预先构建的 Tapestry 示例一样,教程示例基本上也做好了运行的准备。

这是对的 —— Hello World!

理解这些应用程序非常简单。先进入档案展开所创建的新目录,然后进入 helloworld/ 子目录。虽然可以签出源代码树,但是源代码的组织主要是个人偏好的问题。所以不必在这上面花费时间,请把示例构建为一个 WAR 文件:输入 ant 并让 Ant 替您构建 Hello World 示例应用程序。输出并不太令人振奋:

																								
																										Buildfile: build.xml

compile:
 [mkdir] Created dir: /usr/local/java/tapestry-tutorials/helloworld/target/classes

war:
[war] Building war: /usr/local/java/tapestry-tutorials/helloworld/target/helloworld.war

BUILD SUCCESSFUL
Total time: 2 seconds

																								
																						

Ant 创建了一个 WAR 文件(在 target/ 目录中),可以把它放进 Tomcat 的 webapps/ 目录(通常是 /usr/local/jakarta-tomcat-5.5.9/webapps 或类似的地方)。这样 Tomcat 就会自动展开 WAR 文件并把它部署成 Web 应用程序。对于其他教程 Direct Link 和 Forms 来说,也同样操作,在 tapestry-tutorials 基目录下可以找到这些教程自己的目录。图 6 显示了 Direct Link 应用程序在 Tomcat 上运行的情况:


图 6. 在 Tomcat 上运行的 Direct Link
Direct Link 是 Tapestry 的一个简单而良好的实际演示

应用程序目录结构

可以用 Direct Link 教程熟悉 Tapestry 应用程序的目录结构。首先,找到预先配置好的快速入门 Tomcat 安装并进入 webapps/ 目录。可以看到刚才移进去的 WAR 文件 —— 例如 directlink.war —— 还有一个同名目录。同名目录中是展开的 WAR 文件,所以请进入这个目录到处看看。可以看到一些文件:

  • META-INF 是 WAR 文件清单的目录。
  • Home.html 是 HTML 模板,Tapestry 用它显示主页。
  • WEB-INF 是全部应用程序资源、web.xml 部署描述符和属性文件所在的目录。

现在进一步详细研究 Tapestry 的 HTML 模板和部署描述符。

HTML 模板

首先,打开 Home.html 并签出 Tapestry 极为简单的页面设计模板;这个文件如 清单 3 所示。

在第 2 部分中,将学习许多关于页面创建的内容,但是现在要注意的主要事情是:对于大多数情况来说,Tapestry 中的 Web 页面都是用普通 HTML 加少数额外属性构建的。如果不愿意,可以不编写 JSP 代码,而且也不必把 HTML 放在众多的 servlet out.println() 语句中。相反,只要创建丰富的 HTML(或 XHTML)和 CSS 页面,并添加少数 Tapestry 特有的属性,那么就拥有了一个非常动态的应用程序。

这么做的结果就是,可以方便地让专家用 HTML、CSS 对整个站点进行布局,并用伪链接进行设计。到了部署应用程序的时候,只需用 Tapestry 的元素替换链接和组件即可。例如,请注意在清单 3 中,计数器的初始值被设置为默认值 “37”,包围在 span 标记内。设计师可以方便地用 spandiv 标记把动态值或动态区圈出来。然后,Tapestry 设计师可以插入像 jwcidlistener 这样的属性,得到拥有出色外观的页面,但是却不需要让页面设计师学习 Java 或 Servlet API。

部署描述符

清单 4 显示了 Tapestry 应用程序典型的部署描述符。像在其他 Web 应用程序中一样,部署描述符只是把 servlet 映射到名称。具体地说,Tapestry ApplicationServlet 映射到 app URI。这是 Tapestry 中的标准实践,所以可能想把这个描述符放到您的所有 Tapestry 应用程序中。(关于这一点,在第 2 部分中会学到更多。)


清单 4. 示例 Tapestry 描述符
																								
																										<?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>Tutorial: DirectLink</display-name>
  <servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/app</url-pattern>
  </servlet-mapping>
</Web-app>

																								
																						





回页首


代码展示!

到了现在,您可能非常想看看让 Tapestry 应用程序运行的实际代码。让人大吃一惊的是:几乎没什么代码要看!虽然很可能需要编写定制代码(而且这种情况也很常见),但是对于许多任务来说,只需要使用基本的 Java 类。在 Direct Link 应用程序中(在其中要显示动态值),Tapestry 加上大约 20 行 Java 代码就满足了需求。清单 5 显示了为 Direct Link 示例应用程序驱动计数器的简单 Java 类。


清单 5. Tapestry 非常简单的 Java
																								
																										import org.apache.tapestry.annotations.Persist;
import org.apache.tapestry.html.BasePage;

public abstract class Home extends BasePage
{
    @Persist
    public abstract int getCounter();
    public abstract void setCounter(int counter);

    public void doClick(int increment)
    {
        int counter = getCounter();

        counter += increment;

        setCounter(counter);
    }

    public void doClear()
    {
        setCounter(0);
    }
}

																								
																						

有一个方法递增计数器,还有一个方法清除计数器,这就够了!没有特定于 HTML 的逻辑,没有奇怪的 Tapestry 交互(只有 @Persist 标注,在下一篇文章中将介绍它),以及两个获取和设置计数器的抽象方法。有了这个类负责计数器之后,只需要让 Tapestry 知道它要使用哪个标识符,那么像 Home.html 这样的页面组件就可以访问标识符了。可以通过 app.application 文件做这件事,这个文件保存在应用程序的 WEB-INF/ 目录中,如下所示:

																								
																										<?xml version="1.0"?>

<!DOCTYPE application PUBLIC
  "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">

<application>
  <meta key="org.apache.tapestry.page-class-packages" 
  value="tutorials.directlink.pages"/>

</application>

																								
																						

请注意 app.application 文件实际上没有把 Home.class 文件直接映射到名称;相反,它只是告诉应用程序应当到哪儿去寻找 Java 类。所以,在 HTML 页面中的任何 Java 引用都会让 Tapestry 查看 tutorials.directlink.pages 包。不需要直接映射:包中的任何类,对页面来说都可用。在这个示例中,页面的名称(Home.html)自动与同名类映射(Home.class)。足够简单,对吗?剩下的要理解的就是,在 Tapestry 和定制的 Home.class 中,HTML 实际上如何引用特定动作和方法。





回页首


Tapestry 的组件集

这个谜的最后一部分就是 清单 3 中的 Home.html 中使用的 Tapestry 组件集。在第 2 部分中将学到关于 Tapestry 组件集的更多内容,但是在这里我会做一个简短介绍。请注意,每个组件都由 @ 符号开始,并用 jwcid 属性进行标注(请回头看清单 3 以恢复记忆)。这些组件与 Tapestry 和定制类进行交互:

  • Insert 做的事正如其名:把值插入页面。这个示例中的值是 ognl:counter,是从 Java 类中取出的。

  • PageLink 创建了到其他页面的链接;在这个示例中,链接到同一页面,所以实际效果是造成刷新。

  • DirectLink 请求绑定到这个具体页面的 Java 组件(还记得吗,页面的名称与 Java 类的名称对应)。然后,listener 属性允许指定要调用的方法(doClear()doClick())。

显然,关于 Tapestry 组件,还有许多东西需要知道。在 @DirectLink 之前的那些短文本,像 “by5” 和 “by10”,是什么意思?为什么必须在 listener 属性中的方法名称之前加上 “listener” 前缀(看起来多余,是不是?)。除了 PageLinkInsert 之外,还有哪些组件构建到了 Tapestry 中?这些都是好问题,请等到下一篇文章就会知道答案。(是的,这就是 所谓的 “好戏在后头”。)





回页首


Tapestry:值得使用

正如我在这篇文章开始时说的,而且也像您自己看到的,Tapestry 的口号是简单。虽然 Tapestry 只是 Web 开发的众多选择之一,但是在有些方面它很不寻常:它可以让您几乎只用 HTML、一些模板标记和一些 XML(多数情况下只是可选的),就能创建基于 servlet 的 Web 应用程序。而且,与其他简单的 Web 应用程序解决方案不同,Tapestry 会随着项目的成长而伸缩 —— 即使成百上千个页面也可以。

在这篇文章的第 2 部分中,我将深入研究 Tapestry 的 Web 应用程序开发。我将介绍如何创建 Web 页面并添加动态数据的占位符。还将学习如何创建 Java 类、把它们登记到 Tapestry 以及把它们打包成 Web 应用程序的组成部分。我还会介绍如何在页面中使用这些 Java 类,以及 Tapestry 提供的其他一些很酷的用户界面组件。在第 2 部分中,您将学会如何把简单的 HTML、模板标记和属性文件添加在一起,形成很棒的 Web 应用程序。





回页首


参考资料

学习

获得产品和技术

讨论




回页首


关于作者

Brett McLaughlin 的照片

Brett McLaughlin 从 Logo 时代就开始使用计算机(还记得那个小三角吗?),并在 Nextel Communications 以及 Lutris Technologies 这样的公司工作。最近几年,他成为 Java 和 XML 阵营最知名的作者和程序员。他的最新力作 Java 5.0 Tiger: A Developer's Notebook,是关于 Java 最新版本的第一本书,而他的经典作品 Java and XML 仍然是在 Java 中使用 XML 技术的绝对经典之一。

18:37:14  |  固定链接 | 引用通告 (0) | 记录它
2005-9-28
Struts国际化处理
摘要:
通过下拉框里各个语言(中文,日本语,English)的选择,切换jsp页面文字。


本文Matrix永久镜像:http://www.matrix.org.cn/resource/article/43/43663_Struts.html
说明:本文可能由Matrix原创,也可能由Matrix的会员整理,或者由
Matrix的Crawler在全球知名Java或者其他技术相关站点抓取并永久
保留镜像,Matrix会保留所有原来的出处URL,并在显著地方作出说明,
如果你发觉出处URL有误,请联系Matrix改正.
1 准备资源文件。

资源文件命名格式:filename_language_country.properties.
中文文件名为index_zh_CN.properties。
日文文件名为 index_ja_JP.properties。
英文文件名为 index_en.properties。

英文文件内容:
index.jsp.welcome=Colimas Library Management System
index.jsp.name=Name
index.jsp.userid=User ID
index.jsp.pass=Password


中文文件内容:
index.jsp.welcome=\u4f60\u597d
index.jsp.name=\u59d3\u540d
index.jsp.userid=\u7528\u6237\u540d
index.jsp.pass=\u5bc6\u7801


日文文件内容:
index.jsp.welcome=\u3044\u3089\u3063\u3057\u3083\u3044\u307e\u305b
index.jsp.name=\u59d3\u540d
index.jsp.userid=\u30e6\u30fc\u30b6\u30fcID
index.jsp.pass=\u30d1\u30b9\u30ef\u30fc\u30c9

\uxxxx是中文被转换后的ASCII码。可以使用native2ascii.exe工具转换。

2 struts-config.xml里配置资源文件
<message-resources parameter="resources.config.index" />

resources.config.index是classes目录下的resources/config子目录的index__xx_xx.properties文件.
struts根据你的浏览器的语言设置调用不同语言的资源文件。
例如:如果你的IE默认语言为中文则。Struts将使用index_zh_CN.properties。而在struts-config.xml里只需写出“index”即可

ActionMapping
<form-beans>
      <!--1 Multi-Lanuage support formbean-->
        <form-bean
            name="SelectLanguageForm"
            type="com.nova.colimas.web.form.SelectLanguageForm"/>
   </form-beans>
<!-- =========================================== Global Forward Definitions -->

    <global-forwards>
        <!-- Default forward to "Welcome" action -->
        <!-- Demonstrates using index.jsp to forward -->
        <forward
            name="index"
            path="/pages/index.jsp"/>   
    </global-forwards>


<!-- =========================================== Action Mapping Definitions -->

    <action-mappings>       
        <!-- 1 select language action -->         
       <action    path="/SelectLanguageAction"
              type="com.nova.colimas.web.action.SelectLanguageAction"
              name="SelectLanguageForm"
              scope="request">
       </action>
      …
    </action-mappings>



3 jsp
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="/tags/struts-bean" prefix="bean"%>
<%@ taglib uri="/tags/struts-html" prefix="html"%>
<%@ taglib uri="/tags/struts-logic" prefix="logic"%>

<html:html>
<Title><bean:message key="index.jsp.welcome"/></Title>
<body>
<logic:present name="user">
   <H3>Welcome <bean:write name="LoginForm" property="userID" />!</H3>
</logic:present>
<logic:notPresent scope="session" name="user">
   <H3>Welcome Colimas!</H3>
</logic:notPresent>
<html:errors />
<html:form action="/SelectLanguageAction.do">
       <html:select property="language">
                <html:option value="0">中文</html:option>
                <html:option value="1">日本語</html:option>
                <html:option value="2">English</html:option>               
       </html:select>
   <html:submit>Select</html:submit>
</html:form>


<html:form action="/LoginAction.do">
   <p><bean:message key="index.jsp.userid"/><input type="text" name="userID" value="tyrone1979" /><br>
   <bean:message key="index.jsp.pass"/><input type="password" name="password" value="197913"/><br>
   <html:submit><bean:message key="index.jsp.login"/></html:submit>
   </p>
</html:form>

</body>
</html:html>


<bean:message key="index.jsp.welcome"/>引用资源文件的index.jsp.welcome属性
SelectLanguageAction.do调用Action实现语言转换。

4 Action
package com.nova.colimas.web.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessages;
//import org.apache.struts.upload.FormFile;
import com.nova.colimas.web.form.SelectLanguageForm;
import org.apache.struts.Globals;
import java.util.Locale;

public class SelectLanguageAction extends Action {
   public ActionForward execute(ActionMapping mapping,
          ActionForm form,
          HttpServletRequest request,
          HttpServletResponse response)
   throws Exception{
      SelectLanguageForm myform=(SelectLanguageForm)form;
      String lan=myform.getLanguage();
      switch((new Integer(lan)).intValue()){
      case 0 :
         request.getSession().setAttribute(Globals.LOCALE_KEY,Locale.CHINA);
         break;
      case 1:
         request.getSession().setAttribute(Globals.LOCALE_KEY, Locale.JAPAN);
         break;
      case 2:
         request.getSession().setAttribute(Globals.LOCALE_KEY, Locale.ENGLISH);
         break;
      default:
         request.getSession().setAttribute(Globals.LOCALE_KEY, Locale.ENGLISH);
         break;
      }
              return mapping.findForward("index");
   }
}
Form
/*
* Created on 2005/06/18
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package com.nova.colimas.web.form;

import org.apache.struts.action.ActionForm;

/**
* @author tyrone
**/
public class SelectLanguageForm extends ActionForm {

   private String language;

   public void Reset() {
      this.language="";
      return;
   }

   /**
    * @return Returns the Language.
    */
   public String getLanguage() {
      return language;
   }
   /**
    * @param language The Language to set.
    */
   public void setLanguage(String property1) {
      this.language = property1;
   }
}

10:33:43  |  固定链接 | 引用通告 (0) | 记录它
2005-9-26
優化數據訪問層性能

选择优化性能的功能

3.1.    使用参数标记作为存储过程的参数

    调用存储过程时,用参数标记做为参数尽量不要用字符做参数。JDBC驱动调用存储过程时要么象执行其他SQL查询一样执行该过程,要么通过RPC直接调用来优化执行过程。如果象SQL查询那样执行存储过程,数据库服务器先解析该语句,验证参数类型,然后把参数转换成正确的数据类型,显然这种调用方式不是最高效的。
    SQL语句总是做为一个字符串送到数据库服务器上,例如,
“{call getCustName (12345)}”。
在这种情况下,即使程序员设想给getCustName唯一的参数是整型,事实上参数传进数据库的仍旧是字符串。数据库服务器解析该语句,分离出单个参数值12345,然后在把过程当作SQL语言执行之前,将字符串“12345”转换成整型值。
    通过RPC在数据库服务器中调用存储过程,就能避免使用SQL字符串带来的开销。
    情形1
    在这个例子中,就不能使用服务器端的RPC优化调用存储过程。调用的过程包括解析语句,验证参数类型,在执行过程之前把这些参数转换成正确的类型。
CallableStatement cstmt = conn.prepareCall (   "{call getCustName (12345)}"); ResultSet rs = cstmt.executeQuery ();  
    情形2
    在这个例子中,可以使用服务器端RPC优化调用存储过程。由于应用避免了文字参数传递带来的开销,且JDBC能以RPC方式直接在数据库中调用存储过程来优化执行,所以,执行时间也大大地缩短了。
CallableStatement cstmt =
     conn.prepareCall (   "{call getCustName (?)}");cstmt.setLong (1,12345);
ResultSet rs = cstmt.executeQuery();
    JDBC根据不同的用途来优化性能,所以我们需要根据用途在PreparedStatement对象和Statement对象之间做出选择。如果执行一个单独的SQL语句,就选择Statement对象;如果是执行两次或两次以上就选择PreparedStatement对象。
    有时,为了提高性能我们可以使用语句池。当使用语句池时,如果查询被执行一次且可能再也不会被执行,那就使用Statement对象。如果查询很少被执行,但在语句池的生命期内可能再一次被执行,那么使用PreparedSatement。在相同的情形下,如果没有语句池,就使用Statement对象。

3.2.    用批处理而不是用PreparedStatement语句


    更新大量的数据通常是准备一个INSERT语句并多次执行该语句,结果产生大量的网络往返。为了减少JDBC调用次数和提高性能,你可以使用PreparedStatement对象的addBatch()方法一次将多个查询送到数据库里。例如,让我们比较一下下边的例子,情形1和情形2。
    情形1:多次执行PreparedStatement语句
PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)");
for (n = 0; n < 100; n++) {  
 ps.setString(name[n]); 
 ps.setLong(id[n]);  
 ps.setInt(salary[n]); 
 ps.executeUpdate();
}
    情形2:使用批处理
PreparedStatement ps = 
conn.prepareStatement(   "INSERT into employees values (?, ?, ?)");
for (n = 0; n < 100; n++) {  
ps.setString(name[n]);  
ps.setLong(id[n]);  
ps.setInt(salary[n]);  
ps.addBatch();
}
ps.executeBatch();
    在情形1中,一个PreparedStatement用于多次执行一个INSERT语句。在这个情况下,为了100次插入需要101次网络往返,其中一次用于准备语句,额外100次网络往返用于执行每一个操作。当addBatch()方法的时候,如情形2所述,仅需要两个网络往返,一个准备语句,另一个执行批处理。尽管使用批处理需要更多的数据库CPU运算开销,但性能可由减少的网络往返获得。记住要让JDBC驱动在性能方面有良好的表现,就要减少JDBC驱动和数据库服务器之间的网络通讯量。

3.3.    选择合适的游标


    选择合适的游标能提高应用的灵活性。本节总结了三类游标的性能问题。向前游标对连续读表中所有的行提供了优秀的性能。就检索表数据而言,没有一个检索数据的方法要比向前游标更快。然而,当应用必须处理非连续方式的行时,就不能使用它。
    对需要数据库高层次的并发控制和需要结果集向前和向后滚动能力的应用而言,JDBC驱动使用的无感知游标是最为理想选择。对无感知游标的第一次请求是获取所有的行(或者当JDBC使用“懒惰”方式读时,可以读取部分行)并将它们存储在客户端。那么,第一次请求将会非常慢,特别是当长数据被检索到的时候。后续的请求不再需要网络交通(或当驱动采用懒惰方式时,只有有限的网络交通)并处理得很快。由于第一次请求处理缓慢,无感知游标不应该用于一行数据的单个请求。当要返回长数据时,内存很容易被耗尽,所以开发人员也应该避免使用无感知游标。一些无感知游标的实现是把数据缓存在数据库中的零时表中,避免了性能问题,但是,大多数是把信息缓存在应用本地。
    无感知游标,有时又叫键集驱动的游标,使用标识符,如已经存在于你数据库中的ROWID。当你通过结果集滚动的时候,适合于标识符的数据会被检索到。由于每个请求都产生网络交通量,所以性能将会非常差。然而,返回非连续行不会更多的影响性能。
    为了更进一步说明,我们来看一个通常返回应用1000行数据的应用。在执行时或第一行被请求时,JDBC不会执行由应用提供的SELECT语句。而是JDBC驱动用键标识符替换查询的SELECT列表,例如,ROWID。这个修改的查询将会被驱动执行,并且所有1000键值将会被从数据库中检索出来并被驱动缓存。每一个来自应用对结果行的请求将转到JDBC驱动,为了返回合适的行,JDBC在它本地缓存中查询键值,构造一个类似于“WHERE ROWID=?”包含WHERE的优化的语句,执行这个修改了查询,然后从服务器上检索单个结果行。
    当应用使用来自缓存中的无感知(Insensitive)游标数据时,有感知(Sensitive)游标在动态情形下就是首选的游标模式。

 

3.4.    有效地使用get方法


    JDBC提供了很多从结果集中检索数据的方法,例如getInt(),getString(),以及getObject()。getObject()方法是最普通的方法,但在没有说明非默认映射时提供了最差的性能。这是因为为了确定被检索值的类型和产生合适的映射,JDBC驱动必须做额外的处理。所以,总是使用能明确数据类型的方法。
    为了更好地提高性能,请提供被检索列的列数字,例如,getString(1),getLong(2),和getInt(3),而不是列名。如果列数字没有说明,网络流量是不受影响的,但转换和查找的成本上升了。例如,假设你使用getString(“foo”)…驱动可能不得不将列的标识符foo转换成大写(如果必要),并在列列表中用“foo”和所有的列名比较。如果提供了列数字,很大部分的处理都被节省了。
    例如,假如你有一个15列100行的结果集,列名没有包括在结果集中。你感兴趣的有三列,EMPLOEEMENT(字符串),EMPLOYEENUMBER(长整型),和SALARY(整型)。如果你说明了getString(“EmployeeName”),getLong(“EmployeeNumber”)和getInt(“Salary”),每列的列名必须转换成和数据库元数据中匹配的大小写,毫无疑问查询将相应的增加。如果你说明getString(1),getLong(2),和getInt(15),性能将会大大地提高。

3.5.    检索自动产生的键


    许多数据库已经隐藏了描述表中每行唯一键的列(又叫伪列)。通常,由于伪列描述了数据的物理磁盘地址,故而在查询中使用这种类型的列存取行是最快的方式。在JDBC3.0以前,应用仅能在插入数据之后立即执行SELECT语句检索到伪列的值。
For example:
//insert rowint 
rowcount = stmt.executeUpdate (   "insert into LocalGeniusList (name) values ('Karen')"); 
// now get the disk address - rowid - for the newly inserted row
ResultSet rs = stmt.executeQuery (   "select rowid from LocalGeniusList where name = 'Karen'");
    这个检索伪列的方法有两个主要的缺点。第一,检索伪列需要通过网络把一个单独的查询语句发送到服务器上执行。第二,由于表中可能没有主键,查询条件可能不能唯一地确定行。在后边的情形中,多个伪列值被返回,应用或许不能确定哪个是最近插入的行。
JDBC规范一个可选的特性是当行插入表时,能检索到行的自动产生的键信息。
For example:
int rowcount = stmt.executeUpdate (   "insert into LocalGeniusList (name) values ('Karen')",
// insert row AND return 
keyStatement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys (); 
// key is automatically available
    即便该表没主键,这都给应用提供了一个唯一确定行值的最快方法。当存取数据时,检索伪列键的能力给JDBC开发人员提供了灵活性并创造了性能。

4.    管理连接和数据更新


 

4.1.    管理连接


    连接管理的好坏直接影响到应用的性能。采用一次连接创建多个Statement对象的方式来优化你的应用,而不是执行多次连接。在建立最初的连接之后要避免连接数据源。
    一个不好的编码习惯是执行SQL语时连接和断开好几次。一个连接对象可以有多个Statement对象和它关联。由于Statement对象是定义SQL语句信息的内存存储,它能管理多个SQL语句。此外,你可以使用连接池来显著地提高性能,特别是对那些通过网络连接或通过WWW连接的应用。连接池让你重用连接,关闭连接不是关闭与数据库的物理连接,而是将用完的连接放到连接池中。当一个应用请求一个连接时,一个活动的连接将从连接池中取出重用,这样就避免了创建新连接的而产生的网络I/O。

4.2.    在事务中管理提交


    由于磁盘I/O和潜在的网络I/O,提交事务往往要慢。经常使用WSConnection.setAutoCommit(false)来关闭自动提交设置。
    提交实际上包括了什么呢?数据库服务器必须刷新包含更新的和新数据的磁盘上的每一个数据页。这通常是一个对日志文件连续写的过程,但也是磁盘I/O。默认情况下,当连接到数据源时,自动提交是打开的,由于提交每个操作需要大量的磁盘I/O,自动提交模式通常削弱了性能。此外,大部分数据库没有提供本地的自动提交模式。对这种类型的服务器,JDBC驱动对每一个操作必须明确地给服务器送出COMMIT语句和一个BEGIN TRANSACTION。
    尽管使用事务对应用的性能有帮助,但不要过度地使用。由于为了防止其他用户存取该行而在行上长时间的持有锁将减少吞吐量。短时间内提交事务可以最大化并发量。

4.3.    选择正确的事务模式


    许多系统支持分布式事务;也就是说,事务能跨越多个连接。由于记录日志和所有包含在分布式事务中组件(JDBC驱动,事务监视器和数据库系统)之间的网络I/O,分布式事务要比普通的事务慢四倍。除非需要分布式事务,否则尽量避免使用它们。如果可能就使用本地事务。应该注意的是许多Java应用服务器提供了一个默认的利用分布式事务的事务行为。为了最好的系统性能,把应用设计在运行在单个连接对象之下,除非必要避免分布式事务。

4.4.    使用updateXXX方法


    尽管编程的更新不适用于所有类型的应用,但开发人员应该试着使用编程的更新和删除,也就是说,使用ResultSet对象的updateXXX()方法更新数据。这个方法能让开发人员不需要构建复杂的SQL语句就能更新数据。为了更新数据库,在结果集中的行上移动游标之前,必须调用updateRow()方法。
    在下边的代码片断中,结果集对象rs的Age列的值使用getInt()方法检索出来,updateInt()方法用于用整型值25更新那一列。UpdateRow()方法用于在数据库中更新修改了值的行。 
int n = rs.getInt("Age"); 
// n contains value of Age column in the resultset rs...
rs.updateInt("Age", 25); 
rs.updateRow();
    除了使应用更容易地维护,编程更新通常产生较好的性能。由于指针已经定位在被更新的行上,定位行的所带来的开销就避免了。

4.5.    使用getBestRowIdentifier()


    使用getBestRowIdentifier()(请参阅DatabaseMetaData接口说明)确定用在更新语句的Where子句中的最优的列集合。伪列常常提供了对数据的最快的存取,而这些列仅能通过使用getBestRowIdentifier()方法来确定。
    一些应用不能被设计成利用位置的更新和删除。一些应用或许通过使用可查询的结果列,如调用getPrimaryKeys()或者调用getIndexInfo()找出可能是唯一索引部分的列,使Where子句简洁化。这些方法通常可以工作,但可能产生相当复杂的查询。看看下边的例子:
ResultSet WSrs = WSs.executeQuery    ("SELECT first_name, last_name, ssn, address, city, state, zip    FROM emp");
// fetch data...
WSs.executeUpdate ("UPDATE EMP SET ADDRESS = ?   WHERE first_name = ? and last_name = ? and ssn = ?    and address = ? and city = ? and state = ?    and zip = ?");
// fairly complex query
    应用应该调用getBestRowIdentifier()检索最优集合的能确定明确的记录的列(可能是伪列)。许多数据库支持特殊的列,它们没有在表中被用户明确地定义,但在每一个表中是“隐藏”的列(例如,ROWID和TID)。由于它们是指向确切记录位置的指针,这些伪列通常给数据提供了最快的存取。由于伪列不是表定义的部分,它们不会从getColumns中返回。为了确定伪列是否存在,调用getBestRowIndentifier()方法。
再看一下前边的例子:
...
ResultSet WSrowid = getBestRowIdentifier(... "emp", ...);
...
WSs.executeUpdate ("UPDATE EMP SET ADDRESS = ?  WHERE ROWID = ?";
// fastest access to the data!
    如果你的数据源没有包含特殊的伪列,那么getBestRowIdentifier()的结果集由指定表上的唯一索引组成(如果唯一索引存在)。因此,你不需要调用getIndexInfo来找出最小的唯一索引。


 

9:54:26  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
日期時間計算
学习怎样创建和使用日期

概要
    不管你是处理财务交易还是计划着下一步的行动,你都要知道怎样在Java中建立,使用和显示日期。这需要你简单的查阅一下相应类的API参考:一个日期可以创建3个相关类的对象。这篇文章告诉你你想要知道的内容。(3,000字)
作者:Robert Nielsen
翻译:Cocia Lin

    Java统计从1970年1月1日起的毫秒的数量表示日期。也就是说,例如,1970年1月2日,是在1月1日后的86,400,000毫秒。同样的,1969年12月31日是在1970年1月1日前86,400,000毫秒。Java的Date类使用long类型纪录这些毫秒值.因为long是有符号整数,所以日期可以在1970年1月1日之前,也可以在这之后。Long类型表示的最大正值和最大负值可以轻松的表示290,000,000年的时间,这适合大多数人的时间要求。
Date 类
  
Date类可以在java.util包中找到,用一个long类型的值表示一个指定的时刻。它的一个有用的构造函数是Date(),它创建一个表示创建时刻的对象。getTime()方法返回Date对象的long值。在下面的程序中,我使用Date()构造函数创建一个表示程序运行时刻的对象,并且利用getTime()方法找到这个日期代表的毫秒数量:

import java.util.*;


public class Now {
   public static void main(String[] args) {
      Date now = new Date();
      long nowLong = now.getTime();
      System.out.println("Value is " + nowLong);
   }
}

当我运行这个程序后,我得到972,568,255,150.快速确认一下这个数字,起码在一个合理的范围:它不到31年,这个数值相对1970年1月1日到我写这篇文章的时间来说,是合理的。计算机是这个毫秒值表示时间,人们可不愿意说" 我将在996,321,998,34见到你。"幸运的是,Java提供了一个转换Date对象到字符串的途径,表示成传统的形式。我们在下一节讨论DateFormat类,它直观的建立日期字符串。
DateFormat类
 DateFormat类的一个目标是建立一个人们能够识别的字符串。然而,因为语言的差别,不是所有的人希望看到严格的相同格式的日期。法国人更喜欢看到"25 decembre 2000,",但是美国人习惯看到"December 25,2000."所以一个DateFormat的实例创建以后,这个对象包含了日期的显示格式的信息。如果使用用户电脑区域设置缺省的格式,你可以象下面那样,创建DateFormat对象,使用getDateInstance()方法:

DateFormat df = DateFormat.getDateInstance();  

DateFormat类在java.text包中可以找到。
转换成字符串
你可以使用format()方法转换Date对象为一个字符串。下面的示例程序说明了这个问题:

import java.util.*;
import java.text.*;

public class NowString {
   public static void main(String[] args) {
      Date now = new Date();
      DateFormat df = DateFormat.getDateInstance();
      String s = df.format(now);
      System.out.println("Today is " + s);
   }


在上面的代码中,展示了没有参数,使用缺省格式的getDateInstance()方法。Java还提供了几个选择日期格式,你可以通过使用重载的getDateInstance(int style)获得。出于方便的原因,DateFormat提供了几种预置的常量,你可以使用这些常量参数。下面是几个SHORT, MEDIUM, LONG, 和FULL类型的示例:

import java.util.*;
import java.text.*;

public class StyleDemo {
   public static void main(String[] args) {
      Date now = new Date();

      DateFormat df =  DateFormat.getDateInstance();
      DateFormat df1 = DateFormat.getDateInstance(DateFormat.SHORT);
      DateFormat df2 = DateFormat.getDateInstance(DateFormat.MEDIUM);
      DateFormat df3 = DateFormat.getDateInstance(DateFormat.LONG);
      DateFormat df4 = DateFormat.getDateInstance(DateFormat.FULL);
      String s =  df.format(now);
      String s1 = df1.format(now);
      String s2 = df2.format(now);
      String s3 = df3.format(now);
      String s4 = df4.format(now);

      System.out.println("(Default) Today is " + s);
      System.out.println("(SHORT)   Today is " + s1);
      System.out.println("(MEDIUM)  Today is " + s2);
      System.out.println("(LONG)    Today is " + s3);
      System.out.println("(FULL)    Today is " + s4);
   }
}

程序输出如下:

(Default) Today is Nov 8, 2000
(SHORT)   Today is 11/8/00
(MEDIUM)  Today is Nov 8, 2000
(LONG)    Today is November 8, 2000
(FULL)    Today is Wednesday, November 8, 2000

同样的程序,在我的电脑上使用缺省设置运行后,改变区域设置为瑞典,输出如下:

(Default) Today is 2000-nov-08
(SHORT)   Today is 2000-11-08
(MEDIUM)  Today is 2000-nov-08
(LONG)    Today is den 8 november 2000
(FULL)    Today is den 8 november 2000    
 
从这里,你能看到,瑞典的月份不是大写的(虽然November还是november).还有,LONG和FULL版本在瑞典语中是一样的,但是美国英语却不同。另外,有趣的是,瑞典语单词的星期三,onsdag,没有包含在FULL日期里,英语却包括。
注意你能够使用getDateInstance()方法改变DateFormat实例的语种;但是,在上面的例子中,是通过改变Windows98的控制面板的区域设置做到的。不同的地方的区域设置不同,结果就不同,这样有好处,也有不足,Java程序员应该了解这些。一个好处是Java程序员可以只写一行代码就可以显示日期,而且世界不同地区的电脑运行同样的程序会有不用的日期格式。 但是这也是一个缺点,当程序员希望显示同一种格式的时--这也有可取之处,举例来说,在程序中混合输出文本和日期,如果文本是英文,我们就不希望日期格式是其他的格式,象德文或是西班牙文。如果程序员依靠日期格式编程,日期格式将根据运行程序所在电脑的区域设置不用而不同。
解析字符串
 通过parse()方法,DateFormat能够以一个字符串创立一个Date对象。这个方法能抛出ParseException异常,所以你必须使用适当的异常处理技术。下面的例子程序通过字符串创建Date对象:

import java.util.*;
import java.text.*;

public class ParseExample {
   public static void main(String[] args) {
      String ds = "November 1, 2000";
      DateFormat df = DateFormat.getDateInstance();
      try {
         Date d = df.parse(ds);
      }
      catch(ParseException e) {
         System.out.println("Unable to parse " + ds);
      }
   }
}

在创建一个任意的日期时parse()方法很有用。我将通过另一种方法创建一个任意得日期。同时,你将看到怎样进行基本日期计算,例如计算90天后的另一天。你可以使用GregorianCalendar类来完成这个任务。
GregorianCalendar类
 创建一个代表任意日期的一个途径使用GregorianCalendar类的构造函数,它包含在java.util包中:

GregorianCalendar(int year, int month, int date)

注意月份的表示,一月是0,二月是1,以此类推,是12月是11。因为大多数人习惯于使用单词而不是使用数字来表示月份,这样程序也许更易读,父类Calendar使用常量来表示月份:JANUARY, FEBRUARY,等等。所以,创建Wilbur 和 Orville制造第一架动力飞机的日期(December 17, 1903),你可以使用:

GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17); 
出于清楚的考虑,你应该使用前面的形式。但是,你也应该学习怎样阅读下面的短格式。下面的例子同样表示December 17,1903(记住,在短格式中,11表示December)

GregorianCalendar firstFlight = new GregorianCalendar(1903, 11, 17);  

在上一节中,你学习了转换Date对象到字符串。这里,你可以做同样的事情;但是首先,你需要将GregorianCalendar对象转换到Date。要做到这一点,你可以使用getTime()方法,从它得父类Calendar继承而来。GetTime()方法返回GregorianCalendar相应的Date对象。你能够创建GregorianCalendar对象,转换到Date对象,得到和输出相应的字符串这样一个过程。下面是例子:

import java.util.*;
import java.text.*;

public class Flight {

   public static void main(String[] args) {
      GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);   
      Date d = firstFlight.getTime();
      DateFormat df = DateFormat.getDateInstance();
      String s = df.format(d);
      System.out.println("First flight was " + s);
   }
}

有时候创建一个代表当前时刻的GregorianCalendar类的实例是很有用的。你可以简单的使用没有参数的GregorianCalendar构造函数,象这样:

GregorianCalendar thisday = new GregorianCalendar();

一个输出今天日期的例子程序,使用GregorianCalendar对象:

import java.util.*;
import java.text.*;

class Today {
   public static void main(String[] args) {
      GregorianCalendar thisday = new GregorianCalendar(); 
      Date d = thisday.getTime();
      DateFormat df = DateFormat.getDateInstance();
      String s = df.format(d);
      System.out.println("Today is " + s);
   }
}

注意到,Date()构造函数和GregorianCalendar()构造函数很类似:都创建一个对象,条件简单,代表今天。
日期处理
GregorianCalendar类提供处理日期的方法。一个有用的方法是add().使用add()方法,你能够增加象年,月数,天数到日期对象中。要使用add()方法,你必须提供要增加的字段,要增加的数量。一些有用的字段是DATE, MONTH, YEAR, 和 WEEK_OF_YEAR。下面的程序使用add()方法计算未来80天的一个日期。在Jules的<环球80天>是一个重要的数字,使用这个程序可以计算Phileas Fogg从出发的那一天1872年10月2日后80天的日期:

import java.util.*;
import java.text.*;

public class World {
   public static void main(String[] args) {
      GregorianCalendar worldTour = new GregorianCalendar(1872, Calendar.OCTOBER, 2);
      worldTour.add(GregorianCalendar.DATE, 80);
      Date d = worldTour.getTime();
      DateFormat df = DateFormat.getDateInstance();
      String s = df.format(d);
      System.out.println("80 day trip will end " + s);
   }
}

这个例子是想象的,但在一个日期上增加天数是一个普遍的操作:影碟可以租3天,图书馆可以借书21天,商店经常需要将购买的物品在30天内卖出。下面的程序演示了使用年计算:

import java.util.*;
import java.text.*;

public class Mortgage {
   public static void main(String[] args) {
      GregorianCalendar mortgage = new GregorianCalendar(1997, Calendar.MAY, 18);
      mortgage.add(Calendar.YEAR, 15);
      Date d = mortgage.getTime();
      DateFormat df = DateFormat.getDateInstance();
      String s = df.format(d);
      System.out.println("15 year mortgage amortized on " + s);    }
}

    add()一个重要的副作用是它改变的原来的日期。有时候,拥有原始日期和修改后的日期很重要。不幸的是,你不能简单的创建一个GregorianCalendar对象,设置它和原来的相等(equal)。原因是两个变量指向同一个Date()对象地址。如果Date对象改变,两个变量就指向改变后的日期对象。代替这种做法,应该创建一个新对象。下面的程序示范了这种做法:

import java.util.*;
import java.text.*;

public class ThreeDates {
   public static void main(String[] args) {
      GregorianCalendar gc1 = new GregorianCalendar(2000, Calendar.JANUARY, 1);
      GregorianCalendar gc2 = gc1;
      GregorianCalendar gc3 = new GregorianCalendar(2000, Calendar.JANUARY, 1);
      //Three dates all equal to January 1, 2000

      gc1.add(Calendar.YEAR, 1);
      file://gc1 and gc2 are changed

      DateFormat df = DateFormat.getDateInstance();

      Date d1 = gc1.getTime();
      Date d2 = gc2.getTime();
      Date d3 = gc3.getTime();

      String s1 = df.format(d1);
      String s2 = df.format(d2);
      String s3 = df.format(d3);

      System.out.println("gc1 is " + s1);
      System.out.println("gc2 is " + s2);
      System.out.println("gc3 is " + s3);
   }
}

    程序运行后,gc1和gc2被变成2001年(因为两个对象指向同一个Date,而Date已经被改变了)。对象gc3指向一个单独的Date,它没有被改变。
计算复习日期
在这节,你将看到一个依据现实世界的例子。这个详细的程序计算过去一个具体的日期。例如,你阅读这篇文章,你想要记住一个印象深刻的知识点。如果你没有照片一样的记忆力,你就要定期的复习这些新资料,这将帮助你记住它。关于复习系统,Kurt Hanks 和 Gerreld L. Pulsipher在他们的< Five Secrets to Personal Productivity个人能力的5个秘密>中有讨论,建议看过第一眼后马上回顾一下,然后是1天后,1个星期后,1个月后,3个月后,1年后。我的这篇文章,你要马上回顾一下,从现在算起,再就是明天,然后是1个星期,1个月,3个月,1年后。我们的程序将计算这些日期。
这个程序非常有用的,它将是PIM(Personal Information Manager个人信息管理器)的一个组成部分,并将确定复习时间。在下面的程序中,getDates()方法对一个返回日期数组(复习日期)的电子软件很有用。另外,你可以返回单独的一个日期,使用getFirstDay(),getOneDay(),getOneWeek(),getOnMonth()和getOneYear().当时间范围超出这个PIM的ReviewDates的计算范围时ReviewDates类演示了怎样计算时间段。现在,你可以容易的修改它用来处理你需要的时间段,象图书馆借书,录影带租赁和抵押计算。首先,ReviewDates类显示在下面:

import java.util.*;
import java.text.*;

public class ReviewDates {
   private GregorianCalendar firstDay, oneDay, oneWeek, oneMonth, oneQuarter, oneYear;
   final int dateArraySize = 6;

   ReviewDates(GregorianCalendar gcDate) {
      int year = gcDate.get(GregorianCalendar.YEAR);
      int month = gcDate.get(GregorianCalendar.MONTH);
      int date = gcDate.get(GregorianCalendar.DATE);

      firstDay = new GregorianCalendar(year, month, date);
      oneDay = new GregorianCalendar(year, month, date);
      oneWeek = new GregorianCalendar(year, month, date);
      oneMonth = new GregorianCalendar(year, month, date);
      oneQuarter = new GregorianCalendar(year, month, date);
      oneYear = new GregorianCalendar(year, month, date);

      oneDay.add(GregorianCalendar.DATE, 1);
      oneWeek.add(GregorianCalendar.DATE, 7);
      oneMonth.add(GregorianCalendar.MONTH, 1);
      oneQuarter.add(GregorianCalendar.MONTH, 3);
      oneYear.add(GregorianCalendar.YEAR, 1);
   }

   ReviewDates() {
      this(new GregorianCalendar());
   }

   public void listDates() {
      DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
      Date startDate = firstDay.getTime();
      Date date1 = oneDay.getTime();
      Date date2 = oneWeek.getTime();
      Date date3 = oneMonth.getTime();
      Date date4 = oneQuarter.getTime();
      Date date5 = oneYear.getTime();

      String ss =  df.format(startDate);
      String ss1 = df.format(date1);
      String ss2 = df.format(date2);
      String ss3 = df.format(date3);
      String ss4 = df.format(date4);
      String ss5 = df.format(date5);

      System.out.println("Start date is " + ss);
      System.out.println("Following review dates are:");
      System.out.println(ss1);
      System.out.println(ss2);
      System.out.println(ss3);
      System.out.println(ss4);
      System.out.println(ss5);
      System.out.println();
   }

   public GregorianCalendar[] getDates() {
      GregorianCalendar[] memoryDates = new GregorianCalendar[dateArraySize];
      memoryDates[0] = firstDay;
      memoryDates[1] = oneDay;
      memoryDates[2] = oneWeek;
      memoryDates[3] = oneMonth;
      memoryDates[4] = oneQuarter;
      memoryDates[5] = oneYear;
      return memoryDates;
   }

   public GregorianCalendar getFirstDay() {
      return this.firstDay;
   }

   public GregorianCalendar getOneDay() {
      return this.oneDay;
   }

   public GregorianCalendar getOneWeek() {
      return this.oneWeek;
   }

   public GregorianCalendar getOneMonth() {
      return this.oneMonth;
   }

   public GregorianCalendar getOneQuarter() {
      return this.oneQuarter;
   }

   public GregorianCalendar getOneYear() {
      return this.oneYear;
   }


下面是使用ReviewDates类列出复习日期的例子程序:

import java.util.*;

public class ShowDates {
   public static void main(String[] args) {
      ReviewDates rd = new ReviewDates();
      rd.listDates();

      GregorianCalendar gc = new GregorianCalendar(2001, Calendar.JANUARY, 15);
      ReviewDates jan15 = new ReviewDates(gc);
      jan15.listDates();
   }
}

总结
 这篇文章介绍了关于日期处理的3个重要的类:Date,DateFormat,GregorianCalendar.这些类让你创建日期,转换成字符串,和计算日期基本元素。处理Java中的日期问题,这篇文章只是冰山一角。可是,我在这里介绍的类和方法不仅仅是你学习高级技术的跳板,这些类和方法本身就可以处理很多通常的日期相关的任务
关于作者
 Robert Nielsen是SCJP。他拥有硕士学位,专攻计算机教育,并且在计算机领域执教多年。他也在各样的杂志上发表过很多计算机相关的文章。
关于译者
Cocia Lin(cocia@163.com)是程序员。他拥有学士学位,现在专攻Java相关技术,刚刚开始在计算机领域折腾。

9:52:14  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2005-9-15
Java的时间处理

这篇文章是在我发表过的<计算Java时间>(译者:已经翻译完成)的基础上的。在这里,我列出那篇文章几个你应该熟悉得关键点。如果这几点你不太清楚,我建议你读一下<计算Java时间>,了解一下。
1. Java计算时间依靠1970年1月1日开始的毫秒数.                                                                                                                                                              
2. Date类的构造函数Date()返回代表当前创建的时刻的对象。Date的方法getTime()返回一个long值在数值上等于1970年1月1日之前或之后的时刻。


3. DateFormat类用来转换Date到String,反之亦然。静态方法getDateInstance()返回DateFormat的缺省格式;getDateInstance(DateFormat.FIELD)返回指定的DateFormat对象格式。Format(Date d)方法返回String表示日期,例如"January 1,2002."反过来,parse(String s)方法返回以参数字符串表示的Date对象。


4. format()方法返回的字符串格式根据不同地区的时间设置而有所不同。


5. GregorianCalendear类有两个重要的构造函数:GregorianCalerdar(),返回代表当前创建时间的对象;GregorianCalendar(int year,int month,int date)返回代表任意日期的对象。GregorianCalendar类的getTime()方法返回日期对象。Add(int field,int amount)方法通过加或减时间单位,象天数,月数或年数来计算日期。


GregorianCalendar和 时间
 两个GregorianCalendar的构造函数可以用来处理时间。前者创建一个表示日期,小时和分钟的对象:

GregorianCalendar(int year, int month, int date, int hour, int minute)

第二个创建一个表示一个日期,小时,分钟和秒:

GregorianCalendar(int year, int month, int date, int hour, int minute, int second)

首先,我应该提醒一下,每一个构造函数需要时间信息中的日期信息(年,月,日)。如果你想说2:30 p.m.,你必须指出日期。
同样,每一个GregorianCalendar构造函数创建一个在时间上使用毫秒计算的对象。所以,如果你的构造函数只提供年,月,日参数,那小时,分钟,秒和毫秒的值将被置0.


DateFormat和时间
你可以使用静态方法getDateTimeInstance(int dateStyle,int timeStyle)来建立DateFormat对象来显示时间和日期。这个方法表明你想要的日期和时间格式。如果你喜欢使用缺省格式,可以使用getDateTimeInstance()来代替它。
你可以使用静态方法getTimeInstance(int timeStyle)创建DateFormat对象来显示正确的时间。
下面的程序示范了getDateTimeInstance()和getTimeInstance()怎样工作:

import java.util.*;
import java.text.*;

public class Apollo {
   public static void main(String[] args) {
      GregorianCalendar liftOffApollo11 = new GregorianCalendar(1969, Calendar.JULY, 16, 9, 32);
      Date d = liftOffApollo11.getTime();
      DateFormat df1 = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
      DateFormat df2 = DateFormat.getTimeInstance(DateFormat.SHORT);
      String s1 = df1.format(d);
      String s2 = df2.format(d);
      System.out.println(s1);
      System.out.println(s2);
   }
}  
       
在我的电脑上,上面的程序显示如下:

Jul 16, 1969 9:32:00 AM
9:32 AM
(输出根据你所在得地区有所不同)

计算时间间隔
     你可能有时需要计算过去的时间;例如,给你开始和结束时间,你想知道制造流程的持续时间。一个出租公司按小时或天数出租东西,计算时间对他们也很有用。同样的,在金融界,经常需要计算重要的支付时间。
将问题复杂化,人类至少是用两种方法计算时间。你可以说一天已经结束当24小时过去了,或者日历从今天翻到明天。我们将讨论我们想到的这两种情况。


时间段,情况 1:严格时间单位
在这种情况中,只有24小时过去,这天才过去,60分钟过去,这个小时才过去,60秒过去,这个分钟才过去,以此类推。在这个方法中,23小时的时间将被认为是0天。
使用这种方法计算时间段,你从计算过去的毫秒开始。为了做到这一点,首先转换每个日期为从1970年1月1日起得毫秒数。你可以从第二个毫秒值中减去第一个毫秒值。这里有一个简单的计算:

import java.util.*;

public class ElapsedMillis {
   public static void main(String[] args) {
      GregorianCalendar gc1 = new GregorianCalendar(1995, 11, 1, 3, 2, 1);
      GregorianCalendar gc2 = new GregorianCalendar(1995, 11, 1, 3, 2, 2);
      // the above two dates are one second apart
      Date d1 = gc1.getTime();
      Date d2 = gc2.getTime();
      long l1 = d1.getTime();
      long l2 = d2.getTime();
      long difference = l2 - l1;
      System.out.println("Elapsed milliseconds: " + difference);
   }
}    

上面的程序打印如下:

Elapsed milliseconds: 1000

这个程序也带来一点混淆。GregorianCalendar类的getTime()返回一个Date对象,Date类的getTime()方法返回从1970年1月1日到这个时间的long类型的毫秒数值。虽然他们的方法名字相同,返回值却不一样!
下面的程序片断用简单的整数除法转换毫秒到秒:

long milliseconds = 1999;
long seconds = 1999 / 1000;

这种方法舍去小数部分转换毫秒到秒,所以1,999毫秒等于1秒,2,000毫秒等于2秒。
计算更大的单位-例如天数,小时和分钟-给定一个时间数值,可以使用下面的过程:
1. 计算最大的单位,减去这个数值的秒数
2. 计算第二大单位,减去这个数值的秒数
3. 重复操作直到只剩下秒
例如,如果你的时间的10,000秒,你想知道这个数值相应的是多少小时,多少分钟,多少秒,你从最大的单位开始:小时。10,000除以3600(一个小时的秒数)得到小时数。使用整数除法,答案是2小时(整数除法中小数舍去)计算剩下的秒数,10,000-(3,600 x 2) = 2,800秒。所以你有2小时和2,800秒。
将2,800秒转换成分钟,2,800除以60。使用整数除法,答案是46。2,800 - (60 x 46) = 40秒。最后答案是2小时,46分,40秒。
下面的Java程序使用上面的计算方法:

import java.util.*;

public class Elapsed1 {
   public void calcHMS(int timeInSeconds) {
      int hours, minutes, seconds;
      hours = timeInSeconds / 3600;
      timeInSeconds = timeInSeconds - (hours * 3600);
      minutes = timeInSeconds / 60;
      timeInSeconds = timeInSeconds - (minutes * 60);
      seconds = timeInSeconds;
      System.out.println(hours + " hour(s) " + minutes + " minute(s) " + seconds + " second(s)");
   }

   public static void main(String[] args) {
      Elapsed1 elap = new Elapsed1();
      elap.calcHMS(10000);
   }


输出结果如下:

2 hour(s) 46 minute(s) 40 second(s)

上面的程序甚至在时间少于一个小时也可以正确的计算小时数。例如,你用上面的程序计算1,000秒,输出入下:
0 hour(s) 16 minute(s) 40 second(s)
举一个现实世界的例子,下面的程序计算阿波罗11飞到月球使用得时间:

import java.util.*;

public class LunarLanding {

   public long getElapsedSeconds(GregorianCalendar gc1, GregorianCalendar gc2) {
      Date d1 = gc1.getTime();
      Date d2 = gc2.getTime();
      long l1 = d1.getTime();
      long l2 = d2.getTime();
      long difference = Math.abs(l2 - l1);
      return difference / 1000;
   }

   public void calcHM(long timeInSeconds) {
      long hours, minutes, seconds;
      hours = timeInSeconds / 3600;
      timeInSeconds = timeInSeconds - (hours * 3600);
      minutes = timeInSeconds / 60;
      System.out.println(hours + " hour(s) " + minutes + " minute(s)" );
   }

   public static void main(String[] args) {
      GregorianCalendar lunarLanding = new GregorianCalendar(1969, Calendar.JULY, 20, 16, 17);
      GregorianCalendar lunarDeparture = new GregorianCalendar(1969, Calendar.JULY, 21, 13, 54);
      GregorianCalendar startEVA = new GregorianCalendar(1969, Calendar.JULY, 20, 22, 56);
      GregorianCalendar endEVA = new GregorianCalendar(1969, Calendar.JULY, 21, 1, 9);

      LunarLanding apollo = new LunarLanding();

      long eva = apollo.getElapsedSeconds(startEVA, endEVA);
      System.out.print("EVA duration = ");
      apollo.calcHM(eva);

      long lunarStay = apollo.getElapsedSeconds(lunarLanding, lunarDeparture);
      System.out.print("Lunar stay = ");
      apollo.calcHM(lunarStay);
   }
}         

上面程序输出如下:

EVA duration = 2 hour(s) 13 minute(s)
Lunar stay = 21 hour(s) 37 minute(s)

目前为止,我们计算的基础公式是这样的:1分钟=60秒,1小时=60分,1天=24小时。
"1个月=?天,1年=?天"怎么办?
月份的天数有28,29,30,31;一年可以是365或366天。因此,当你试图计算严格单位的月份和年时,问题就产生了。例如,如果你使用月份的平均天数(近似30.4375),并且计算下面的时间间隔:

* July 1, 2:00 a.m. to July 31, 10:00 p.m.
* February 1, 2:00 a.m. to February 29, 10:00 p.m.

第一个计算结果是1个月;第二个结果是0个月!
所以,在计算严格单位时间的月份和年份是要想好。


时间段,情况 2:时间单位变化
时间单位的变化相当的简单:如果你要统计天数,你可以简单的统计日期变化次数。例如,如果某事15日开始,17日结束,经过2天。(日期先是便到16,再到17)同样的,一个步骤下午3:25开始,4:10 p.m结束,历时1个小时,因为小时数值变了一次(从3到4)。
图书馆经常使用这种习惯计算时间。例如,如果你从图书馆接一本书,我不能占有这本书最少24小时,会认为图书馆这样才给你算一天。而是,我的账号上记录我借书的日期。日期以变成下一天,我就已经结这本书一天了,即使总计不足24小时。
当使用单位的变化来计算时间段,通常感觉计算的时间没有多于一个时间单位。例如,如果9:00 p.m.我借了一本图书馆的书,第二天中午还回去,我能算出我借了这本书一天了。可是,有一种感觉在问:"1天和几个小时呢?"这本说总计借出15个小时,答案是一天还差9个小时呢?因此,这篇文章里,我将以一个时间单位变化计算时间。


单位变化的时间算法
 这是你怎样计算两个日期的时间变化:
1. 制作两个日期的拷贝。Close()方法能制作拷贝。
2. 使用日期拷贝,将所有的小于时间单位变化的部分设置成它的最小单位。例如,如果计算天数,那么将小时,分钟,秒和毫秒设置成0。这种情况中,使用clear()方法将时间值设置称他们各自的最小值。
3. 取出较早的日期,将你要计算的单位加1,重复直到两个日期相等。你加1的次数就是答案。可以使用before()和after()方法,他们返回boolean值,来判断是否一个日期在另一个日期之前或之后。
下面的类的方法用来计算天数和月数。

import java.util.*;

public class ElapsedTime {

   public int getDays(GregorianCalendar g1, GregorianCalendar g2) {
      int elapsed = 0;
      GregorianCalendar gc1, gc2;

      if (g2.after(g1)) {
         gc2 = (GregorianCalendar) g2.clone();
         gc1 = (GregorianCalendar) g1.clone();
      }
      else   {
         gc2 = (GregorianCalendar) g1.clone();
         gc1 = (GregorianCalendar) g2.clone();
      }

      gc1.clear(Calendar.MILLISECOND);
      gc1.clear(Calendar.SECOND);
      gc1.clear(Calendar.MINUTE);
      gc1.clear(Calendar.HOUR_OF_DAY);

      gc2.clear(Calendar.MILLISECOND);
      gc2.clear(Calendar.SECOND);
      gc2.clear(Calendar.MINUTE);
      gc2.clear(Calendar.HOUR_OF_DAY);

      while ( gc1.before(gc2) ) {
         gc1.add(Calendar.DATE, 1);
         elapsed++;
      }
      return elapsed;
   }

   public int getMonths(GregorianCalendar g1, GregorianCalendar g2) {
      int elapsed = 0;
      GregorianCalendar gc1, gc2;

      if (g2.after(g1)) {
         gc2 = (GregorianCalendar) g2.clone();
         gc1 = (GregorianCalendar) g1.clone();
      }
      else   {
         gc2 = (GregorianCalendar) g1.clone();
         gc1 = (GregorianCalendar) g2.clone();
      }

      gc1.clear(Calendar.MILLISECOND);
      gc1.clear(Calendar.SECOND);
      gc1.clear(Calendar.MINUTE);
      gc1.clear(Calendar.HOUR_OF_DAY);
      gc1.clear(Calendar.DATE);

      gc2.clear(Calendar.MILLISECOND);
      gc2.clear(Calendar.SECOND);
      gc2.clear(Calendar.MINUTE);
      gc2.clear(Calendar.HOUR_OF_DAY);
      gc2.clear(Calendar.DATE);

      while ( gc1.before(gc2) ) {
         gc1.add(Calendar.MONTH, 1);
         elapsed++;
      }
      return elapsed;
   }
}

你可以在上面的类中补充另外的方法来处理小时和分钟。同样,计算时间段的算法能更高效一些,尤其是时间相隔很长。可是,作为介绍目的,这个算法有短小和简单的优势。
下面的例子使用ElapsedTime类来计算两个日期之间的天使,而后是月数:

import java.util.*;

public class Example {
   public static void main(String[] args) {
      GregorianCalendar gc1 = new GregorianCalendar(2001, Calendar.DECEMBER, 30);
      GregorianCalendar gc2 = new GregorianCalendar(2002, Calendar.FEBRUARY, 1);

      ElapsedTime et = new ElapsedTime();
      int days = et.getDays(gc1, gc2);
      int months = et.getMonths(gc1, gc2);

      System.out.println("Days = " + days);
      System.out.println("Months = " + months);
   }
}

当计算时,上面的程序可能有用,例如,最近的航班。它显示下面的输出:

Days = 33
Months = 2

(OK,关于航班的计算有些夸张;这个天数算法很适合像图书馆借书这样的应用,你看到了她怎样工作)

20:56:35  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
java日期相减问题
两个日期相减,必须是Date型变量使用其getTime()后相减,所以如果是从Calendar获得时间,则需:
Calendar date1 = Calendar.getInstance();
Calendar date2 = Calendar.getInstance();

date1.getTime().getTime() - date2.getTime().getTime() ;

结果为long型,毫秒!
 
毫秒轉換成時間
 
public void calcHMS(int timeInSeconds) {
      int hours, minutes, seconds;
      hours = timeInSeconds / 3600;
      timeInSeconds = timeInSeconds - (hours * 3600);
      minutes = timeInSeconds / 60;
      timeInSeconds = timeInSeconds - (minutes * 60);
      seconds = timeInSeconds;
      System.out.println(hours + " hour(s) " + minutes + " minute(s) " + seconds + " second(s)");
   }
14:16:49  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2005-8-10
对集合排序的例子

import java.util.ArrayList;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/*
 * 当前文件: TestCollections.java
 * 创建日期: 2005-8-10
 * 版 本 号: 1.0
 * 作 者: kang1
 *
 */

/**
 * 亲爱的kang1,请您在此详细描述我的用途.
 */

class BookType{
 String idsn;
 String name;
 /**
  * @param idsn
  * @param name
  */
 public BookType(String idsn, String name)
 {
  super();
  this.idsn = idsn;
  this.name = name;
 }
 /**
  * @return Returns the idsn.
  */
 public String getIdsn()
 {
  return idsn;
 }
 /**
  * @param idsn The idsn to set.
  */
 public void setIdsn(String idsn)
 {
  this.idsn = idsn;
 }
 /**
  * @return Returns the name.
  */
 public String getName()
 {
  return name;
 }
 /**
  * @param name The name to set.
  */
 public void setName(String name)
 {
  this.name = name;
 }
}
public class TestCollections
{

 public static void main(String[] args)
 {
  List list=new ArrayList();
  list.add(new BookType("1","k"));
  list.add(new BookType("5","z"));
  list.add(new BookType("4","g"));
  
  Comparator OrderIsdn = new Comparator(){
   public int compare(Object o1, Object o2){
    BookType b1=(BookType)o1;
    BookType b2=(BookType)o2;
    return (b1.getIdsn().hashCode()-b2.getIdsn().hashCode());
   }
  };
  
  Comparator OrderName = new Comparator(){
   public int compare(Object o1, Object o2){
    BookType b1=(BookType)o1;
    BookType b2=(BookType)o2;
    return (b1.getName().hashCode()-b2.getName().hashCode());
   }
  };
  
  Collections.sort(list,OrderName);
  Collections.reverse(list);
  
  for(int i=0;i<list.size();i++){
   BookType ss=(BookType)list.get(i);
   System.out.print(ss.getIdsn());
   System.out.println(ss.getName());
  }
  
 }
}

9:35:03  |  固定链接 | 引用通告 (1) | 记录它 | 技術文章
2005-8-9
Java中的排序

要实现排序功能,一般有两种途径,这里对基本类型不适用,基本类型一般有Arrays中的静态方法.

1.对象本身实现Comparable接口,那么该类的实例就是可以排序的.
有关Comparable: http://blog.csdn.net/treeroot/archive/2004/09/09/99613.aspx
只要实现了Comparable接口,就可以调用Collections的sort方法对集合中的元素排序.

2.指定一个Comparator,也就是实现了Comparator的类的一个实例.
但是Java本身只提供了一个Comparator的实现,就是Collections.reverseOrder().
该方法返回的是一个已经实现了Comparable接口的反序.

看一下Comparator的全部内容:

public interface Comparator {
  int compare(Object o1, Object o2);
  boolean equals(Object obj);
}

定义了两个方法,其实我们一般都只需要实现compare方法就行了,因为类都是默认从Object继承
所以会使用Object的equals方法.
Comparator一般都作为一个匿名类出现,对于没有实现Comparable的对象的集合,排序的时候
需要指定一个Comparator.

这里举例说明
对于实现了Comparable的类我们就用最简单的Integer
List list=new ArrayList();
list.add(new Integer(3));
list.add(new Integer(53));
list.add(new Integer(34));
Collections.sort(list);

对于没有实现Comparable的,我们就用Object,按照hashCode大小来排序.
List list= new ArrayList();
list.add(new Object());
list.add(new Object());
list.add(new Object());
Collections.sort(list,new Comparator(){ public int compare(Object o1, Object o2){
                    return (o1.hashCode()-o2.hashCode());
                })

20:51:14  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
API解读:Collections

Collections和Arrays一样是一个不可实例化的类,提供了一些静态方法来操作Collection。

1.排序 sort
public static void sort(List list)
public static void sort(List list, Comparator c)
用于对List排序,可以使用自然排序,也可以指定Comparator,返回的是从小到大的有序List。

2.二分查找 binarySearch
public static int binarySearch(List list, Object key)
public static int binarySearch(List list, Object key, Comparator c)
应用和排序一样,List必须是有序的,否则改方法无作用。

3.反转
public static void reverse(List list)
适用于List,返回的List和原来的顺序相反

4.打乱顺序
public static void shuffle(List list)
public static void shuffle(List list, Random rnd)
就是把原来的List的顺序随机弄乱。

5.元素直接置换
public static void swap(List list, int i, int j)
这个方法显得有点牵强,我觉得没有必要出现在API里面,实现就是一条语句:
list.set(i, list.set(j, list.get(i)));

6.填充
public static void fill(List list, Object obj)
把List中的元素全部用一个值填充

7.拷贝
public static void copy(List dest, List src)
把源List拷贝到目的List,目的List必须足够大

8.最小值
public static Object min(Collection coll)
public static Object min(Collection coll, Comparator comp)

9.最大值
public static Object max(Collection coll)
public static Object max(Collection coll, Comparator comp)

10.移动
public static void rotate(List list, int distance)
元素右移,有点像 int a=0xff>>8的味道

11.替换
public static boolean replaceAll(List list, Object oldVal, Object newVal)
看方法名就知道了

12.求子List
public static int indexOfSubList(List source, List target)
public static int lastIndexOfSubList(List source, List target)
和String类的indexOf和lastIndexOf类似

13.同步
public static Collection synchronizedCollection(Collection c)
public static List synchronizedList(List list)
public static Map synchronizedMap(Map m)
public static Set synchronizedSet(Set s)
public static SortedMap synchronizedSortedMap(SortedMap m)
public static SortedSet synchronizedSortedSet(SortedSet s)
因为Collection框架中的类大部分都是未经过同步的,可以通过这些方法同步
注意这里没有使用方法重载,因为那么命名不同。另外,返回的虽然是一个同步了的Collection
但是通过迭代器访问时仍然需要自己同步,因为迭代器返回没有被同步。

14.只读
public static Collection unmodifiableCollection(Collection c)
public static List unmodifiableList(List list)
public static Map unmodifiableMap(Map m)
public static Set unmodifiableSet(Set s)
public static SortedMap unmodifiableSortedMap(SortedMap m)
public static SortedSet unmodifiableSortedSet(SortedSet s)
这些方法返回一个只读的Collection,我发现这个有的时候真的特别有用,如果你试图调用修改
方法,会获得一个UnsupportedOperationException系统异常。

15.常量
public static final List EMPTY_LIST = new EmptyList()
public static final Map EMPTY_MAP = new EmptyMap()
public static final Set EMPTY_SET = new EmptySet()
这些常量就是表示空的集合,本身不是null,但是不包含任何元素。

16.单元素集合
public static Set singleton(Object o)
public static List singletonList(Object o)
public static Map singletonMap(Object key, Object value)
这些集合只含有一个元素。

17.多元素集合
public static List nCopies(int n, Object o)
返回一个List,含有n个相同的元素

18.比较器常量
public static Comparator reverseOrder()
这个比较器和自然顺序的排序相反。

19.最后2个
public static ArrayList list(Enumeration e)
public static Enumeration enumeration(final Collection c)
List和Enumeration的转换,由于Enumeration属于过时的API,应该没有什么机会用到他们。

整个方法都在这里,主要是对List的操作,Set和Map的相对少一些。

20:50:32  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
HashMap中的对象根据成员进行自定义排序

      Map是Java中最常用的存储对象的集合类之一,存储在HashMap中的对象在取出时是无序的,下文以示例介绍了如果对HashMap中存储的对象根据成员进行自定义排序。

应用说明:计算缓存对象的点击次数按次数排序输出。

1. 定义CacheObj类

定义了一个简单的对象,这个对象将存储在一个HashMap中,希望根据CacheObj中的整型成员变量cachedCounter进行排序输出。


/*
 * Created on 2003-3-5
 */

package com.cache;
/**  * @author Weidong *  */
public class CacheObj {
  int cachedCounter;
  Object cacheObj = null;
 
/**
   * @return Returns the cacheObj.
   */

  public Object getCacheObj() {
    return cacheObj;
  }
 
/**
   * @param cacheObj
   *          The cacheObj to set.
   */

  publicvoid setCacheObj(Object cacheObj) {
    this.cacheObj = cacheObj;
  }
 
/**
   * @return Returns the cachedCounter.
   */

  publicint getCachedCounter() {
    return cachedCounter;
  }
 
/**
   * @param cachedCounter
   *          The cachedCounter to set.
   */

  publicvoid setCachedCounter(int cachedCounter) {
    this.cachedCounter = cachedCounter;
  }
}

 

2. 自定义HotItemComparator

HotItemComparator实现了Comparator 接口, 实现的方法非常简单就是根据cachedCounter进行比较返回比较结果

正序:return obj1.getCachedCounter() - obj2.getCachedCounter();

倒序:return obj2.getCachedCounter() - obj1.getCachedCounter();


package com.cache;
import java.util.Comparator;
/**
 * @author Weidong 
 */

public final class HotItemComparator implements Comparator {
   
/*
     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
     */

    publicint compare(Object arg0, Object arg1) {
        CacheObj obj1 = (CacheObj) arg0;
        CacheObj obj2 = (CacheObj) arg1;
        return obj1.getCachedCounter() - obj2.getCachedCounter();
    }
}

3. 测试用例

  • 定义变量:public static final Comparator HOT_ITEM = new HotItemComparator();
  • 将HashMap追加到List对象sortList中:sortList.addAll(caches.values());
  • 对sorList应用HOT_ITEM排序:Collections.sort(sortList, HOT_ITEM);

    package com.cache;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    /**
     * @author Weidong
     */

    publicclass TestBean {
        publicstaticfinal Comparator HOT_ITEM = new HotItemComparator();
     
        publicstaticvoid sortMap() {
            Map caches = new HashMap();
            CacheObj s1 = new CacheObj();
            s1.setCachedCounter(2);
            CacheObj s2 = new CacheObj();
            s2.setCachedCounter(3);
            CacheObj s3 = new CacheObj();
            s3.setCachedCounter(1);
            caches.put("1", s1);
            caches.put("2", s2);
            caches.put("3", s3);
     
            List sortList = new ArrayList();
            sortList.addAll(caches.values());
            Collections.sort(sortList, HOT_ITEM);
            Iterator iter = sortList.iterator();
            while (iter.hasNext()) {
                CacheObj s = (CacheObj) iter.next();
                System.out.println("counter is "+s.getCachedCounter());
            }
        }
        publicstaticvoid main(String[] args) {
            sortMap();
        }
    }

输出:文中用正序

counter is 1
counter is 2
counter is 3
19:36:18  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2005-8-5
session
我原来开发过一些网站,也比较关心用户登陆的机制。谈谈我的一些看法。
我觉得用session记录是非常方便的,但是session是耗内存的,而且在需要跨服务器时需要额外的支持。所以我用cookie加数据库的形式模拟session。我在用户登陆时写2个cookie,一个是用户名还有一个是用md5编码的随机字符串,并在A_loginUsers表中记录用户名,用户次登陆的IP,登陆时产生的随机字符串以及登陆时间。
然后在每个需要登陆的页面我都会用以下sql语句验证:
1
2
3
4
"select lastChkTime from A_loginUsers where userName='"
+SuccessUserName+"' and rndChkFlag='"+abSimulateSession+
"' and userIP='"+getUserREMOTE_ADDR+"' and DATE_ADD
(lastChkTime,INTERVAL 1200 SECOND)>='"+nowTime+"'"

SuccessUserName是存在客户端中的cookie,abSimulateSession是登陆时写在客户端的随机字符串。getUserREMOTE_ADDR是取得现在用户的IP。只要这3项有任何一项不符,就认为是非法登陆。这样用户即使能更改cookie中的用户名,因为随即码不同也不能登陆。
1
2
+"' and DATE_ADD(lastChkTime,INTERVAL 1200 SECOND)>='"+nowTime+"'"
是判断用户是否登陆超时。如果不需要超时判断可以将这句去除。

以上是我习惯用的用户登陆机制,
20:57:24  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2005-7-29
DES對字符串進行加密與解密

/*
 * 当前文件: DesEncrypt.java
 * 创建日期: 2005-7-29
 * 版 本 号: 1.0
 * 作 者: kang1
 *
 */
package com.chuangs.pai.utilities;


import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 *
 * 使用DES加密与解密,可对byte[],String类型进行加密与解密
 * 密文可使用String,byte[]存储.
 *
 * 方法:
 *  void getKey(String strKey)    从strKey的字条生成一个Key
 *
 *  String getEncString(String strMing)  对strMing进行加密,返回String密文
 *  String getDesString(String strMi)  对strMin进行解密,返回String明文
 *
 * byte[] getEncCode(byte[] byteS)   byte[]型的加密
 * byte[] getDesCode(byte[] byteD)   byte[]型的解密
 */

public class DesEncrypt

 Key key;
 
 /**
  * 根据参数生成KEY
  * @param strKey
  */
 public void getKey(String strKey)
 {
  try{
   KeyGenerator _generator = KeyGenerator.getInstance("DES");
   _generator.init(new SecureRandom(strKey.getBytes()));
   this.key = _generator.generateKey();
   _generator=null;
  }catch(Exception e){
   e.printStackTrace();
  }  
 }
 /**
  * 加密 String明文输入,String密文输出
  * @param strMing
  * @return
  */
 public String getEncString(String strMing)
 {
  byte[] byteMi = null;
  byte[] byteMing = null;
  String strMi = "";
  BASE64Encoder base64en = new BASE64Encoder();
  try
  {
   byteMing = strMing.getBytes("UTF8");
   byteMi = this.getEncCode(byteMing);
   strMi = base64en.encode(byteMi);
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  finally
  {
   base64en = null;
   byteMing = null;
   byteMi = null;
  }
  return strMi;
 }
 /**
  * 解密 以String密文输入,String明文输出
  * @param strMi
  * @return
  */
 //
 public String getDesString(String strMi)
 {
  BASE64Decoder base64De = new BASE64Decoder();
  byte[] byteMing = null;
  byte[] byteMi = null;
  String strMing = "";
  try
  {
   byteMi = base64De.decodeBuffer(strMi);
   byteMing = this.getDesCode(byteMi);
   strMing = new String(byteMing, "UTF8");
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  finally
  {
   base64De = null;
   byteMing = null;
   byteMi = null;
  }
  return strMing;
 }
 /**
  * 加密 以byte[]明文输入,byte[]密文输出
  * @param byteS
  * @return
  */
 ///byte明文,加密成byte密文
 private byte[] getEncCode(byte[] byteS)
 {
  byte[] byteFina = null;
  Cipher cipher;
  try
  {
   cipher = Cipher.getInstance("DES");
   cipher.init(Cipher.ENCRYPT_MODE, key);
   byteFina = cipher.doFinal(byteS);
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  finally
  {
   cipher = null;
  }
  return byteFina;
 }
 /**
  * 解密 以byte[]密文输入,以byte[]明文输出
  * @param byteD
  * @return
  */
 //byte密文,加密成byte明文
 private byte[] getDesCode(byte[] byteD)
 {
  Cipher cipher;
  byte[] byteFina=null;
  try{
   cipher = Cipher.getInstance("DES");
   cipher.init(Cipher.DECRYPT_MODE, key);
   byteFina = cipher.doFinal(byteD);
  }catch(Exception e){
   e.printStackTrace();
  }finally{
   cipher=null;
  }
  return byteFina;
  
 }

}

12:00:27  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2005-7-28
JavaMail发送邮件中主题乱码的解决方法
在设置邮件主题前需要将主题字串的字节编码为BASE64格式,并添加编码头,示例代码如下:
    sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder();    msg.setSubject("=?GB2312?B?"+enc.encode(subject.getBytes())+"?=");
10:42:26  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2005-7-19
java中文问题的解决

1.问题的起源
  每个国家(或区域)都规定了计算机信息交换用的字符编码集,如美国的 ASCII,中国的 GB2312-80,日本的 JIS 等,作为该国家/区域内信息处理的基础,有着统一编码的重要作用。字符编码集按长度分为 SBCS(单字节字符集),DBCS(双字节字符集)两大类。早期的软件(尤其是操作系统),为了解决本地字符信息的计算机处理,出现了各种本地化版本(L10N),为了区分,引进了 LANG,Codepage 等概念。但是由于各个本地字符集代码范围重叠,相互间信息交换困难;软件各个本地化版本独立维护成本较高。因此有必要将本地化工作中的共性抽取出来,作一致处理,将特别的本地化处理内容降低到最少。这也就是所谓的国际化(I18N)。各种语言信息被进一步规范为 Locale 信息。处理的底层字符集变成了几乎包含了所有字形的 Unicode。

  现在大部分具有国际化特征的软件核心字符处理都是以 Unicode 为基础的,在软件运行时根据当时的 Locale/Lang/Codepage 设置确定相应的本地字符编码设置,并依此处理本地字符。在处理过程中需要实现 Unicode 和本地字符集的相互转换,甚或以 Unicode 为中间的两个不同本地字符集的相互转换。这种方式在网络环境下被进一步延伸,任何网络两端的字符信息也需要根据字符集的设置转换成可接受的内容。

  Java 语言内部是用 Unicode 表示字符的,遵守 Unicode V2.0。Java 程序无论是从/往文件系统以字符流读/写文件,还是往 URL 连接写 HTML 信息,或从 URL 连接读取参数值,都会有字符编码的转换。这样做虽然增加了编程的复杂度,容易引起混淆,但却是符合国际化的思想的。

  从理论上来说,这些根据字符集设置而进行的字符转换不应该产生太多问题。而事实是由于应用程序的实际运行环境不同,Unicode 和各个本地字符集的补充、完善,以及系统或应用程序实现的不规范,转码时出现的问题时时困扰着程序员和用户。

2.GB2312-80,GBK,GB18030-2000 汉字字符集
  其实解决 JAVA 程序中的汉字编码问题的方法往往很简单,但理解其背后的原因,定位问题,还需要了解现有的汉字编码和编码转换。

  GB2312-80 是在国内计算机汉字信息技术发展初始阶段制定的,其中包含了大部分常用的一、二级汉字,和 9 区的符号。该字符集是几乎所有的中文系统和国际化的软件都支持的中文字符集,这也是最基本的中文字符集。其编码范围是高位0xa1-0xfe,低位也是 0xa1-0xfe;汉字从 0xb0a1 开始,结束于 0xf7fe;

  GBK 是 GB2312-80 的扩展,是向上兼容的。它包含了 20902 个汉字,其编码范围是 0x8140-0xfefe,剔除高位 0x80 的字位。其所有字符都可以一对一映射到 Unicode 2.0,也就是说 JAVA 实际上提供了 GBK 字符集的支持。这是现阶段 Windows 和其它一些中文操作系统的缺省字符集,但并不是所有的国际化软件都支持该字符集,感觉是他们并不完全知道 GBK 是怎么回事。值得注意的是它不是国家标准,而只是规范。随着 GB18030-2000国标的发布,它将在不久的将来完成它的历史使命。

  GB18030-2000(GBK2K) 在 GBK 的基础上进一步扩展了汉字,增加了藏、蒙等少数民族的字形。GBK2K 从根本上解决了字位不够,字形不足的问题。它有几个特点:

  ●它并没有确定所有的字形,只是规定了编码范围,留待以后扩充。

  ●编码是变长的,其二字节部分与 GBK 兼容;四字节部分是扩充的字形、字位,其编码范围是首字节 0x81-0xfe、二字节0x30-0x39、三字节 0x81-0xfe、四字节0x30-0x39。

  ●它的推广是分阶段的,首先要求实现的是能够完全映射到 Unicode 3.0 标准的所有字形。

  ●它是国家标准,是强制性的。

  现在还没有任何一个操作系统或软件实现了 GBK2K 的支持,这是现阶段和将来汉化的工作内容。

3.JSP/Servlet 汉字编码问题及在 WAS 中的解决办法
  3.1 常见的 encoding 问题的现象

  网上常出现的 JSP/Servlet encoding 问题一般都表现在 browser 或应用程序端,如:

  ●浏览器中看到的 Jsp/Servlet 页面中的汉字怎么都成了 ’?’ ?

  ●浏览器中看到的 Servlet 页面中的汉字怎么都成了乱码?

  ●JAVA 应用程序界面中的汉字怎么都成了方块?

  ●Jsp/Servlet 页面无法显示 GBK 汉字。

  ●Jsp/Servlet 不能接收 form 提交的汉字。

  ●JSP/Servlet 数据库读写无法获得正确的内容。

  隐藏在这些问题后面的是各种错误的字符转换和处理(除第3个外,是因为 Java font 设置错误引起的)。解决类似的字符 encoding 问题,需要了解 Jsp/Servlet 的运行过程,检查可能出现问题的各个点。

  3.2 JSP/Servlet web 编程时的 encoding 问题

  运行于Java 应用服务器的 JSP/Servlet 为 Browser 提供 HTML 内容,其过程如下图所示:


  其中有字符编码转换的地方有:

  a.JSP 编译。Java 应用服务器将根据 JVM 的 file.encoding 值读取 JSP 源文件,并转换为内部字符编码进行 JSP 编译,生成 JAVA 源文件,根据 file.encoding 值写回文件系统。如果当前系统语言支持 GBK,那么这时候不会出现 encoding 问题。如果是英文的系统,如 LANG 是 en_US 的 Linux, AIX 或 Solaris,则要将 JVM 的 file.encoding 值置成 GBK 。系统语言如果是 GB2312,则根据需要,确定要不要设置 file.encoding,将 file.encoding 设为 GBK 可以解决潜在的 GBK 字符乱码问题。

  b.Java 需要被编译为 .class 才能在 JVM 中执行,这个过程存在与a.同样的 file.encoding 问题。从这里开始 servlet 和 jsp 的运行就类似了,只不过 Servlet 的编译不是自动进行的。

  c.Servlet 需要将 HTML 页面内容转换为 browser 可接受的 encoding 内容发送出去。依赖于各 JAVA App Server 的实现方式,有的将查询 Browser 的 accept-charset 和 accept-language 参数或以其它猜的方式确定 encoding 值,有的则不管。因此 constant-encoding 也许是最好的解决方法。对于中文网页,可在 JSP 或 Servlet 中设置 contentType="text/html; charset=GB2312";如果页面中有GBK字符,则设置为contentType="text/html; charset=GBK",由于IE 和 Netscape对GBK的支持程度不一样,作这种设置时需要测试一下。

  因为16位 JAVA char在网络传送时高8位会被丢弃,也为了确保Servlet页面中的汉字(包括内嵌的和servlet运行过程中得到的)是期望的内码,可以用 PrintWriter out=res.getWriter() 取代 ServletOutputStream out=res.getOutputStream(), PrinterWriter 将根据contentType中指定的charset作转换(ContentType需在此之前指定!);也可以用OutputStreamWriter封装 ServletOutputStream 类并用write(String)输出汉字字符串。

对于 JSP,JAVA Application Server 应当能够确保在这个阶段将嵌入的汉字正确传送出去。

  d.这是 URL 字符 encoding 问题。如果通过 get/post 方式从 browser 返回的值中包含汉字信息, servlet 将无法得到正确的值。SUN的 J2SDK 中,HttpUtils.parseName 在解析参数时根本没有考虑 browser 的语言设置,而是将得到的值按 byte 方式解析。这是网上讨论得最多的 encoding 问题。因为这是设计缺陷,只能以 bin 方式重新解析得到的字符串;或者以 hack HttpUtils 类的方式解决。参考文章 2、3 均有介绍,不过最好将其中的中文 encoding GB2312、 CP1381 都改为 GBK,否则遇到 GBK 汉字时,还是会有问题。

  Servlet API 2.3 提供一个新的函数 HttpServeletRequest.setCharacterEncoding 用于在调用 request.getParameter(“param_name”) 前指定应用程序希望的 encoding,这将有助于彻底解决这个问题。

  WebSphere Application Server 对标准的 Servlet API 2.x 作了扩展,提供较好的多语言支持。上述c,d情况,WAS 都要查询 Browser 的语言设置,在缺省状况下zh、zh-cn 等均被映射为 JAVA encoding CP1381(注意:CP1381 只是等同于 GB2312 的一个 codepage,没有 GBK 支持)。这样做我想是因为无法确认 Browser 运行的操作系统是支持GB2312, 还是 GBK,所以取其小。但是实际的应用系统还是要求页面中出现 GBK 汉字,最著名的是朱总理名字中的“?”(rong2 ,0xe946,\u9555),所以有时还是需要将 Encoding/Charset 指定为 GBK。当然 WAS 中变更缺省的 encoding 没有上面说的那么麻烦,针对 a,b,参考文章 5 ),在 Application Server 的命令行参数中指定 -Dfile.encoding=GBK 即可; 针对 d,在 Application Server 的命令行参数中指定-Ddefault.client.encoding=GBK。如果指定了-Ddefault.client.encoding=GBK,那么c情况下可以不再指定charset。

  3.3 数据库读写时的 encoding 问题

  JSP/Servlet 编程中经常出现 encoding 问题的另一个地方是读写数据库中的数据。

  流行的关系数据库系统都支持数据库 encoding,也就是说在创建数据库时可以指定它自己的字符集设置,数据库的数据以指定的编码形式存储。当应用程序访问数据时,在入口和出口处都会有 encoding 转换。对于中文数据,应当保证数据的完整性。GB2312,GBK,UTF-8 等都是可选的数据库 encoding;如果选择 ISO8859-1(8-bit SBCS),那么应用程序在写数据之前须将 16Bit 的一个汉字或 Unicode 拆分成两个 8-bit 的字符,读数据之后则需将两个字节合并起来,同时还有判别其中的 SBCS 字符。没有充分利用数据库 encoding 的作用,反而增加了编程的复杂度,ISO8859-1不是推荐的数据库 encoding。JSP/Servlet编程时,可以先用数据库管理系统提供的功能检查其中的中文数据是否正确。

  然后应当注意的是读出来的数据的 encoding,JAVA 程序中一般得到的是 Unicode。写数据时则相反。

  3.4 定位问题时常用的技巧

  定位中文encoding问题通常采用最笨的也是最有效的办法——在你认为有嫌疑的程序处理后打印字符串的内码。通过打印字符串的内码,你可以发现什么时候中文字符被转换成Unicode,什么时候Unicode被转回中文内码,什么时候一个中文字成了两个 Unicode 字符,什么时候中文字符串被转成了一串问号,什么时候中文字符串的高位被截掉了……

  取用合适的样本字符串也有助于区分问题的类型。如:”aa啊aa?aa” 等中英相间、GB、GBK特征字符均有的字符串。一般来说,英文字符无论怎么转换或处理,都不会失真(如果遇到了,可以尝试着增加连续的英文字母长度)。

4.结束语
  其实 JSP/Servlet 的中文encoding 并没有想像的那么复杂,虽然定位和解决问题没有定规,各种运行环境也各不尽然,但后面的原理是一样的。了解字符集的知识是解决字符问题的基础。不过,随着中文字符集的变化,不仅仅是 java 编程,中文信息处理中的问题还是会存在一段时间的。

5.参考文章
1) Character Problem Review
2) Java 编程技术中汉字问题的分析及解决
3) NLS Characters in WebSphere: SBCS/DBCS display on same page
4) GB18030
5) Setting language encoding in web applications: Websphere applications Server
作者简介
  张建芳,软件工程师,毕业于北京理工大学计算机应用学院,有多年中文本地化经验。您可通过 jfzhang@usa.net 与他联系。

  

8:19:18  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2005-7-18
<html:select>默認選中值
<html:select value="2">  //选中的是"女"
 <html:option value="1">男</html:option>
<html:option value="2">女</html:option>
</html:select>
22:05:41  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
2005-7-15
XML和JSP交互技术(上)
XML和JSP都是近两年才出现的技术,目前已经成为了很多程序员的热点话题。XML(可扩展的标记语言)是用来定义文档标记语言的框架,主要用来存储和发送数据信息,以便各种基于WEB的应用之间能更方便的交换数据。而Jsp是服务器端程序动态设计语言,可以用来设计服务器端各种程序如B2B、B2C等各种系统,由于其面向对象、编译执行、健壮等特性,也得到了越来越多的应用。 

  一个很实用的技术就是如何将XML和JSP结合起来,所幸的是我们不用去写底支持层东西了,因为很多厂商如SUN、IBM等都发布了自己的支持XML的API,其中SUN公司提供了一个支持在Java下使用XML的API-JAXP(JAVATM API for XML Processing Optional Package),这部分API提供了基本的读写、操作XML文档的功能,通过它我们可以很方便的将XML集成到Java应用程序中来。 

  目前JAXP的版本是1.1,除了基本XML支持外还支持SAX 2.0 、DOM 2和XSL技术。SUN公司提供的JAXP可以在SUN公司的主页http://java.sun.com/xml 下载。  

  一、 JAXP的安装 

  1、安装前请确认您用的是JDK 1.1.8 以上版本的Java环境。 

  2、在http://java.sun.com/xml 下载JAXP1.1的zip版本,解压到硬盘中,假定解压目录为#JAXP11,解压后发现有三个jar文件jaxp.jar crimson.jar xalan.jar,这就是JAXP的核心了。 

  3、修改系统的CLASSPATH变量,在Windows平台下添加这样一行: 

   #JAXP11\jaxp.jar; #JAXP11\crimson.jar; #JAXP11\xalan.jar 

   在Unix/Linux系统下添加下面一行: 

   #JAXP11/jaxp.jar: #JAXP11/crimson.jar: #JAXP11/xalan.jar alan.jar  

如果你使用的是Java2的话,有一种更简单的方法,将上面的三个文件直接拷贝到JDK的Lib扩展目录中去,如#JAVA_HOME/jre/lib/ext(#JAVA_HOME代表JDK目录),这样就不用修改CLASSPATH了。 

  4、好了,安装完成了,下一步就是编写程序然后运行了。 

  二、 简单的XML例子 

  XML (EXtensible Markup Language )是一种类似HTML的语言,和HTML不同,XML主要用来描述结构化数据,通过XML格式我们可以很方便的在各种应用程序之间交换数据,而这些都是传统技术需要花费很大精力才能做到的。 

  让我们来看一个简单的XML文档例子,这个例子保存了部分个人档案,请将它保存为personal.xml文件,因为后面我们的Jsp文件还会调用它中间的数据。 

    

  <个人档案> 

  <姓名>刘玉锋  

  <性别>男  

  <年龄>24  

  coolknight@263.net  

  <个人主页>http://www.21jsp.com  

  <介绍>欢迎大家访问我的主页!  

   

是不是很类似于HTML文件,如HTML中的元素"你好"等等,因为XML和HTML一样都是标准SGML的子集合,所以有类似之处。但也有很多不同之处需要注意,比如XML文件必须有成对地标记而且大小写敏感,而这些在HTML中都是默认允许的。 

  第一行是必须的XML声明, 我们可以看到声明是在之间的,中间可以定义部分属性,version="1.0"表示文档将使用XML1.0的规范,encoding="gb2312"表示采用中文字符集,这样我们在下面对于数据就可以使用中文了。 

  接着就是<个人档案>标记了,这是XML文件中的根元素,也是不可缺少的,而且必须有一个对应的结束标记,在开始和结束标记之间我们就可以定义自己的数据描述了。 

  嵌套在<个人档案>标记如"<姓名>刘玉锋"就是具体的数据描述了,同根元素一样必须是成对的标记,在标记中间可以是标记对应具体的数值。这种表示方法有点类似于数据库中的记录了,字段名字为"姓名"、"性别"等,上面的XML文件就相当于一个只有一条记录的表"个人档案"。当然在XML文件中可以进行多层嵌套,但这就不在本文的讨论范围了。 

  当然,这里只是一个特别简单能代表XML的例子,XML的相关内容特别多,如果想更多了解XML的话建议还是看相关的书籍。 

  三、JSP和XML交互 

  前面已经说过了,Jsp通过SUN公司的API-JAXP可以实现和XML的交互,那么具体实现主要有两种方法,一种是采用DOM2 API,另外一种是采用SAX2 API。 

  在这里我们主要讨论JAXP中的SAX(Simple API for XML Parsing)技术,DOM2技术可以看SUN公司相关的文档。 

  1)关于SAX模型 

  SAX模型是一种处理XML文件的方法,它是事件驱动的,有些类似于AWT中的事件驱动机制,通过事件驱动来识别XML文档的内容。在API中关于SAX的主要有下面的几个包: 

  oorg.xml.sax 

  oorg.xml.sax.helpers 

  oorg.xml.sax.ext 

在前台的Java程序或者是Jsp程序中通过调用这几个包中的API就可以很好地实现Java和XML的交互。 

  2)关于HandlerBase接口 

  我们知道在AWT中一般是通过实施ActionListener等接口实现事件的处理的,同样的在SAX中SUN也提供了一个类似的接口HandlerBase来处理XML解析的功能,通过将HandlerBase和XML文件关联可以很好的来处理XML文件。 

  在实施接口中我们主要重载三个HandlerBase的方法startElement(String tag, AttributeList attrs)、characters(char[] ch, int start, int length)、endElement(String name)。 

  startElement()在读取一行XML数据的开始标记时候触发,子类必须覆盖这个方法,这样就可以在处理XML节点前先进行自己的处理(比如开始读取或者写入XML文件中的节点时候)。 

  public void startElement (String name, AttributeList attributes) 

   throws SAXException 

   { 

    // no op 

    } 

参数name代表XML节点名字,attributes代表默认或者特殊的属性,这个方法抛出一个违例org.xml.sax.SAXException。 

  characters()方法主要用来处理和之间具体的数据,在处理节点数据时候触发,我们可以覆盖这个方法来进行数据操作的处理,可以添加代码读取节点数据值或者是写入节点数据值。 

  public void characters (char ch[], int start, int length) 

   throws SAXException 

   { 

    // no op 

    } 

参数ch[]代表一个字符数组,start代表字符数组的开始位置,length代表要取的字符数组中ch[]中的元素个数,同样的这个方法抛出一个违例org.xml.sax.SAXException。 

  endElement()方法在处理节点元素结束的时候触发,也就是碰到标记的时候,我们可以覆盖这个方法来进行数据的收尾工作,比如将节点数据写入到文件中。 

  public void endElement (String name) 

   throws SAXException 

    { 

     // no op 

    } 

参数name代表XML节点名字,这个方法抛出一个违例org.xml.sax.SAXException 

  从上面我们也可以看出,三个方法在XML事件处理中的顺序依次为: 

   startElement()àcharacters()àendElement(String name) 

也许还不好理解,不要紧在下面我们将会写一个类myHandler类实现HandlerBase接口,并且覆盖这三个主要的方法来实现我们的XML文件读取操作。 

  3)关于哈希表  

  由于程序用到了哈希表,所以在这里先简单的介绍一下哈希表的基本语法以便大家能更好的理解下面的程序。 

  哈希表HashTable是从Dictionary派生出来的,里面具有一系列的关键字和数值,一个关键字对应一个数值,识别主要是通过对象的哈希代码hashCode识别。 

  我们程序中用到的方法如下: 

  put(Object key,Object value)添加一对关键字/数值到哈希表中 

  get(Object key)根据关键字得到它的值 

  keys()取得所有关键字并返回一个集合Enumeration 

此外,哈希表还具有其他很多有用的方法如长度size()、是否为空empty()、是否重复containsKey()等等,限于篇幅在这里就不进行介绍了。

http://www.ieee.org.cn/dispbbs.asp?boardID=41&ID=8381

19:06:04  |  固定链接 | 引用通告 (0) | 记录它 | 技術文章
jsp 生成靜態頁面的技術

在做项目的时候遇到了这个问题,好郁闷,我居然当时没法解决,无奈之下上csdn求助,要说我的命还不错,居然有50张帖子回复我的问题,虽然大部分朋友都没有很好的解决方法,但是却有一个高手给我提供了一个很好的思路,我现在把它完善为如下程序:

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class toHtml extends HttpServlet
{

    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
    String url="";
    String name="";
   
        ServletContext sc = getServletContext();
       
        String file_name=request.getParameter("file_name");//你要访问的jsp文件,如index.jsp
  //则你访问这个servlet时加参数.如http://localhost/toHtml?file_name=index

        url = "/"+file_name+".jsp";//这是你要生成HTML的jsp文件,如
                                   //http://localhost/index.jsp的执行结果.

        name="/home/resin/resin-2.1.6/doc/"+file_name+".htm";//这是生成的html文件名,如index.htm.
               
        RequestDispatcher rd = sc.getRequestDispatcher(url);
       
        final ByteArrayOutputStream os = new ByteArrayOutputStream();
       
        final ServletOutputStream stream = new ServletOutputStream()
        {
            public void write(byte[] data, int offset, int length)
            {
                os.write(data, offset, length);
            }

            public void write(int b) throws IOException
            {
                os.write(b);
            }
        };
       
        final PrintWriter pw = new PrintWriter(new OutputStreamWriter(os));
       
        HttpServletResponse rep = new HttpServletResponseWrapper(response)
        {
            public ServletOutputStream getOutputStream()
            {
                return stream;
            }
           
            public PrintWriter getWriter()
            {
                return pw;
            }
        };
        rd.include(request, rep);
        pw.flush();      
        FileOutputStream fos = new FileOutputStream(name); //把jsp输出的内容写到xxx.htm
        os.writeTo(fos);
        fos.close();
        PrintWriter out=response.getWriter();
        out.print("<p align=center><font size=3 color=red>首页已经成功生成!Andrew</font></p>");
    }
}

以上的程序我写好是运行在unix环境下的,如果有朋友在windows下想要使用,一定要注意修改文件目录的表示方法,注意windows环境下的特殊字符.