技术开发 频道

.NET开发人员的J2EE基础

  【IT168 技术文章】

        为何要转向 J2EE?

  如果您不是十分渴望冒险投入一种新的开发环境,可以考虑以下这些 J2EE 好处:

  选择,更多的选择:由于 J2EE 是一个定义良好的标准集,您在部署自己的代码时有许多 J2EE 实现可供选择。只要坚持使用标准 API 和避免厂商专用的扩展,应用程序无需编码变更就能在各种各样的 J2EE 实现上运行。

  我们是在说选择吗?:J2EE 实现在从大型机到 Wintel、UNIX 和 Linux 的各种平台上可用。您可以编写应用程序一次,然后在各种不同的平台上部署它。

  我们不能就安于现状吗?:J2EE 包括一个用于访问许多诸如 CICS、IMS、ERP 和 CRM 这样的遗留 EIS 系统的标准 API。它还包括 Web 服务支持,因此您可以集成 .NET 系统和支持行业 Web 服务标准的其他系统。J2EE 还包括标准消息 API 支持(Java Message Service,JMS),以及一个用于访问关系数据库的 API(Java Database Connectivity,JDBC)。这样广泛的选择允许您集成各种现有的系统,而不会损失您对它们的投资。

  机房不再烟雾缭绕:来自世界各地的专家通过 Java Community Process(JCP)开发 J2EE 规范。JCP 发布了初步的规范草案以供公众评论。即使您不主动参与,也总是会知道哪些未来的规范正在筹备之中。该规范还包括一个参考实现,您可以在决定实现它之前使用它来检查新技术。

  J2EE 简介

  J2EE、即 Java 2 Enterprise Edition,是一个由许多与使用 Java 语言开发分布式应用程序相关的组件组成的规范。您可以使用 J2EE 组件来编写基于 Web 的应用程序和传统的客户机-服务器应用程序,以及使用标准的 API 来连接到诸如关系数据库等遗留资源。如果您来自 ASP.NET 开发背景, Java Servlets和 JavaServer Pages(JSP)技术就是对您最有用的组件。

  Java Servlets

  Java Servlets 是作为诸如 IIS 或 Apache Web Server 等 Web 服务器的扩展来运行的 Java 类。Java Servlet 类似于 ISAPI 过滤器,即 ASP.NET HttpHandler 类,或类似于 cgi-bin 程序/脚本。Java Servlet 在客户端浏览器直接或间接地调用一个专门配置的 URL 时运行。servlet 能够访问 HTTP 请求中的所有信息,并且能够通过提供返回给客户端的内容来直接处理该请求。或者,servlet 能够将客户端浏览器重定向到另一个资源。大多数 J2EE Web 应用程序都主要把 servlet 用作 HTML 表单的目标以处理用户输入,然后作相应的处理。响应页面的生成通常委托给 JSP 页面。

  JavaServer Pages 技术

  JSP 页面类似于 ASP.NET 页面。也就是说,它们也是包含脚本元素的 HTML 页面,这些脚本在用户请求该页面时在服务器上运行。ASP.NET 页面和 JSP 页面之间的一个关键区别在于,ASP.NET 页面使用某种 .NET 语言(比如 C# 和 VB.NET)作为脚本语言,而 JSP 页面使用 Java 语言。典型的 JSP 页面包含 Java 代码片断和 JSP 规范中定义的一些特殊的类 HTML 标签,它们与标准 HTML 交织在一起以提供静态和动态内容的组合。Java Servlet 和 JSP 页面之间的区别在概念上类似于 ASP.NET HttpHandler 类和 ASP.NET 页面之间的区别。在两种情况下,前者都是可用于直接或间接地向其他资源发送 HTML 的一段代码,后者都是一个可以包含嵌入代码的 HTML 文档。

  Web 服务器和应用服务器

  如果熟悉 ASP.NET 页面,您应该知道运行这些页面的 ASP.NET 运行库――它配合 IIS 工作。您还会向 Web 应用程序添加自己的 HttpHandler 类以及托管的和非托管的组件,ASP.NET 运行库也会调用这些组件。这使得在 IIS 上部署 Web 应用程序很容易。但是它把您限制在 Windows 平台上,即唯一能够运行 IIS 的平台。J2EE 使用一种不同的方法,因为它的设计目标就是运行在各种不同的操作系统上(包括 Windows)。与尝试将运行 Java Servlet 和 JSP 页面的代码直接嵌入 Web 服务器不同,它使用一个称为 应用服务器的单独服务器组件来运行它们。大多数应用服务器(比如 IBM WebSphere Application Server)还有一个单独的插入组件,用于桥接应用服务器和特定的 Web 服务器。例如,WebSphere Application Server 附带了针对 IIS 和 Apache Web server 的单独插件。这样允许您在运行 J2EE 组件时使用自己选择的 Web 服务器。

  应用服务器作为单独的可插入服务器组件这种角色带来了多个优点:

  Web 服务器选择:您不会被限定使用某个 Web 服务器来提供 HTML 页面服务。您可以继续使用自己最喜欢的 Web 服务器来用于此目的,并且使用任何应用服务器来处理 Java Servlet 和 JSP 页面。这种能力在您将 ASP.NET 应用程序移植到 J2EE 时特别有用。您可以继续运行 IIS 和 ASP.NET 运行库,并且分阶段地移植应用程序。您不需要一下子改写整个应用程序。

  平台选择:你可以编写 J2EE 应用程序一次,然后在能够运行应用服务器的各种不同操作系统上部署它——包括 Windows、AIX 和 Linxu。您不会被限定于某个能够运行特定 Web 服务器的平台。

  应用服务器厂商选择:由于行业标准规范定义了 Java Servlets 和 JavaServer Pages 技术,您可以编写 J2EE 应用程序一次,然后在多种应用服务器环境中部署它,比如 WebSphere Express 或 Apache Tomcat,Apache Tomcat 是一个流行的开放源代码应用服务器。J2EE 还定义了必须如何打包 Web 应用程序,因此您可以将自己开发的应用程序引入某个 J2EE 环境,无需更改代码或重新编译应用程序,就能将它重新部署到另一个应用服务器中。将应用程序部署到多个平台也是如此。

  应用服务器如何运行 servlet 和 JSP 代码

  正如前面提到过的,J2EE 规范强制一种部署 Java Servlet 和其他 J2EE 组件的标准格式。一个称为 部署描述符的 XML 文档就是这种标准格式的一部分。部署描述符包含从每个 servlet 到用于调用特定 servlet 的 URL 的映射。应用服务器使用部署描述符中的信息来决定应该针对给定的请求调用哪个 servlet。

  应用服务器调用 JSP 页面的方式类似于 ASP.NET 运行库调用 ASP.NET 页面的方式。 J2EE 应用服务器将每个 JSP 页面转换为一个单独的特殊 servlet,它在该页面被请求时编译和运行。这个特殊的 servlet 保持加载在内存中,直至 JSP 文件改变。这样最大限度地降低了必须为每个 JSP 页面创建和编译一个类所导致的性能影响。

  模型-视图-控制器(MVC)体系结构

  J2EE 是在考虑到一个特定的应用程序结构的情况下开发的,这个结构称为 模型-视图-控制器(Model-View-Controller,MVC)。MVC 定义了三个应用程序层之间的清楚分离:

  模型:应用程序的数据和业务规则集——通常称为应用程序的业务逻辑。

  视图:应用程序的用户界面。

  控制器:定义了应用程序如何对用户输入或模型层的变化作出反应——通常称为应用逻辑。

  MVC 体系结构的优点

  J2EE 中没有任何东西强迫您使用 MVC 体系结构来组织应用程序,但是存在这样做的许多很好理由。通过定义三个层之间的清楚分离,MVC 允许构成每个层的组件之间的松散耦合。这样使得组件更加可复用和更灵活。例如,假设您的需求之一是支持某个 Web 应用程序中相同数据的不同类型的视图,因为不同的部门需要数据库中相同数据的不同子集。您需要开发特定于每个必需子集的新的视图组件。如果视图逻辑和数据库访问代码是紧密耦合的——ASP.NET 页面就是将数据库访问代码和 HTML 交织在一起,那么每个视图都要包含数据库访问代码。维护重复的代码不仅需要大量的工作,而且还可能导致出错。MVC 体系结构在这种场景中使用数据库代码作为该模型的一部分,而不同的视图组件可以复用它。

  J2EE 组件和 MVC

  图 1 显示了我们到目前为止所讨论的 J2EE 组件如何映射到一个 MVC 体系结构。注意模型和视图之间不存在任何联系。控制器的功能是充当两者之间的中转站。

  图 1. MVC 与 J2EE Web 应用程序

  在典型场景中,用户提交一个其目标是一个 servlet 的 HTML。servlet 分析输入,并使用模型中的类来调用业务逻辑以满足该请求。然后 servlet 将结果传递给一个 JSP 页面,以便向用户显示那些结果。

  JavaServer Faces

  您或许使用过 Web Forms 来开发 ASP.NET 应用程序的用户界面和 UI 控制逻辑。 JavaServer Faces(JSF)规范提供了 J2EE 编程世界中的等价功能。像 Web Forms 一样,JSF 提供运行库组件,这些组件允许工具厂商为基于 Web 的 UI 开发提供拖放功能。它还允许厂商开发可供他们的开发工具使用的自定义组件。

  要看到 JSF 的实际应用,可考察一下 WebSphere Studio 5.11 版中的系列工具(请参阅 参考资料)。WebSphere Studio 还有一个名为 Page Designer 的完全集成的工具,可以使用它通过拖放操作来可视化地开发 HTML 页面和 JSP 页面。Page Designer 已实现了 JavaServer Faces 规范,您在使用它时应该会看到一些熟悉的组件,比如 HTML 表单、DataGrids 和 DataList。您还会看到熟悉的“代码分离(code behind)文件(使用 Java 语言),它们包含特定页面的 UI 事件处理代码。

  JSF 环境的一个杰出特性在于,它允许您将页面分组为符合逻辑的树形结构。例如,如果有一组包含多个表单的页面,那么 JSF 树形结构就很理想——特别是在用户可能基于先前的输入或基于某些用户特征,通过一条不同的路径经历那些页面的情况下。该结构为树中的每个页面指定一个合理的名称。您在自己的事件处理代码中使用这些名称来导航不同的分支,具体取决于特定的运行时条件。

  其他 J2EE 技术

  Java Servlets 和 JSP 技术为您提供用 Java 语言开发平台无关的 Web 应用程序所需要的工具。其他一些 J2EE 规范和组件为您带来更高级的功能:

  Enterprise JavaBeans(EJB)技术:企业组件(或者说 bean)存在三种形式:

  会话 bean:类似于 .NET 中的 COM+ 服务的特殊 Java 类。像 COM+ 服务一样,会话 bean 在容器中运行,容器提供诸如声明事务管理、基于角色的安全、分布式环境中的无缝访问以及根据需要激活等服务。会话 bean 又存在两种形式:

  无状态的:方法调用之间没有维护状态,因此您必须提供通过参数来调用某个方法时所需要的全部信息。无状态会话 bean 的优点在于,容器可以使用任何实例来服务于任何客户机调用。

  有状态的:方法调用之间的状态得到保持,以便客户机总是与特定的实例相关联。有状态的会话 bean 的优点在于,客户机可以使用对话模式来与有状态的会话 bean 交互。当重新创建中间状态信息的成本很昂贵时,这是特别有用的。

  实体 bean:可看作是 ADO.NET DataSets 的更高级实现的特殊 Java 类。虽然概念上类似于 DataSets,但是实体 bean 的实现更像是 COM+ 服务。像 DataSets 一样,它们是存储在关系数据库或其他持久存储中的持久数据的对象表示,并且可以封装数据模型中的表之间的关系。像 COM+ 服务一样,它们在容器中运行,容器提供诸如声明事务管理、基于角色的安全、分布式环境中的无缝访问等服务。实体 bean 是共享对象,因此容器还要处理并发控制,并确保底层的持久数据保持其 ACID(Atomicity、Consistency、Isolation 和 Durability——原子性、一致性、隔离性和持久性)属性。(与会话 bean 不同,实体 bean 是共享对象,因此多个客户机可以并发地访问单个实例。)简而言之,实体 bean 防止您直接访问底层的持久存储。无需作出任何应用程序更改,就可以将它们部署到各种不同的持久存储中。(也就是说,无需改动任何代码,就可以在部署时将实体 bean 映射到它的持久存储。)

  消息驱动的 bean:充当 JMS 相容的消息中间件的监听器的特殊 Java 类。JMS 是用于访问消息队列的标准 Java API。可以将消息驱动的 bean 配置为指向特定的消息队列;容器会在消息到达该队列中时激活它们。消息驱动的 bean 给应用程序提供了在消息到达时调用的应用逻辑。每种 J2EE 1.3 相容的应用服务器都必须提供一个 JMS 实现,不过您也可以使用诸如 WebSphere MQ(以前名为 MQSeries)这样的流行消息中间件。

  Java 连接器体系结构(Java Connector Architecture,JCA):用于访问许多诸如 CICS、IMS、ERP 和 CRM 这样的遗留 EIS 系统的标准 API。JCA 把您解放出来,从此不必再学习针对每种 EIS 系统的单独 API。

  在深入某些 J2EE 编程概念之前,我们首先向您介绍 Java 编程语言。可以使用 Java 语言来编写服务器端应用程序以及具有 GUI 的桌面应用程序。本文假设您想要在服务器端使用 Java 语言来补充一个基于 Web 的界面,因此我们将跳过 CUI 编程环境,而是重点关注该平台的非可视化方面。我们首先介绍 Java 软件开发包(Java Software Development Kit,SDK),然后向您展示如何使用 Java 代码来编写历史悠久的 Hello World 应用程序。之后,我们将更深入地介绍两种最流行的 .NET 语言(Visual Basic .NET 和 C#)与 Java 语言之间的区别。感谢 Scott Stricker 对本节和下面两节所做的贡献,这些内容节选自他的教程“Java programming for C/C++ developers”。

  Java SDK 简介

  Java SDK 是编写和运行 Java 程序所需要的一组命令行工具和包。Java 程序通过即时(Just In Time,JIT)编译器编译为平台无关的字节代码,然后该字节代码可以在运行时编译为本机代码。其中最重要的工具是 Java 编译器(javac.exe)和 Java 解释器(java.exe),后者用于运行 Java 程序。该 SDK 还包括基础的类(称为 Java 平台),它们提供您开始编写应用程序所需要的基本功能和 API。

  Sun Microsystems 已发布了针对其每个主要 Java 平台版本的 SDK。我们推荐您获取最新的 SDK 版本(Java 1.4.2)来完成本教程的学习。Java SDK 是免费可用的。如果您还没有拥有它,请马上下载它。

  你可以在线参考 Java 2 Standard Edition(J2SE)API 文档。它是一个 HTML 文档集合,您可以在标准的 Web 浏览器中浏览它们。该 API 文档是必备的参考资料,您或许会频繁地使用它。

  安装 SDK

  在下载 SDK 之后,您需要将它安装到机器上。安装过程很简单。如果安装程序让您在典型安装和自定义安装之间的选择,请选择典型安装。(仅当您准确知道自己在做什么,并且不希望给机器增加负载时,才应该选择自定义安装。)安装过程通常提供安装标准 Java 平台类的源代码的选项。如果机器上有充足的磁盘空间,我们推荐您接受这个选项。这些文件将为您提供一个机会,让您考察组成 Java 语言和标准 API 的类的实现。它们设计和实现得特别好,您可以从中学到很多知识。

  在安装 SDK 之后,您可能需要配置它,以使它能在您的系统上工作。如何配置 SDK 取决于您的操作系统和所使用的 SDK 版本。该 SDK 包括完整的安装和配置说明。

  第一个 Java 程序

  现在您可以编写自己的第一个 Java 程序——无处不在的 Hello World 程序。打开文本编辑器,输入您从清单 1 中准确看到的源代码。

  清单 1. Hello World 程序

1 public class HelloWorld {
2    public static void main(String[] args) {
3       System.out.println("Hello World");
4    }
5 }
6
7

  Java 语言是大小写敏感的,诸如 class 和 public 这样的关键字总是小写的。可以使用任意大小写字母组合来表示变量和方法名称,只要您在整个给定的类中一致地使用它们。当您完成键入时,请把代码保存为一个名为 HelloWorld.java 的文件。对于这个文件名,您没有其他的选择。Java 源文件使用 .java 扩展名,并且每个 Java 源代码文件都 必须与您在其中定义的类具有完全相同的名称。我们已经多次重申了:大小写是很重要的,因此像 HELLOWORLD.JAVA 和 Helloworld.java 这样的文件名将导致编译错误。 您可以将 HelloWorld.java 保存在机器上任何适当的目录中。您需要转到这个目录来使用命令行工具,因此要确保该目录便于访问。

  编译程序

  现在就准备好可以编译 HelloWorld 程序了。SDK 附带的 Java 语言编译器是一个名为 javac.exe 的命令行应用程序。要编译一个 Java 源代码文件,您只需将 .java 文件的名称传递给 javac.exe 程序。要编译这个 HelloWorld 程序,请打开一个命令提示符窗口,将目录切换到您保存 HelloWorld.java 文件的位置。然后执行下面这个命令:

1 javac HelloWorld.java

  像 Visual Basic .NET 或 C# 编译器一样,Java 编译器可能生成任意数目的错误。自然地,您需要纠正所有错误,Java 编译器才会成功地编译 HelloWorld 程序。成功的编译将生成一个名为 HelloWorld.class 的类文件。这个文件代表您将在 Java 解释器中运行的可执行文件。

  运行程序

  SDK 附带的 Java 语言解释器是一个名为 java.exe 的命令行应用程序。要运行一个 Java 字节代码可执行文件,您只需将该 java 程序的名称传递给 java 解释器。在使用 Java 解释器时不要指定 .class 扩展名。解释器仅接受类文件,因此添加 .class 扩展名将产生一个错误。要运行这个 HelloWorld 程序,请打开一个命令提示符窗口,将目录切换到您编译 HelloWorld.java 文件的位置。这个字节代码可执行文件 HelloWorld.class 应该就在该目录中。然后执行下面这个命令:

1  java HelloWorld

  Java 解释器尝试执行 HelloWorld 程序的 main() 方法。Java 程序的 main() 方法等价于控制台应用程序使用的 Visual Basic .NET Sub Main() 或 C# Main() 方法。返回类型为 void 的 Java 方法等价于 Visual Basic .NET 中的 Sub 。具有其他返回类型的方法等价于 Visual Basic .NET 中的 Function 。

  Java 解释器可能会报告运行时错误,这通常会终止程序执行。与在 Visual Basic .NET 和 C# 中一样,Java 运行时错误要比编译时错误更难于调试,不过没有编译时错误出现得那么频繁。像 .NET 程序一样,Java 程序在一个托管环境中执行,因此您可以宽限地处理运行时错误。

  现在让我们考察一下 Java 语言与 Visual Basic .NET 之间的区别。

  类型

  Java 语言和 Visual Basic .NET 都是单继承的面向对象语言,它们具有一个作为其他所有类的基类的类:Visual Basic .NET 中的 System.Object 和 Java 语言中的 java.lang.Object 。这意味着对于您开发的类层次,两种语言是相似的。如果沿着层次树往上,您最终会到达对应的根类。

  Java 语言使用了原始类型(primitive type)的概念,它们非常类似 C 和 C++ 中的对应概念。它们不是任何类层次的一部分,也不具有任何方法。此外,当使用它们作为参数时,它们总是按值传递。表 1 列出了 Java 语言中的原始类型和它们在 Visual Basic .NET 中的等价类型:

  表 1 Java 语言中的原始类型和它们在 Visual Basic .NET 中的等价类型

Java 原始类型描述等价的 Visual Basic .NET 类型描述
int32 位有符号整数Integer32 位有符号整数
long64 位有符号整数long32 位有符号整数
short16 位有符号整数short16 位有符号整数
char16 位无符号整数Char16 位无符号整数
byte8 位无符号整数Byte8 位无符号整数
boolean有效值是 true或者 falseBoolean有效值为 true或者 false
float32 位浮点数Single32 位浮点数
double64 位浮点数Double64 位浮点数

  在 Java 语言中,每种原始类型具有一个对应的包装类,可以使用它将该类型作为对象而不是作为原始类型来处理。每个包装类具有一个构造函数,允许您根据原始类型中的数据创建该包装类型的一个实例。在 Visual Basic .NET 中,您可以隐式地将对应的类型转换为 Object 的一个实例,因此不需要在这种情形下使用包装类。清单 2 中的例子突出了它们之间的区别。

  清单 2. 原始类型和它们的包装类

1 Visual Basic .NET                      Java
2 Module Foo                                  public class Foo
3                                             {
4    Sub someMethod(ByRef arg  As Object)        private void someMethod(Object arg) {
5       ' do something with arg                     // do something with arg
6    end Sub                                     }
7    Sub Main()                                  public static void main(String[] args) {
8       Dim i = 0 As Integer                                int i=0;
9                                                   Foo x = new Foo();
10       someMethod(i);                              x.someMethod(new Integer(i));
11    End Sub                                     }
12 End Module                                  }
13

  清单 2 中的 Java 原始类型被显式地包装在 Object 的一个派生类中,而在 Visual Basic .NET 中,这种转换是隐式的。

  注意在 Java 语言中,原始类型是按值传递的。对象实例内部地使用指针来表示,这些指针也是按值传递的(也就是指针的值,而不是指针所指向的内容)。在 Visual Basic .NET 中,默认的行为是值类型按值传递,引用类型按引用传递。Java 语言中没有等价于 Visual Basic .NET ByRef 和 ByVal 的关键字。表 2 显示了等价于 Visual Basic .NET 类型但是没有映射到 Java 原始类型的 Java 语言类型。

  表 2 常用的 Java 系统类和它们在 Visual Basic .NET 中的等价类型

Java 类描述等价的 Visual Basic .NET 类描述
java.lang.Object任何非原始类型都是 Object 的派生类Object每种类型都是 Object 的派生类
java.lang.StringUnicode 字符StringUnicode 字符
java.math.BigDecimal可调整进位制的 32 位小数Decimal可调整进位制的 32 位小数
java.util.Date日期(排除时间部分)Date日期(排除时间部分)

  Java 语言与 Visual Basic .NET 不同,它没有结构和模块。所有 Java 代码都必须是某个类或接口的一部分。如果要移植包含模块和结构的 Visual Basic .NET 代码,您必须将它们改写为 Java 类。

  继承和接口

  两种语言都仅允许单继承,但是都允许实现多个接口。这在两种语言中的实现方式有所不同。例如,清单 3 显示了如何从一个名为 Parent 的类派生子类并实现两个分别名为 IOne 和 ITwo 的接口。

  清单 3 派生子类并实现接口

1 Visual Basic .NET            Java
2 Public Class Child               public class Child extends Parent implements IOne, ITwo
3    Inherits Parent               {
4    Implements IOne                  ...
5    Implements ITwo               }
6    ...
7 End Class
8

  End Class

  在 Java 语言中, extends 关键字表示继承, implements 关键字表示实现一个或多个接口,接口之间用逗号分隔。

  

  如果熟悉 Visual Basic .NET 中的名称空间,那么对 Java 语言中的包就不应该有任何概念问题。像名称空间一样,包允许您组织类以避免当您在不同上下文中使用相同名称的类时存在的名称冲突。名称空间的逻辑类分组促进了类复用,使得导航大量的类成员更加容易。在 Java 语言中,您需要通过两种方式处理这种分组:如何将类声明为特定包的成员,以及如何引用特定包中的类。清单 4 中的例子说明了名称空间和包的处理。

  清单 4. 名称空间和包

  

1 Visual Basic .NET                     Java
2   'Foo will be in this namespace           // Foo will be in this package
3 Namespace MyApp.Utilities                 package com.mycompany.myapp.utilities;
4 Public class Foo                          public class Foo
5                                            {
6    ...                                       ...
7 End Class                                 }
8 End Namespace
9   'using Foo in another class             // using Foo in another class
10 Imports MyApp.Utilities.Foo              import com.mycompany.myapp.utilities.Foo;
11 Public Class Foo2                        public class Foo2
12     ...                                   {
13                                              ...
14 End Class                                }
15

  在 Java 语言中,约定的做法是包名称全部使用小写,并使用反向的 Internet 域名作为每个包的前缀。上述清单使用 Java import 语句(类似 Visual Basic .NET Imports 语句)来引用 Foo2 中的 Foo 类,而没有使用完全限定的名称(Visual Basic .NET 中的 MyApp.Utilities.Foo 或 Java 代码中的 com.mycompany.myapp.utilities.Foo )。

  打包以供复用

  在 Visual Basic .NET 中,您可以编写一组类,把它们生成程序集(assembly),程序集在文件系统中表示为动态链接库。其他类可以引用这些程序集,以便使用它们所包含的类。Java 语言也允许把一组类打包到一个称为“Java 归档(Java Archive,JAR)文件”的文件中以供复用。您可以将一组类组合到一个带有 .jar 扩展名的文件中,然后在其他类中引用该 JAR 文件。具有 .jar 扩展名的文件是标准的 zip 文件,可以使用 WinZip 或其他压缩实用程序来操作它们。不过为方便起见,Java SDK 包含了一个名为 jar.exe 的实用程序(在 Windows 平台上),可以使用它来把一组类组合到一个具有 .jar 扩展名的文件中。

  在考察使用 jar.exe 实用程序的例子之前,理解包名称和 Java 平台用于生成类以及在运行时加载它们的目录结构之间的关系是很重要的。请考虑一个名为 Test 的类,它的源代码在一个名为 Test.java 的文件中。如果将 Test.java 定义为包 com.mycompany.test 的一部分,那么编译器会为结果类模块创建一个目录树。该目录树就建立在包名称的基础上。此例中该目录树为 com\mycompany\test,并且包名称中的点号被转换为目录边界(分隔符 \)。

  现在打开一个命令提示符窗口,然后创建一个目录(例如 c:\javapack)。切换到该目录( cd javapack )。使用您最喜欢的文本编辑器,将清单 5 中的代码添加到一个名为 Test.java 的新文件中。

  清单 5. 使用包的例子

1 package com.mycompany.test;
2 public class Test
3 {
4    public static void main(String[] args) {
5        System.out.println("In test");
6    }
7 }
8

  现在使用以下命令编译 Test.java。( -d 选项应该指向您为这个例子创建的目录):

1 java -d c:\javapack Test.java
2

  现在在 c:\javapack 目录下应该有一个名为 com 的子目录。 事实上,您可以看到编译所产生的 comTest.class 文件的完全限定名称是 Test.class。注意包名称( com.mycompany.test )是如何转换为对应目录结构(com\mycompany\test)的,该目录结构以您使用 -d 选项指定的目录作为根目录。

  下面我们将展示如何打包 Test.class 以方便其他类复用。从 c:\javapack 目录运行以下命令:

1 jar -cvf Test.jar com
2

  这个命令将创建一个名为 Test.jar 的文件,它包含 com 子目录下的所有类。

  运行以下命令来使用 Test.jar 文件中的类:

1  java -classpath Test.jar com.mycompany.test.Test
2

  注意您必须使用完全限定的类名称来从命令行运行该命令,而且还要注意使用 -classpath 选项来指向 Test.jar 文件的方式。或者,您可以把 Test.jar 文件添加到 CLASSPATH 环境变量中,该变量是分号分隔的 JAR 文件列表,以及 Java 编译器和 Java 虚拟机(Java virtual machine,JVM)用来寻找需要加载的类的目录列表。

  访问修饰符

  访问修饰符 public 、 private 和 protected 在两种语言中的工作方式大部分都是相同的。 在 Visual Basic .NET 中,默认的访问修饰符是 Friend 。在 Java 语言中,默认的访问权限是允许任何子类或相同包中的任何类访问当前类、字段或方法。这大致等价于 Visual Basic .NET 中的 Protected Friend 修饰符,该修饰符仅允许从相同程序集或从子类访问。

  方法重载

  Visual Basic .NET 中的子类如果要重载父内中的某个方法:

  该方法一定 不 能在父类中使用 private 访问修饰符来声明。

  该方法必须在父类中声明为 Overridable 。

  子类中的方法必须与父类中的对应方法具有相同的名称、返回类型和参数签名。

  子类中的方法必须使用 Overrides 关键字来声明。(您也可以使用 new 关键字,但是一般不推荐这样做。)

  父类中的方法一定 不 能声明为 NotOverridable 。

  Java 语言中方法重载的前提条件不太严格:

  该方法一定 不 能在父类中使用 private 访问修饰符来声明。

  子类中的方法必须与父类中的对应方法具有相同的名称、返回类型和参数签名。

  父类中的方法一定 不 能声明为 final 。

  这些区别的含义是,在 Java 代码中,子类中不可能包含与父类中的非私有方法具有相同名称和签名的方法而不会隐含地重载它。在 Visual Basic .NET 中,您必须显式地指明何时想要重载父类中的方法。还要注意,Java 语言中的 final 关键字或多或少地等价于 Visual Basic .NET 中的 NotOverridable 关键字。

  异常处理

  结构化的异常处理在两种语言中几乎完全相同。(两者都可以往后追溯到一份创始性的论文,即 Andrew Koenig 和 Bjarne Stroustrup 于 1990 年撰写的 Exception Handling for C++。) 两种语言都使用两种异常概念:应用程序生成的异常,以及系统运行库(Visual Basic .NET 的公共语言运行库,Java 语言的 JVM)生成的异常。 两种语言都具有 Exception 基类,应用程序异常和系统异常都是由它派生而来的。图 2 显示了每种语言中的 Exception 类层次。

  图2. Java 语言和 Visual Basic .NET 中的 Exception 类

  然而,两种语言的编译器对您的代码如何处理异常具有不同的预期。在 Visual Basic .NET 中,您可以选择捕获异常,或让它们沿调用堆栈向上传播到类的方法的调用者(和/或构造函数)。Java 语言允许同样的处理,但是对于未捕获的应用程序异常(也就是 java.lang.Exception 的子类),您必须显式地将它们作为方法声明的一部分来列出。因此 Java 编译器预期您或者自己捕获所有应用程序异常,或者告诉编译器关于未捕获的异常的信息。例如,假设 Foo 类的构造函数可能抛出一个应用程序异常,那么清单 6 中的 Visual Basic .NET 或 Java 代码对各自的编译器来说都不会有问题。

  清单 6. 处理应用程序异常 

1 Visual Basic .NET                      Java
2 ...                                         ...
3 Sub SomeMethod()                            public void someMethod() {
4    Try                                        try {
5     Dim Foo X As New Foo()                           Foo x = new Foo();
6                                               }
7    Catch e As ApplicationException            catch (Exception e)
8                                               {
9     ...                                          ...
10    End Try                                    }
11 End Sub                                     }
12

  然而,如果改变代码以使其不捕获异常,那么您就必须改变 Java 方法声明(如清单 7 所示)以避免编译器错误。

  清单 7. 未捕获的应用程序异常

1 Visual Basic .NET                     Java
2 ...                                        ...
3 Sub SomeMethod()                           public void someMethod() throws Exception {
4   Dim Foo X As New Foo();                      Foo x = new Foo();
5 End Sub                                    }
6

  两种语言在这方面存在的另一个区别在于,在 Visual Basic .NET 中,每个捕获块的参数是可选的。如果省略它,那么所有异常都会被捕获。Java 语言不允许这样,而是允许一个等价功能( java.lang.Throwable ),它捕获所有异常类的父类,如清单 8 所示。

  清单 8. 捕获所有异常

1 Visual Basic .NET                      Java
2 ...                                         ...
3 Sub SomeMethod()                            public void someMethod() {
4    Try                                        try {
5     Dim Foo X As New Foo()                           Foo x = new Foo();
6                                               }
7    Catch                                      catch (Throwable e)
8                                               {
9     ...                                          ...
10    End Try                                    }
11

  数组声明

  Java 语言提供两种声明数组的方法;Visual Basic .NET 仅提供了一种方法。Java 数组工作起来很像 Visual Basic .NET 中的动态数组:必须通过一个显式的步骤来给它分配内存。清单 9 中的代码说明了这个区别。与在 Visual Basic .NET 中一样,Java 数组的下标从 0 开始。

  清单 9. 声明数组

1 Visual Basic .NET                 Java
2 ...                                    ...
3 ' This is how an array is              // In Java the following
4 'declared in Visual Basic .NET         // are both allowed
5 Dim MyArray() As Integer               private int[] myArray;
6                                        // or
7                                        private int myArray[];
8 'Allocate some storage for the array   // Allocate some storage
9                                        // for the array
10 ReDim MyArray(30)                      myArray = new int[30];
11

  委托

  Java 语言没有直接等价于 Visual Basic .NET 委托的结构。可以通过声明并实现一个具有单个方法定义的接口来模拟委托功能。

  变量声明

  Java 语言是强类型的,控制变量声明的规则等价于打开 Option Explicit 选项后的 Visual Basic .NET 规则。也就是说,您必须在使用变量之前声明它们。Java 语言不允许改变这个性质。

  OnError GoTo

  Java 语言没有直接等价于 Visual Basic .NET 的 GoTo 语句的结构。不过,您可以使用异常处理机制相当容易地执行异常处理。

  构造函数

  像 Visual Basic .NET 一样,Java 类可以包含具有不同参数列表的不同构造函数。在 Visual Basic .NET 中,构造函数通过名为 New() 的 Sub 来声明。Java 语言构造函数的名称与类名称相同。清单 10 中的代码说明了这个区别。

  清单 10:构造函数

1 等价的 Visual Basic .NET 类型
2 Public Class Foo                                  public class Foo {
3    Private MyVar As Integer                          private int myVar;
4    Public Sub New(ByVal NewVal As Integer)           public Foo(int newVal) {
5       MyVar = NewVal                                    myVar = newVal;
6    End Sub                                           }
7    ...                                               ...
8 End Class                                         }
9

  属性(property)

  等价于 Visual Basic .NET 属性的 Java 结构称为 字段(field)。在 Java 语言中,您不能将 getter 和 setter 定义为字段定义的一部分,但是可以向类中声明那些字段的地方添加 getter 和 setter。清单 11 中的代码说明了这个区别。

  清单 11:属性

1 Visual Basic .NET                      Java
2 Public Class Foo                            public class Foo {
3    Private MyPropVal As String                  private String myProp;
4    Public Property MyProp() As String           public String getMyProp() {
5       Get                                          return myProp;
6          Return MyPropVal                       }
7       End Get
8       Set(ByVal NewValue As String)             public void setMyProp(String newValue) {
9          MyPropVal = NewValue                      myProp = newValue;
10       End Set                                   }
11    End Property
12    ...                                           ...
13 End Class                                   }
14

  小结

  Visual Basic .NET 的语法类似于以前的 Visual Basic 版本,但是最新的版本包括了许多可从 Java 语言中找到的面向对象特性。继承、接口和异常处理就是两种语言的实现存在相似性的一些方面。与以前版本的 Visual Basic 相比,这种相似性应该使您转向 Java 平台更加容易。我们提倡您首先把 Visual Basic .NET 小程序转换到 Java 语言。不要忘了使用 Java 平台文档,要查找功能上等价于 System... 名称空间中的 Visual Basic .NET 类的 Java 类,您会发现这些文档非常有用

  下面让我们考察 Java 语言和 C# 之间的区别。这两种语言具有许多相似之处,因此我们将重点关注其区别所在。

  类型

  Java 语言和 C# 都是单继承的面向对象语言,它们都具有一个作为其他所有类的基类的类:C# 中的 System.object 和 Java 语言中的 java.lang.Object 。这意味着对于您开发的类层次,两种语言是相似的。如果沿着层次树往上,您最终会到达对应的根类。

  Java 语言使用了原始类型(primitive type)的概念,它们非常类似 C 和 C++ 中的对应概念。它们不是任何类层次的一部分,也不具有任何方法。此外,当使用它们作为参数时,它们总是按值传递。表 3 列出了 Java 语言中的原始类型和它们在 C# 中的等价类型:

  表 3 Java 语言中的原始类型和它们在 C# 中的等价类型

Java 原始类型描述等价的 C# 类型描述
int32 位有符号整数int32 位有符号整数
long64 位有符号整数long32 位有符号整数
short16 位有符号整数short16 位有符号整数
char16 位无符号整数char16 位无符号整数
byte8 位无符号整数byte8 位无符号整数
boolean有效值是 true或者 falsebool有效值是 true或者 false
float32 位浮点数float32 位浮点数
double64 位浮点数double64 位浮点数
  在 Java 语言中,每种原始类型具有一个对应的包装类,可以使用它将该类型作为对象而不是作为原始类型来处理。每个包装类具有一个构造函数,允许您根据原始类型中的数据创建该包装类型的一个实例。 在 C# 中,您可以隐式地将对应的类型转换为 object 的一个实例,因此不需要在这种情形下使用包装类。清单 12 中的代码突出了它们之间的区别。

  清单 12. 原始类型和它们的包装类的例子

1 C#                                         Java
2 public class Foo                                 public class Foo
3 {                                                {
4   private void someMethod(object arg) {            private void someMethod(Object arg) {
5      // do something with arg                          // do something with arg
6   }                                                }
7   public static void Main(string[] args) {         public static void main(String[] args) {
8      int i = 0;                                               int i=0;
9      Foo x = new Foo();                               Foo x = new Foo();
10      x.someMethod(i);                                 x.someMethod(new Integer(i));
11   }                                                }
12 }                                                 }
13

  清单 12 中的 Java 原始类型被显式地包装在 Object 的一个派生类中,而在 C# 中,这种转换是隐式的。(C# 中隐式的转换称为 装箱(boxing)。)

  注意在 Java 语言中,原始类型是按值传递的,对象类型在内部使用指针来表示,它们也是按值传递的。C# 中的默认行为是相同的,不过该语言还包括 ref 关键字来强制将值类型作为引用来传递。

  表 4 显示了一些等价于 C# 类型但是没有映射到 Java 原始类型的 Java 语言类型。

  表 4 C# 类型和它们的 Java 等价类型

1 等价的 Java 类型  描述  C# 类型  描述  
2 java.lang.Object  任何非原始类型都是 Object 的派生类 object  每种类型都是 object 的派生类
3 java.lang.String  Unicode 字符 string  Unicode 字符
4 java.math.BigDecimal  可调整进位制的 32 位小数 decimal  可调整进位制的 32 位小数
5

 

  继承和接口

  两种语言都仅允许单继承,但是都允许实现多个接口。这在两种语言中的实现方式有所不同。例如,清单 13 显示了如何从一个名为 Parent 的类派生子类,并实现两个分别名为 IOne 和 ITwo 的接口。

  清单 13 派生子类并实现接口

1 C#                                   Java
2 public class Child : Parent, IOne, ITwo   public class Child extends Parent implements IOne, ITwo
3 {                                         {
4    ...                                       ...
5 }                                          }
6
7
8

  注意在 Java 语言中, extends 关键字表示继承, implements 关键字表示一个或多个接口的实现。

  

  如果熟悉 C# 中的名称空间,那么对 Java 语言中的包就不应该有任何概念问题。像名称空间一样,包允许您组织类以避免当您在不同上下文中使用相同名称的类时存在的名称冲突。名称空间的逻辑类分组促进了类复用,使得导航大量的类成员更加容易。在 Java 语言中,您需要通过两种方式处理这种分组:如何将类声明为特定包的成员,以及如何引用特定包中的类。清单 14 中的例子说明了名称空间和包的处理。

  清单 14. 名称空间和包

  

1 C#                                  Java
2 // Foo will be in this namespace         // Foo will be in this package
3 namespace MyApp.Utilities                package com.mycompany.myapp.utilities;
4 public class Foo                         public class Foo
5 {                                        {
6   ...                                      ...
7 }                                        }
8 // using Foo in another class            // using Foo in another class
9 using MyApp.Utilities.Foo;               import com.mycompany.myapp.utilities.Foo;
10 public class Foo2                        public class Foo2
11 {                                        {
12    ...                                      ...
13 }                                        }
14

  注意在 Java 语言中,约定的做法是包名称全部使用小写,并使用反向的 Internet 域名作为每个包的前缀。上述清单使用 Java import 语句(类似 C# using 语句)来引用 Foo2 中的 Foo 类,而没有使用完全限定的名称(C# 中的 MyApp.Utilities.Foo 或 Java 语言中的 com.mycompany.myapp.utilities.Foo )。

  打包以供复用

  在 C# 中,您可以编写一组类,把它们生成程序集(assembly),程序集在文件系统中表示为动态链接库。其他类可以引用这些程序集,以便使用它们所包含的类。Java 语言也允许把一组类打包到一个称为 Java 归档(Java Archive,JAR)文件的文件中以供复用。您可以将一组类组合到一个带有 .jar 扩展名的文件中,然后在其他类中引用该 JAR 文件。具有 .jar 扩展名的文件是标准的 zip 文件,可以使用 WinZip 或其他压缩实用程序来操作它们。不过为方便起见,Java SDK 包含了一个名为 jar.exe 的实用程序(在 Windows 平台上),可以使用它来把一组类组合到一个具有 .jar 扩展名的文件中。

  在考察使用 jar.exe 实用程序的例子之前,理解包名称和 Java 平台用于生成类以及在运行时加载它们的目录结构之间的关系是很重要的。请考虑一个名为 Test 的类,它的源代码在一个名为 Test.java 的文件中。如果将 Test.java 定义为包 com.mycompany.test 的一部分,那么编译器会为结果类模块创建一个目录树。该目录树就建立在包名称的基础上。此例中该目录树为 com\mycompany\test,并且包名称中的点号被转换为目录边界(分隔符 \)。

  现在打开一个命令提示符窗口,然后创建一个目录(例如 c:\javapack)。切换到该目录( cd javapack )。使用您最喜欢的文本编辑器,将清单 15 中的代码添加到一个名为 Test.java 的新文件中。

  清单 15. 使用包的例子

1 package com.mycompany.test;
2 public class Test
3 {
4    public static void main(String[] args) {
5        System.out.println("In test");
6    }
7 }
8

 

  使用以下命令编译 Test.java。(注意 -d 选项应该指向您为这个例子创建的目录):

1 java -d c:\javapack Test.java
2

  现在 c:\javapack 目录下应该有一个名为 com 的子目录。事实上,您可以看到编译所产生的 comTest.class 文件的完全限定名称是 Test.class。 注意包名称( com.mycompany.test )是如何转换为对应目录结构(com\mycompany\test)的,该目录结构以您使用 -d 选项指定的目录作为根目录。

  下面我们将展示如何打包 Test.class 以方便其他类复用。从 c:\javapack 目录运行以下命令:

1 jar -cvf Test.jar com
2

  jar 命令将创建一个名为 Test.jar 的文件,它包含 com 子目录下的所有类。

  执行以下命令来使用 Test.jar 文件中的类:

1 java -classpath Test.jar com.mycompany.test.Test
2

  注意您必须使用完全限定的类名称来从命令行运行该命令,而且还要注意使用 -classpath 选项来指向 Test.jar 文件的方式。或者,您可以把 Test.jar 文件添加到 CLASSPATH 环境变量中,该变量是分号分隔的 JAR 文件列表,以及 Java 编译器和 Java 虚拟机(Java virtual machine,JVM)用来寻找需要加载的类的目录列表。

  访问修饰符

  访问修饰符 public 、 private 和 protected 在两种语言中的工作方式大部分都是相同的。在 C# 中,默认的访问权限是 private ;Java 语言中的默认行为是允许任何子类或相同包中的任何类访问当前类、字段或方法。这大致等价于 C# 中的 internal 关键字,该修饰符仅允许从相同程序集访问。

  方法重载

  C# 中的子类如果要重载父类中的某个方法:

  该方法一定 不 能在父类中使用 private 访问修饰符(或没有访问修饰符)来声明。

  该方法必须在父类中声明为 virtual 。

  子类中的重载方法必须与父类中的方法具有相同的名称、返回类型和参数签名。

  子类中的方法必须使用 Overrides 关键字来声明。(您也可以使用 new 关键字,但是一般不推荐这样做。)

  父类中的方法一定 不 能声明为 sealed 。

  Java 语言中方法重载的前提条件不太严格:

  该方法一定 不 能在父类中使用 private 访问修饰符来声明。

  子类中的方法必须与父类中的对应方法具有相同的名称、返回类型和参数签名。

  父类中的方法一定 不 能声明为 final 。

  这些区别的含义是,在 Java 代码中,子类中不可能包含与父类中的非私有方法具有相同名称和签名的方法而不会隐式地重载它。在 C# 中,您必须显式地指明何时想要重载父类中的方法。还要注意 Java 语言中的 final 关键字如何或多或少地等价于 C# 中的 sealed 关键字。

  异常处理

  结构化的异常处理在两种语言中几乎完全相同。(两者都可以往后追溯到一份创始性的论文,即 Andrew Koenig 和 Bjarne Stroustrup 于 1990 年撰写的 Exception Handling for C++。) 两种语言都使用了两种异常概念:应用程序生成的异常,以及系统运行库(C# 的公共语言运行库,Java 语言的 JVM)生成的异常。两种语言都具有 Exception 基类,应用程序异常和系统异常都是由它派生而来的。 图 3 显示了两种语言中的 Exception 类层次。

  图3. Java 语言和 C# 中的 Exception 类层次

  然而,两种语言的编译器对您的代码如何处理异常具有不同的预期。在 C# 中,您可以选择捕获异常,或让它们沿调用堆栈向上传播到类的方法的调用者(和/或构造函数)。Java 语言允许同样的处理,但是对于未捕获的应用程序异常(也就是 java.lang.Exception 的子类),您必须显式地将它们作为方法声明的一部分来列出。因此 Java 编译器预期您或者自己捕获所有应用程序异常,或者告诉编译器关于未捕捉的异常的信息。例如,假设 Foo 类的构造函数可能抛出一个应用程序异常,那么清单 16 中的 C# 或 Java 代码对各自的编译器来说都不会有问题。

  清单 16. 处理应用程序异常

  

1 C#                                  Java
2 ...                                      ...
3 public void someMethod() {               public void someMethod() {
4    try {                                    try {
5     Foo x = new Foo();                         Foo x = new Foo();
6    }                                        }
7    catch (ApplicationException e)           catch (Exception e)
8    {                                        {
9     ...                                        ...
10    }                                        }
11 }                                        }
12

  然而,如果改变代码以使其不捕获异常,那么您就必须改变 Java 方法声明(如清单 17 所示)以避免编译器错误。

  清单 17. 未捕获的应用程序异常

1 C#                               Java
2 ...                                  ...
3 public void someMethod() {           public void someMethod() throws Exception {
4     Foo x = new Foo();                     Foo x = new Foo();
5 }                                    }
6

  两种语言在这方面存在的另一个区别在于,在 C# 中,每个捕获块的参数是可选的。如果省略它,那么所有异常都会被捕获。Java 语言不允许这样,而是允许一个等价功能( java.lang.Throwable ),它捕获所有异常类的父类,如清单 18 所示。

  清单 18. 捕获所有异常

1 C#                                   Java
2 ...                                       ...
3 public void someMethod() {                public void someMethod() {
4    try {                                     try {
5     Foo x = new Foo();                          Foo x = new Foo();
6    }                                         }
7    catch                                     catch (Throwable e)
8    {                                         {
9     ...                                         ...
10    }                                         }
11 }                                         }
12

  数组声明

  Java 语言提供两种声明数组的方法;C# 仅提供了一种方法。清单 19 中的代码说明了这个区别。

  清单 19. 声明数组

1 C#                                       Java
2 ...                                           ...
3 // This is how an array is declared in C#     // In Java the following are both allowed
4 private int[] myArray;                        private int[] myArray;
5                                               // or
6                                               private int myArray[];
7

  委托和索引器(indexer)

  Java 语言没有直接等价于 C# 委托的结构。您可以通过声明并实现一个具有单个方法定义的接口来模拟委托功能。

  Java 语言也没有索引器;您需要将它们编写为常规的类方法。

  操作符重载

  Java 语言不允许操作符重载(这是一个从 C++ 借用来的 C# 特性)。您可以容易地编写方法来模拟操作符重载行为。

  非安全模式

  C# 的非安全模式允许您使用指针和内存插接块(pin bolck)来绕过垃圾收集。Java 运行库本身广泛使用了指针,但是 Java 语言没有指针,也没有等价的非安全模式。这样是为了遵循 Java 平台的“编写一次,随处运行”的哲学,它允许你安全地避免平台依赖性、内存泄露以及“失控(runaway)”代码。

  小结

  您的 C# 背景应该使得转向 Java 平台相当容易。本文中手把手的代码示例或许会让您认识到:这两种语言使用了相当类似的语法。它们在概念上也相当相似。继承、接口和异常处理就是这两种语言的实现几乎完全相同的一些方面。我们提倡您首先把一些 C# 小程序转换到 Java 语言。不要忘了使用 Java 平台文档,要查找功能上等价于 System... 名称空间中的 C# 类的 Java 类,您会发现这些文档非常有用。

  要理解 J2EE 应用程序体系结构的基本概念,第一步是确定如何将现有的 ASP.NET 应用程序移植到一个基于 J2EE 的模型,或者确定如何从头编写一个 J2EE 应用程序。我们将考察几个 ASP.NET 模型,以及可能如何将它们转换为根据 J2EE 组件构建的模型。您将看到简单的“意大利面条式的代码”如何演进为一个更优雅、可复用和可扩展的环境。在研究一些代码例子之前,让我们首先进一步考察一下 Web 应用程序中使用得最多的两个 J2EE 组件:Java Servlets 和 JavaServer Pages 技术。

  Java Servlets 编程:基础

  编写 Java Servlets 是为了以编程方式控制来自浏览器的 URL 请求。典型的 servlet 调用类似如下:

  客户端向 Web 服务器发出请求,同时指定一个 servlet 作为 URL 的一部分——例如:

     <FORM action="/myWebApp/LoginServlet" method="POST">

  Web 服务器将该请求转发给应用服务器,后者查找 servlet 类实例的位置。

  然后 Web 容器调用该 servlet。(该 servlet 的单个实例将加载到内存中,每个请求在不同的线程中调用该单个实例。)

  注意 HTML 表单中 servlet 的 URL包括该 servlet 的名称和一个称为 上下文根(context root) 的前缀(在上面的例子中是 /myWebApp )。上下文根大致等价于一个 IIS 虚拟目录。

  图 4 描述了这些步骤。

  图 4. Servlet 调用

  Servlet 扩展了 HttpServlet 类。您可以根据需要重载 HttpServlet 的以下方法:

  init() :在应用服务器加载某个 servlet 时调用

  destroy() :在应用服务器卸载某个 servlet 时调用

  doGet() :在 servlet 通过 HTTP GET 被调用时调用

  doPost() :在 servlet 通过 HTTP POST 被调用时调用

  Servlet 的编写涉及编写代码来处理 HTTP 请求、处理任何参数,以及直接返回 HTML 内容或委托其他资源(比如 JSP 页面)来处理响应。不推荐从 servlet 直接返回 HTML 内容,因为在 Java 类中管理 HTML 代码很麻烦。

  当应用服务器调用 doGet() 或 doPost() 方法时,将有两个对象作为参数被传递:

  HttpServletRequest 允许访问任何请求参数和导致产生该 servlet 调用的 HTTP 请求中的其他信息。

  HttpServletResponse 充当返回客户端的通信渠道,允许 servlet 直接返回内容或返回其他 HTTP 返回代码(错误、重定向,等等)。

  清单 20 和 21 显示了两个 Hello World servlet。清单 20 直接返回内容,而清单 21 使用 JSP 页面来返回内容。

  清单 20. HelloWorld servlet: 直接返回内容

1  public class HelloWorldServlet extends HttpServlet {
2
3   /**
4
5   * Handles all HTTP POST requests
6
7   *
8
9   * @param request Object that encapsulates the request to the servlet
10
11   * @param response Object that encapsulates the response from the servlet
12
13   */
14
15   public void doPost(
16
17   javax.servlet.http.HttpServletRequest request,
18
19   javax.servlet.http.HttpServletResponse response)
20
21   throws ServletException, IOException {
22
23   try {
24
25   PrintWriter out = response.getWriter();
26
27   out.println("");
28
29   out.println("Hello World");
30
31   out.println("");
32
33   } catch (Throwable t) {
34
35   ...
36
37   }
38
39   }
40
41   }
42
43

  注意您从响应对象中得到了一个 PrintWriter ,并将 HTML 一次一行地输出到客户端。

  清单 21. HelloWorld servlet: 使用 JSP 页面来返回内容

1 public class HelloWorldServlet extends HttpServlet {
2
3   /**
4
5   * Handles all HTTP POST requests
6
7   *
8
9   * @param request Object that encapsulates the request to the servlet
10
11   * @param response Object that encapsulates the response from the servlet
12
13   */
14
15   public void doPost(
16
17   javax.servlet.http.HttpServletRequest request,
18
19   javax.servlet.http.HttpServletResponse response)
20
21   throws ServletException, IOException {
22
23   try {
24
25   RequestDispatcher rd = getServletContext().getRequestDispatcher("helloworld.jsp");
26
27   rd.forward(request, response);
28
29   } catch (Throwable t) {
30
31   ...
32
33   }
34
35   }
36
37   }
38
39

  RequestDispatcher 是您想要向其转发请求的资源的包装器。注意要同时包括原始请求和响应对象,以便目标资源能够访问它们。从 getServletContext() 返回的 ServletContext 允许 servlet 与底层应用服务器通信,以获得一个 RequestDispatcher 。 注意所有 servlet 都能够通过 getServletContext() 方法访问它们的 ServletContext 。

  使用 JavaServer Pages 技术来编程:基础

  JSP 技术使您能够使用 Java 语言进行服务器端编程。JSP 页面是包含 HTML 和 Java 代码的复合页面,其中的 Java 代码由应用服务器在将页面返回客户端之前处理。应用服务器处理该嵌入代码,以便在将页面返回客户端之前生成静态内容。像 .aspx 文件一样,JSP 文件通常看起来像具有一些附加标签和 Java 代码片断的 HTML。

  J2EE 应用服务器在 JSP 页面第一次被请求时,将每个JSP 页面转换为一个特殊的 servlet。该 servlet 将编译并加载到内存中。只要该 JSP 源代码没有修改,它就一直为针对该页面的请求提供服务。当源代码修改时,该过程又重来一次,这样就产生了一个新版本的 servlet。

  可以在 JSP 页面中使用几个特殊的 JSP 标签,用户还可以定义他们自己开发的标签的行为。这些自定义的标签大致等价于 ASP.NET 中的自定义组件。还可以向 JSP 页面的不同部分添加一些 Java 代码块。J2EE 运行时环境使得许多变量(称为 隐含变量)对您的这些 Java 代码片断可用。隐含变量的例子包括:

  request:与页面的特定调用相关联的 HttpServletRequest

  response:与页面的特定调用相关联的 HttpServletResponse

  out:与 HttpServletResponse 相关联的 PrintWriter

  清单 22 显示了 JSP 页面的一个例子,其中包含 HTML 和 Java 代码的混合。 标签之间的 Java 代码部分称为 scriptlet。

  清单 22. HelloWorld servlet:使用 JSP 页面来返回内容

1 <html>
2 <title>JSP page example</title>
3 The date is:
4 <%
5 Date date = new Date();
6 out.println(date);
7 %>
8 Some more HTML
9 The value of <em>e</em> is:
10 <%
11 double e = Math.exp(1.0);
12 out.println(e);
13 %>
14 Yet even  more HTML
15 The value of PI is:
16 <%
17 double pi = 22.0/7.0;
18 out.println(pi);
19 %>
20 </html>
21

  注意其中使用了 JSP 隐含变量 out 来将内容写回客户端,还要注意 HTML 和 Java 代码的交织。

  一个例子场景

  为了说明各种体系结构选项,我们将使用一个简单的用户登录场景,它包含:

  一个具有 HTML 表单的登录页面,它从用户那里收集用户名和密码

  验证用户凭据(可能使用数据库)并重定向站点主页的应用逻辑

  这个简单的例子允许我们描述各种各样的应用体系结构。

  “意大利面条式”代码

  在 ASP.NET 方,“意大利面条式”代码方法使用单个 .aspx 文件来同时包含应用逻辑和 HTML 表单。不存在代码分离(code-behind)。(不推荐对现实中的例子采用这种方法,因为这样所有表示逻辑和应用逻辑都将是单个文件,从而阻止了您复用用于验证用户凭据的代码。)该代码的轮廓看起来类似清单 23 中所示的代码。

  清单 23. ASP.NET 中的“意大利面条式”代码

1 <html>
2 <head>
3 <title>Login example</title>
4 <script language="C#" runat=server>
5 ...
6 private void btnLogin_Click(object sender, System.EventArgs e)
7 {
8     // Get the form field values
9     ...
10     // Validate the username and password
11     if (ValidateUser(username, password) {
12       Response.Redirect("mainpage.aspx");
13     }
14 }
15 private bool ValidateUser(string userName, string password) {
16       ...
17 }
18 ...
19 </script>
20 </head>
21 <body>
22     <form runat="server">
23     <!-- login form fields go here -->
24     </form>
25 </body>
26 </html>
27

  正如清单 24 所示,您可以在 J2EE 中采用相同的方法,使用单个 JSP 页面来同时包含表单和应用逻辑。

  清单 24. J2EE 中的“意大利面条式”代码

1 <html>
2 <head>
3 <title>Login example</title>
4 <%!
5 private boolean validateUser(String userName, String password) {
6       ...
7 }
8 %>
9 ...
10 </head>
11 <body>
12 <%
13     if (request.getMethod().equals("GET") ) { %>
14     <form method="POST" target="login.jsp">
15     <!-- login form fields go here -->
16     </form>  <% }
17     else  {
18          String userName = request.getParameter("username");
19          String password = request.getParameter("password");
20           if (validateUser(userName, password)) {
21              response.sendRedirect("mainpage.jsp");
22          }
23          ...
24    } %>
25 </body>
26 </html>
27

  JSP 模型不是事件驱动的,因此您需要检查表单是否被发送回去了,方法是检查该请求,并在它不是 POST 请求时包括表单的 HTML。如果它是 POST 请求,您将使用 JSP 中声明的一个方法来验证登录。注意 )对方法无效。还要注意如何使用 if/then/else 编程结构来容易地包括或排斥较大的 HTML 块。与在 ASP.NET 例子中一样,不推荐将此方法用于 J2EE 开发。表示代码(HTML)和应用逻辑的混合仅允许很少的复用,并且使得代码难于维护。

  改进的“意大利面条式”代码

  在 ASP.NET 方,一种更好的方法建立在前一个例子基础上,不过除了 .aspx 文件外,它还使用了代码分离文件。事件处理代码和用户验证代码转移到了一个代码分离文件中,原先的 .aspx 文件只剩下 HTML 表单和其他 HTML 元素。这相对于前一种方法来说当然是一种进步;表示代码更清楚地分离了,这样可以让一个 HTML 设计师负责表示,让一个程序员负责代码分离文件。

  如果使用标准 J2EE 组件,您就无法使用 ASP.NET 的事件驱动的代码分离文件方法。J2EE 端的一种更好方法是将应用逻辑转移到一个 Java Servlet,从而使 JSP 页面仅限于使用 HTML 组件。就像 ASP.NET 中的代码分离文件方法相对于“意大利面条式”代码是一种改进一样,这同样也是一种改进。清单 25 显示了如何通过把应用逻辑放到 servlet 中来简化 JSP 页面。

  清单 25. J2EE 中改进的“意大利面条式”代码 JSP page

1 <html>
2 <head>
3 <title>Login example</title>
4 ...
5 </head>
6 <body>
7 <form method="POST" target="LoginServlet">
8 <!-- login form fields go here -->
9 </form>
10 </body>
11 </html>
12

  清单 26 显示了 servlet 中的验证代码和导航逻辑。

  清单 26. J2EE 中改进的“意大利面条式”代码 Java Servlet

1 public class LoginServlet extends HttpServlet {
2
3   /**
4
5   * Handles all HTTP POST requests
6
7   *
8
9   * @param request Object that encapsulates the request to the servlet
10
11   * @param response Object that encapsulates the response from the servlet
12
13   */
14
15   public void doPost(
16
17   javax.servlet.http.HttpServletRequest request,
18
19   javax.servlet.http.HttpServletResponse response)
20
21   throws ServletException, IOException {
22
23   try {
24
25   String userName = request.getParameter("username");
26
27   String password = request.getParameter("password");
28
29   if (validateUser(userName, password)) {
30
31   response.sendRedirect("mainpage.jsp");
32
33   }
34
35   ...
36
37   } catch (Throwable t) {
38
39   ...
40
41   }
42
43   }
44
45   private boolean validateUser(String userName, String password) {
46
47   ...
48
49   }
50
51   }
52
53

  清单 26 中的 servlet 是表单提交的目标,并且充当一个控制器――处理用户输入并基于该输入调出适当的页面。注意 HttpServlet 父类允许您通过提供可重载的方法( doGet() 和 doPost() ),同时处理 GET 和 POST 请求。

  这种方法的主要缺点在于,凭据验证代码(它很可能要访问数据库)对 ASP.NET 例子来说是代码分离文件的一部分,对 J2EE 例子来说是 servlet 的一部分。如果不同的页面需要使用这个逻辑,您就必须重复它。重复的代码更难于维护和更易于出错,因为您必须跟踪多个副本。

  模型-视图-控制器(MVC)方法

  下面我们将展示如何对这个例子使用更纯的 MVC 方法。在 ASP.NET 端,这体现为将凭据验证逻辑转移到一个单独的类库中,然后您可以在代码分离文件中访问该类库。在 J2EE 端,凭据验证代码将转移到一个单独的类中,然后从 servlet 访问该类。清单 27 中的代码片断显示了该 servlet 看起来的样子。

  清单 27. J2EE 中的 MVC Java Servlet

1 1 import com.ibm.businessobjects.UserValidation
2 2
3 3   public class LoginServlet extends HttpServlet {
4 4
5 5   /**
6 6
7 7   * Handles all HTTP POST requests
8 8
9 9   *
10 10
11 11   * @param request Object that encapsulates the request to the servlet
12 12
13 13   * @param response Object that encapsulates the response from the servlet
14 14
15 15   */
16 16
17 17   public void doPost(
18 18
19 19   javax.servlet.http.HttpServletRequest request,
20 20
21 21   javax.servlet.http.HttpServletResponse response)
22 22
23 23   throws ServletException, IOException {
24 24
25 25   try {
26 26
27 27   String userName = request.getParameter("username");
28 28
29 29   String password = request.getParameter("password");
30 30
31 31   UserValidation user = new UserValidation();
32 32
33 33   if (user.validate(userName, password)) {
34 34
35 35   response.sendRedirect("mainpage.jsp");
36 36
37 37   }
38 38
39 39   ...
40 40
41 41   } catch (Throwable t) {
42 42
43 43   ...
44 44
45 45   }
46 46
47 47   }
48 48
49 49   }
50 50
51 51

  清单 28 显示了在一个单独的类中的凭据验证代码。

  清单 28. J2EE 中的 MVC User-validation class

1 package com.ibm.businessobjects;
2
3   public class UserValidation {
4
5   public boolean validate(String username, String password) {
6
7   ...
8
9   }
10
11   }
12
13

  凭据验证 servlet 所使用的类中进行。现在该 servlet 不必知道关于验证如何进行的信息。只要您保持相同的公开接口不变,就可以随意更改 UserValidation 类,不再需要改变 servlet 代码。

  其他 J2EE 组件

  前面的例子展示了如何将业务逻辑分离到一个或一组单独的类中。这样允许您以各种不同的方式实现业务逻辑,而不会影响 servlet 和 JSP 页面。与使用一个需要访问数据库(使用 JDBC)的业务对象不同,您可以通过各种不同的方式实现这个逻辑。例如:

  使用 JCA 来访问诸如大型机等遗留系统:JCA 是一个 J2EE API,它允许 J2EE 应用程序使用标准接口来与遗留系统(比如 CICS 或者 IMS)交互。

  使用 Web 服务客户端 API 来访问 Web 服务:这种方法涉及使用 Apache Axis 或其他某种 Java Web 服务客户端 API 来访问 Web 服务。

  使用 JMS 将消息作为验证请求来发送:这种方法涉及使用标准 API 来访问 JMS 相容的消息中间件(比如 WebSphere MQ)。

  使用 EJB 技术来实现验证逻辑:这种方法涉及使用 EJB 组件(而不是简单的 Java 类)来实现业务逻辑。EJB 组件能够使用应用服务器中的服务来管理(数据库或其他)事务,管理方法级的安全,以及提供群集和高速缓存。将此类任务委托给应用服务器,您可以把精力集中在业务逻辑上,而不必担心系统级的问题。

  下面我们将介绍 JDBC API,这是 J2EE 下的行业标准接口集,它允许 Java 程序使用统一的 API 调用集来访问关系数据库。JDBC 允许您开发应用程序一次,然后针对 JDBC 相容的任何数据库部署它们――DB2、Oracle、SQL Server、Sybase、mySQL、Informix 以及其他数据库。

  JDBC API 允许任何 Java 组件(包括 Java Servlets 和 JSP 页面)无缝地访问关系数据库中的数据。 JDBC 由两部分组成:用于访问数据库的 API,以及数据库厂商为各自的数据库提供的可插入 JDBC 驱动程序。JDBC API 最便利的特性之一在于,您不必使用任何厂商专用的类。您可以在运行时使用一个字符串常量来指定适当的驱动程序,以后就可以使用相同的编程接口,而不管所使用的是哪种数据库。这样允许您切换数据库,而不必更改代码(只要您提供在用于运行时指定驱动程序的字符串常量)。

  一个 JDBC 例子

  典型的 JDBC 应用程序必须完成以下步骤才能与数据库交互:

  识别驱动程序。(每个应用程序只需执行这个步骤一次。)

  识别数据库并与之建立连接(在需要时提供身份验证信息)。

  执行查询和/或更新。

  处理结果。

  断开与数据库的连接。

  清单 29 说明了上述步骤。

  清单 29. 一个 JDBC 例子

1 // Step 1. SPECIFY THE APPROPRIATE DRIVER TO USE
2
3   Class.forName("COM.ibm.db2.jdbc.app.DB2Driver");
4
5   // Step 2. Identify the database and connect to it
6
7   Connection conn = DriverManager.getConnection("jdbc:db2:SAMPLE","userid","password");
8
9   // Step 3. Execute query
10
11   Statement stmt = conn.createStatement();
12
13   stmt.execute("SELECT * FROM USERID.EMPLOYEE");
14
15   // Step 4. Get the results
16
17   ResultSet rs = stmt.getResultSet();
18
19   while (rs.next()) {
20
21   String firstCol = rs.getString(1);
22
23   int secondCol = rs.getInt(2);
24
25   ...
26
27   }
28
29   // Step 5. Disconnect from the database
30
31   conn.close();
32
33

  清单 29 中的 Step 1 动态地加载适当的驱动程序。这个例子使用的是 DB2;其他 JDBC 驱动程序具有不同的完全限定名称。Step 2 提供特定于驱动程序的 String 来指出要连接到的数据库。每种 JDBC 驱动程序都有自己的 String 格式。DB2 使用的格式是 "jdbc:db2:DBNAME" ,因此在此例中要连接到的是一个名为 SAMPLE 的数据库。例子中还提供了身份验证信息,以便数据库服务器能够对连接请求进行身份验证。在 Step 4 中,注意例子中如何循环迭代结果集,直至 next() 方法返回 false 。您还必须知道每个列的类型,并对每个列和每种类型调用适当的 getXXXX(n) 方法。还要注意您传递给 getXXXX(n) 方法的整数是列编号。

  这种方法相当类似于使用 ADO.NET DataReader,因为程序员要负责打开和关闭数据库连接。在 ADO.NET 中,您最初对数据库使用 ADO.NET 提供程序所提供的类,因此不能像使用 JDBC 那样容易地在运行时切换数据库。

  连接池

  ADO.NET 还提供一组称为 DataAdapter 的类,它们允许在断开连接的模式下操作,从而使您不必显式地管理数据库连接。使用 DataAdapter 的典型应用流程如下:

  使用提供程序的 DataAdapter 类来识别数据库(在需要时提供身份验证信息)和查询。

  执行查询。

  处理结果。

  使用 JDBC,您可以从显式地管理连接的工作中解放出来。可以配置 J2EE 应用服务器来维护可通过 JDBC 访问的数据库的数据库连接池。然后应用程序就可以从应用服务器管理的池中请求连接,并在完成使用时将它们返回池中。大多数应用服务器都具有管理连接池的相当高级的选项,包括“唤醒”连接的能力,即从没有在给定时间内将连接返回池中的错误应用程序那里取回连接。

  使用连接池的应用程序的典型流程如下:

  查找应用服务器资源库中的 DataSource (用于访问特定的连接池)。(每个应用服务器都有一个资源库,其中包含所有已配置的连接和其他资源,比如消息队列和 SMTP 提供程序。您可以使用名为 JNDI 的标准 J2EE API 来访问资源库。)(您只需执行这个步骤一次。)

  从池中获得连接(在需要时提供身份验证信息)。

  执行查询和/或更新。

  处理结果。

  将连接返回池中。

  清单 30 显示了使用连接池的一个例子。

  清单 30. 一个连接池例子

1 // Step 1. Look up the DataSource
2
3   InitialContext ic = new InitialContext();
4
5   DataSource ds = (DataSource) ic.lookup("jdbc/MyDS");
6
7   // Step 2. Get a connection from the pool
8
9   Connection conn = ds.getConnection();
10
11   // Step 3. Execute query
12
13   Statement stmt = conn.createStatement();
14
15   stmt.execute("SELECT * FROM USERID.EMPLOYEE");
16
17   // Step 4. Get the results
18
19   ResultSet rs = stmt.getResultSet();
20
21   while (rs.next()) {
22
23   String firstCol = rs.getString(1);
24
25   int secondCol = rs.getInt(2);
26
27   ...
28
29   }
30
31   // Step 5. Return connection to the pool
32
33   conn.close();
34
35

  Step 1 假设应用服务器已经使用名为 "jdbc/MyDS" 的 DataSource 来配置好了。这样就封装了一个特定的 JDBC 驱动程序和数据库。您还可以向 DataSource 定义添加身份验证信息,这样应用程序就不必提供该信息。可以使用 InitialContext (它是 JNDI API 的一部分)来查找应用服务器控制的各种资源。Step 3 至 Step 5 与前一个例子相同,不过要注意 Step 5 中对 close() 方法的调用仅把连接返回池中,而不是关闭该数据库连接。

  J2EE 应用程序状态管理

  在编写 J2EE Web 应用程序时,可以任意选择一组丰富的类和接口来管理应用程序的状态。我们将介绍 J2EE HttpSession 类(它类似于 ASP.NET HttpSessionState 类),以及允许您管理应用程序状态的其他类和接口。我们还会讨论如何同时在 Java Servlets 和 JSP 页面中使用这些类和接口。不过,我们将首先介绍 范围的概念,它是理解 J2EE 中的应用程序状态管理的关键。

  范围

  从程序员的角度看,状态管理涉及临时地存储数据和在需要时检索它们。在 J2EE 中,您可以选择多个“存储位置”,每个位置具有它自己规则,控制着所存储的任何数据在多长时间内可用。持续时间范围从处理特定页面时临时存储一些数据到在应用程序运行生命期内存储数据不等。J2EE 中的“存储位置”选择称为特定存储请求或检索的 范围。该范围决定了您将把数据附加到哪些 J2EE 对象,以及那些数据将在多长时间内可用。可用的范围宝库o:

  会话:这类似于 ASP.NET 中的会话范围。只要会话还是活动的,您就可以在该用户会话范围内放置任何对象并检索它。J2EE 应用程序使用 HttpSession 接口(类似于 ASP.NET 中的 HttpSessionState )。对象通过一个 String 作为标签来添加到会话中,并使用相同的标签来检索它。

  请求:在 J2EE 中, HttpServletRequest 对象允许您向它附加数据,这非常类似 HttpSession 接口。当多个资源处理单个请求时,这是很有用的。例如,某个 Java Servlet 可能是一个 HTML 表单提交的目标,然后它将请求转发给一个 JSP 页面以完成它。在这个例子中,该 sevlet 能够向 HttpRequest 对象附加数据,并且 JSP 页面能够访问它。注意在这种场景中,该 servlet 和 JSP 页面都使用相同的 HttpRequest 对象。向相同请求内的不同资源转发的能力类似于 ASP.NET 中的 Server.Transfer 。

  应用程序:所有 J2EE Web 应用程序在部署之前都打包到一个具有 .war 扩展名的文件中。该文件的格式是标准的,因此您可以把同一个应用程序部署到不同的应用服务器。单个 .war 文件中的所有 J2EE 组件都被认为是同一个应用程序的组成部分,并且共享共同的应用程序上下文。这是通过 ServletContext 接口向开发人员公开的,这个接口(就像 HttpSession 和 HttpRequest 接口一样)允许您附加和删除任何 Java 对象。只要应用程序还在运行,添加到 ServletContext 的项就可用,并且会在单独会话的创建和销毁过程中保留下来。

  页面:页面上下文在处理单个页面的过程中可用。例如,JSP 页面顶部的 Java scriptlet 能够在 PageContext 中放置对象,然后相同页面中的其他 scriptlet 就可以访问它。

  管理应用程序状态

  现在您已经对范围有了更好的了解,下面我们可以深入地讨论管理 J2EE 应用程序中的状态的机制。非常好的实践坚持认为,对于任何临时的状态存储,您都应该确定需要存储该数据多长时间,然后使用满足需要的、具有最短生存时间的范围。例如,假设您需要某个 JSP 页面中的数据,该 JSP 页面是从某个 servlet 转发的请求的目标。虽然会话状态和应用程序状态也满足您的需要,但是在这两种情况下,数据都会在使用完之后悬置在那里。这样不必要地增加了当前应用程序的资源需求。对于这个例子,请求范围能够满足需要,却不会在您不需要之后还将数据悬置在那里。

  管理 JSP 页面中的状态

  在 JSP 脚本环境中,所有范围对象都是用隐含变量来表示的。您可以在自己的 sciptlet 中使用这些变量,而不需要显式地声明它们。 可以使用的隐含变量包括:

  session:实现 HttpSession 接口的类的一个实例

  application:实现 HttpSession 接口的类的一个实例

  request:实现 HttpServletRequest 接口的类的一个实例

  pageContext: PageContext 类的一个实例

  清单 31 显示了如何在 JSP scriptlet 中针对不同的范围添加和删除对象。

  清单 31. JSP 页面中的状态管理

1 <%@ page contentType="text/html; charset=iso-8859-1" language="java" session="true" %>
2 <%
3 String foo = "I am a Foo";
4 // Place object in session scope
5 session.setAttribute("Foo", foo);  
6 // Retrieve from session scope
7 String sessionFoo = (String) session.getAttribute("Foo");
8 // Place object in application scope
9 application.setAttribute("Foo", foo);  
10 // Retrieve from application scope
11 String applicationFoo = (String) application.getAttribute("Foo");
12 // Place object in page scope
13 pageContext.setAttribute("Foo", foo);
14 // Retrieve from page scope
15 String pageFoo = (String) application.getAttribute("Foo");
16 // Place object in request scope
17 request.setAttribute("Foo", foo);
18 // Retrieve from request scope
19 String requestFoo = (String) request.getAttribute("Foo");
20 %>
21 ....
22

 

  清单 31 中的例子的第一行是 page 指令,它允许您定义整个页面的属性。请注意 session 属性的使用,它决定了该页面是否要使得会话可用。如果将它设置为 true 却还没有建立会话,那么新的会话就会自动为您创建。如果将它设置为 false,那么会话范围将在该 JSP 页面中不可用。(这个属性的面默认设置是 true,因此这里使用它是多余的,只是出于说明目的。)

  清单 32 是使用各种可用范围来存储和检索数据的 servlet 的一个例子。

  清单 32. servlet 中的状态管理

1 public class MyServlet extends HttpServlet {
2
3   public void doGet(HttpServletRequest request, HttpServletResponse response)
4
5   throws ServletException, IOException {
6
7   performTask(request, response);
8
9   }
10
11   public void doPost(HttpServletRequest request, HttpServletResponse response)
12
13   throws ServletException, IOException {
14
15   performTask(request, response);
16
17   }
18
19   /**
20
21   * Handles all HTTP GET and POST requests
22
23   *
24
25   * @param request Object that encapsulates the request to the servlet
26
27   * @param response Object that encapsulates the response from the servlet
28
29   */
30
31   public void performTask(
32
33   javax.servlet.http.HttpServletRequest req,
34
35   javax.servlet.http.HttpServletResponse res)
36
37   throws ServletException, IOException {
38
39   try {
40
41   // This is how you create a session if is has not been created yet
42
43   // Note that this will return the existing session if you've
44
45   // already created it
46
47   HttpSession session = req.getSession();
48
49   //This is how the application context is retrieved in a servlet
50
51   ServletContext application = getServletContext();
52
53   String foo = "I am a Foo";
54
55   // Place object in session scope
56
57   session.setAttribute("Foo", foo);
58
59   // Retrieve from session scope
60
61   String sessionFoo = (String) session.getAttribute("Foo");
62
63   // Place object in application scope
64
65   application.setAttribute("Foo", foo);
66
67   // Retrieve from application scope
68
69   String applicationFoo = (String) application.getAttribute("Foo");
70
71   // Place object in request scope
72
73   request.setAttribute("Foo", foo);
74
75   // Retrieve from request scope
76
77   String requestFoo = (String) request.getAttribute("Foo");
78
79   ...
80
81   } catch (Throwable t) {
82
83   // All errors go to error page
84
85   throw new ServletException(t.getMessage());
86
87   }
88
89   }
90
91   }
92
93

  JSP 页面和 sevlet 中的会话之间的区别在于,在 sevlet 中,您必须显式地创建和检索它,而在 JSP 页面中,这是自动为您完成的。注意 pageContext 仅适用于 JSP 页面,而不适用于 servlet;还要注意 servlet API 如何提供了两个重载方法来同时处理 GET 和 POST HTTP 请求。这个例子提供了一个名为 performTask() 的通用方法来同时处理这两种类型的请求,不过您可以基于触发 servlet 调用的 HTTP 类型而使用不同的处理。

  编程方式的导航

  在代码中从一个资源导航到另一个资源的情况在 Web 应用程序中是相当普遍的。导航与应用程序状态密切相关,因为当你在一系列资源中导航时,可能需要维护状态信息。例如,如果从 sevlet 向 JSP 页面转发请求,那么您可能希望附加某些在该 servlet 中完成的处理结果,以便这些结果能够显示在 JSP 页面中。

  有三个 ASP.NET 方法支持编程方式的导航:

  HttpResponse.Redirect() 导致向客户端浏览器返回一个特殊的 HTTP 返回代码(连同要重从定向的页面),然后客户端浏览器又对重定向的目标发出新的请求。如果需要在这两个请求之间共享数据,那就必须将数据存储在会话状态中。

  Server.Transfer() 导致调用此方法的资源终止,同时终止对作为转移目标的资源的调用。对客户端浏览器来说,这看起来就像是单个请求。

  Server.Execute() 导致调用此方法的资源挂起,同时挂起对目标资源的调用。当目标资源完成时,挂起的资源又继续处理。 对客户端浏览器来说,这看起来就像是单个请求。

  J2EE 支持以下形式的编程式导航:

  适用 HttpServletResponse.sendRedirect() 方法 :这会导致向客户端浏览器返回一个特殊的 HTTP 返回代码(连同要重从定向的页面),然后客户端浏览器又对重定向的目标发出新的请求。如果需要在这两个请求之间共享数据,那就必须将数据存储在会话或应用程序范围中。

  在 servlet 或 JSP 页面的特殊标签中使用 RequestDispatcher.forward() 方法 :这会导致调用此方法的资源终止,同时终止对作为转发目标的资源的调用。对客户端浏览器来说,这看起来就像是单个请求。

  在 JSP 页面的特殊标签中使用 RequestDispatcher.include() 方法 :这会导致调用此方法的资源挂起,同时挂起对目标资源的调用。当目标资源完成时,挂起的资源又继续处理。 对客户端浏览器来说,这看起来就像是单个请求。

  清单 33 包括了这些导航方法在 JSP 页面中的一些例子。

  清单 33. JSP 页面中的编程式导航

1 <!-- This scriptlet shows how you can redirect to another resource -->
2 <%
3 response.sendRedirect("anotherPage.jsp");
4 %>
5 <!-- This special JSP tag allows you to forward the request to another resource -->
6 <jsp:forward page="anotherPage.jsp"/>
7 <!-- This special JSP tag allows you to include another resource as
8 part of the processing of this page -->
9 <jsp:include page="anotherPage.jsp" flush="true"/>
10

 

  清单 34 显示了这些导航方法在 servlet 中的一些例子。

  清单 34. servlet 中的编程式导航

1 public void doGet(HttpServletRequest request, HttpServletResponse response)
2
3   throws ServletException, IOException {
4
5   ...
6
7   // Redirecting to another resource
8
9   response.sendRedirect("anotherResource.jsp");
10
11   ...
12
13   }
14
15   // This code snippet shows the use of a RequestDispatcher to forward to another resource
16
17   public void doGet(HttpServletRequest request, HttpServletResponse response)
18
19   throws ServletException, IOException {
20
21   ...
22
23   // Get a RequestDispatcher instance
24
25   RequestDispatcher rd = getServletContext().getRequestDispatcher("anotherResource.jsp");
26
27   // Forward the request
28
29   rd.forward(request, response);
30
31   ...
32
33   }
34
35   // This code snippet shows the use of a RequestDispatcher to include another resource
36
37   public void doGet(HttpServletRequest request, HttpServletResponse response)
38
39   throws ServletException, IOException {
40
41   ...
42
43   // Get a RequestDispatcher instance
44
45   RequestDispatcher rd = getServletContext().getRequestDispatcher("anotherResource.jsp");
46
47   // Forward the request
48
49   rd.include(request, response);
50
51   // Continue processing the request
52
53   ...
54
55   }
56
57

  Cookie

  在 ASP.NET 中,cookie 的处理使用了 HttpRequest 和 HttpResponse 对象的 Cookies 属性。这个属性表示 HttpCookie 对象的一个集合,集合中的每个成员分别表示一个单独的 cookie。

  在 J2EE 中,cookie 使用 Cookie 类来表示,并且可通过 HttpServletRequest 和 HttpServletResponse 接口来访问它们。现有的 cookie 使用 HttpServletRequest 接口来访问,新的 cookie 使用 HttpServletResponse 接口来创建。

  清单 35 演示了如何在 J2EE 应用程序中使用 cookie。你可以在 Java Servlet 或 JSP 页面的 scriptlet 中使用这些代码片断。

  清单 35. 在 J2EE Web 应用程序使用 cookie

1 // Create a cookie using the HttpServletResponse interface
2
3   // The first parameter of the constructor is the name of the cookie
4
5   // and the second parameter is the cookie's value
6
7   Cookie myCookie = new Cookie("myCookie", "someValue");
8
9   // Set the age of the cookie - by default cookies will be stored in memory
10
11   // by the browser and will be destroyed when the user closes the browser
12
13   // The setMaxAge() methods takes an integer as its parameter which represents
14
15   // the age in seconds. In this example we're specifying an age of 24 hours for the cookie
16
17   myCookie.setMaxAge(86400);
18
19   // Add cookie to the response
20
21   response.addCookie(myCookie);
22
23   // Here's an example of retrieving cookies from the HttpServletRequest interface
24
25   // Note that the cookies are returned as an array of Cookie objects
26
27   Cookies[] myCookies = request.getCookies();
28
29   // The getCookies method will return null if there are no cookies
30
31   if (myCookies != null) {
32
33   for (int i = 0; i < myCookies.length; i++) {
34
35   Cookie eachCookie = myCookies[i];
36
37   // Do something w/each cookie
38
39   ....
40
41   }
42
43   }
44
45   
46

  结束语

  感谢您使用这个关于 J2EE 开发的入门教程。我们已尽力提供足够的信息使您走上 J2EE 之路,并且使您的开放标准编程之旅尽可能的顺利。

0
相关文章