【IT168 技术文章】
为何要转向 J2EE?
如果您不是十分渴望冒险投入 J2EE 开发环境,请考虑以下这些 J2EE 好处:
选择,更多的选择:由于 J2EE 是一个定义良好的标准集,所以在部署自己的代码时有许多 J2EE 实现可供选择。只要您坚持使用标准 API,避免使用厂商专用的扩展,那么应用程序无需变更代码就能在各种各样的实现上运行。
我们是在说选择吗?:J2EE 实现在从大型机到 Wintel、UNIX 和 Linux 的各种平台上都可用。编写应用程序一次就可将其部署在各种平台上。
我们不能就安于现状吗?:J2EE 包含一个用于访问许多诸如 CICS、IMS、ERP 和 CRM 这样的遗留 EIS 系统的标准 API。它还包括 Web 服务支持,因此您可以集成 .NET 系统和支持行业 Web 服务标准的其他系统。J2EE 还支持标准消息 API(Java Message Service; JMS)和用于访问关系数据库(Java Database Connectivity; JDBC)的 API。这种广泛的选择允许您集成各种现有系统,而不会损失您对它们的投资。
机房不再烟雾缭绕:来自世界各地的专家通过 Java Community Process(JCP)开发 J2EE 规范。JCP 发布了初步的规范草案以供公众评论。即使您不主动参与,也总是会知道哪些未来的规范正在筹备之中。该规范还包括一个参考实现,您可以在决定实现它之前使用它来检查新技术。
J2EE 简介
Java 2 Enterprise Edition 这个规范是由许多与使用 Java 语言开发分布式应用程序相关的组件规范组成的。您可以使用 J2EE 组件来编写基于 Web 的应用程序和传统的客户机-服务器应用程序,以及使用标准的 API 来连接到诸如关系数据库之类的遗留资源。如果您有 IIS/ASP 开发背景, 那么 Java Servlets和 JavaServer Pages(JSP) 技术就是对您最有用的组件。
Java Servlet
Java Servlet 是作为诸如 IIS 或 Apache Web Server 等 Web 服务器的扩展来运行的 Java 类。Java Servlet 类似于 ISAPI 过滤器或 cgi-bin 程序/脚本。servlet 在客户端浏览器直接或间接地调用一个专门配置的 URL 时运行。servlet 能访问 HTTP 请求中的所信息,并能通过提供返回给客户端的内容来直接处理该请求。或者,servlet 能将客户端浏览器重定向到另一个资源。大多数 J2EE Web 应用程序都主要把 servlet 用作 HTML 表单的目标以处理用户输入,然后作相应的处理。响应页面的生成通常委托给一个 JSP 页面。
JavaServer Page 技术
JSP 页面类似于 ASP 页面。即它们是包含脚本元素的 HTML 页面,在用户请求该页面时,这些脚本元素就会在服务器上运行。ASP 页面和 JSP 页面之间的一个关键区别在于,ASP 使用 VBScript 或 JScript 作为脚本语言,而 JSP 页面则使用 Java 语言。典型的 JSP 页面包含 Java 代码片断和一些在 JSP 规范中定义的特殊的类 HTML 标签,它们与标准的 HTML 交织在一起,提供静态内容和动态内容的组合。Java Servlet 和 JavaServer Page 技术之间的区别在概念上类似于 ISAPI 过滤器和 ASP 页面之间的区别。在这两种情况下,前者都是可用于直接或间接向其他资源发送 HTML 的一段代码,而后者都是一个可以包含嵌入代码的 HTML 文档。
Web 服务器和应用服务器
作为 ASP 开发人员,您知道 ASP 页面由 IIS 调用的脚本引擎执行。您还可以向 Web 应用程序添加 ISAPI 过滤器和 COM 组件,以供 IIS 进行调用。这种方法使得在 IIS 上部署 Web 应用程序非常容易。但这仅限于 Windows 平台,即 IIS 可以运行的唯一平台。而 J2EE 采用不同的方法,因为它的设计目标就是在各种操作系统(包括 Windows)上运行。它并非尝试将运行 Java Servlet 和 JSP 页面的代码直接嵌入到 Web 服务器,而是使用一个称为 应用服务器的单独的服务器组件来运行它们。大多数应用服务器(如 IBM WebSphere)还拥有一个单独的插入组件,它们在应用服务器和特定的 Web 服务器之间架起了一座桥梁。例如,WebSphere 附带了针对 IIS 和 Apache Web 服务器的单独插件。这样,您在运行 J2EE 组件时就可以使用自己选择的 Web 服务器。
应用服务器作为单独的可插入组件这种功能带来了许多优点:
Web 服务器选择:您不会被限定使用某个 Web 服务器来提供静态的 HTML 页面。您可以继续使用自己最喜欢的 Web 服务器来实现此目的,并且使用任何应用服务器来处理 Java Servlet 和 JSP 页面。这种能力在您将 ASP 应用程序移植到 J2EE 时特别有用。您可以继续运行 IIS 并分阶段移植应用程序。您不需要一下子改写整个应用程序。
平台选择:您可以编写 J2EE 应用程序一次,然后在能够运行应用程序服务器的各种操作系统上部署它――包括 Windows、AIX 和 Linux。您不会被限定于某个能够运行特定 Web 服务器的平台。
应用服务器厂商选择:由于行业标准规范定义了 Java Servlet 和 JavaServer Page 技术,因此您可以编写 J2EE 应用程序一次,然后将其部署到多个应用服务器环境中,如 WebSphere Express 或 Apache Tomcat,后者是一个流行的开放源代码应用服务器。J2EE 还定义了必须如何打包 Web 应用程序,因此,您可以将自己开发的应用程序引入某个 J2EE 环境,在无需更改代码或重新编译应用程序的情况下,就能将它重新部署另一个应用服务器中。将应用程序部署到多个平台也是如此。
应用服务器如何运行 servlet 和 JSP 代码
如上所述,J2EE 规范强制人们使用一种标准格式来部署 Java Servlets 和其他 J2EE 组件。一个称为 部署描述符的 XML 文档就是这个标准格式的一部分。部署描述符包含从每个 servlet 到用于调用特定 servlet 的 URL 的映射。应用服务器使用部署描述符中的信息来决定针对给定的请求应该调用哪个 servlet。
应用服务器调用 JSP 页面的方式不同于调用 ASP 页面的方式。J2EE 应用服务器将各个 JSP 页面转换为单独的特殊 servlet,它在该页面被请求时编译和运行。这个特殊的 servlet 保持加载在内存中,直到 JSP 文件改变为止。这样最大限度地降低了必须为每个 JSP 页面创建和编译一个类而对性能产生的影响。
模型-视图-控制器体系结构
J2EE 是根据一个特定的应用程序结构开发的,这个结构称为 模型-视图-控制器(MVC)。MVC 清楚地将应用程序定义为三个分离的层:
模型:应用程序的数据和业务规则的集合――通常称为应用程序的业务逻辑。
视图:应用程序的用户界面。
控制器:定义了应用程序如何对用户输入或模型层的变化作出反应――通常称为应用逻辑。
MVC 体系结构的优点
J2EE 中没有任何东西强迫您使用 MVC 体系结构来组织应用程序,但是这样做有许多很好的理由。通过定义三层之间的清楚分离,MVC 允许构成每个层的组件之间进行松散耦合。这使得组件具有更高的可重用性和灵活性。例如,假设您的需求之一是在某个 Web 应用程序中对相同数据支持不同类型的视图,因为不同的部门需要某个数据库中相同数据的不同子集。您需要开发特定于每个所需子集的新视图组件。如果视图逻辑和数据库访问代码是紧密耦合的――ASP 页面就是将数据库访问代码和 HTML 交织在一起,那么每个视图都要包含数据库访问代码。维护重复的代码不仅需要大量的工作,而且可能导致出错。对于这种情形,MVC 体系结构将数据库访问代码作为该模型的一部分,而各种视图组件都可以重用它。
J2EE 组件和 MVC
图 1 显示我们到目前为止所讨论的 J2EE 组件如何映射为 MVC 体系结构。注意,模型和视图之间不存在任何联系。控制器的功能是充当两者之间的中转站。
图 1. MVC 与 J2EE Web 应用程序
在典型场景中,用户提交一个 HTML 表单,这个表单的目标是一个 servlet。servlet 解析输入的数据并使用模型中的类来调用业务逻辑以满足该请求。然后,servlet 将结果传递给一个 JSP 页面,以便向用户显示这些结果。
JavaServer Faces
JavaServer Faces (JSF) 规范提供了运行时组件,这些组件使 J2EE 工具厂商为开发基于 Web 的 UI 提供了拖放功能。它还允许厂商开发可供他们的开发工具使用的自定义组件。要了解 JSF 的实际应用,可以考察一下 WebSphere Studio 5.1.1 版中的系列工具(请参阅 参考资料)。WebSphere Studio 还有一个名为 Page Designer 的完全集成的工具,可以使用它通过拖放操作来可视化地开发 HTML 页面和 JSP 页面。Page Designer 已实现了 JavaServer Faces 规范,它允许您在页面上拖放诸如 HTML 表单元素之类的组件以及一些更高级的组件,这些组件允许将 HTML 表绑定到后台数据源。
其他 J2EE 技术
Java Servlets 和 JSP 技术为您提供用 Java 语言开发平台无关的 Web 应用程序所需的工具。其他一些 J2EE 规范和组件向您提供了您带来更高级的功能:
Enterprise JavaBeans (EJB) 技术:企业组件(或 beans)存在三种形式:
会话 beans:特殊的 Java 类,类似于在 Microsoft Transaction Server 或 MTS 对象控制下运行的 COM 对象。与 MTS 对象一样,会话 beans 在容器中运行 ―― MTS Executive(就 MTS 对象而言)和 EJB 容器(就会话 bean 而言)。EJB 容器提供诸如声明性的事务管理、基于角色的安全、分布式环境中的无缝集成以及根据需要激活等服务。会话 bean 又存在两种形式:
无状态的:方法调用之间没有维护状态,因此您必须提供通过参数来调用某个方法时所需要的全部信息。无状态会话 bean 的优点在于,容器可以使用任何实例来服务于任何客户机调用。
有状态的:方法调用之间的状态得到保持,以便客户机总是与特定的实例相关联。有状态会话 bean 的优点在于,客户机可以使用对话模式来与有状态的会话 bean 交互。当重新创建中间状态信息的成本所需的代价很大时,这就特别有用。
实体 bean:特殊的 Java 类,它们是存储在关系数据库或其他持久存储中持久数据的对象表示。它们可以封装数据库模型中的表,也可以封装数据模型中表之间的关系。与会话 bean 相似,它们在提供以下服务的容器中运行:声明性的事务管理、基于角色的安全和分布式环境中的无缝访问。实体 bean 是共享对象,因此容器还要处理并发控制,并确保底层持久数据保持其 ACID(Atomicity、Consistency、Isolation 和 Durability,即原子性、一致性、隔离性和持久性)属性。(与会话 bean 不同,实体 bean 是共享对象,因此多个客户机可以并发地访问单个实例。)简而言之,实体 bean 防止您直接访问底层的持久存储。无需作出任何应用程序更改,就可以将它们部署到各种不同的持久存储中。(也就是说,无需改动任何代码,就可以在部署时将实体 bean 映射到它的持久存储。)因此,例如,无须更改任何应用程序,就可以将已经映射到 Oracle 或 SQL Server 数据库的一组实体 bean 重新映射到 DB2。
消息驱动的 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。
Java 编程基础
在深入某些 J2EE 编程概念之前,我们首先向您介绍 Java 编程语言。可以使用 Java 语言来编写服务器端应用程序以及具有 GUI 的桌面应用程序。本文假定您想要在服务器端使用 Java 语言来补充一个基于 Web 的界面,因此我们将跳过 CUI 编程环境,而是重点关注该平台的非可视化方面。我们首先介绍 Java 软件开发包(Java Software Development Kit,SDK),然后向您展示如何使用 Java 代码来编写历史悠久的 Hello World 应用程序。然后,我们将深入介绍 Visual Basic 6 和 Java 语言之间的差别。如果您是一个 C/C++ 程序员,可以跳过本节,学习教程“C/C++ 开发人员的 Java 编程”。
Java SDK 简介
Java SDK 是编写和运行 Java 程序所需的一组命令行工具和包。Java 程序通过即时(Just In Time,JIT)编译器编译为平台无关的字节码,然后该字节码可以在运行时编译为本机代码。其中最重要的工具是 Java 编译器(javac.exe)和 Java 解释器(java.exe),后者用于运行 Java 程序。该 SDK 还包括基础的类(称为 Java 平台),它们向您提供了开始编写应用程序所需要的基本功能和 API。
Sun Microsystems 为 Java 平台的 5 个主要版本的各发布了一个 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 程序
2
3 public static void main(String[] args) {
4
5 System.out.println("Hello World");
6
7 }
8
9 }
10
11
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 文件的位置。然后执行下面这个命令:
像 Visual Basic 编译器一样,Java 编译器可能生成任意数目的错误。自然,您需要更正所有错误,Java 编译器才会成功地编译 HelloWorld 程序。编译成功后将生成一个名为 HelloWorld.class 的类文件。这个文件代表您将在 Java 解释器中运行的可执行文件。
运行程序
SDK 附带的 Java 语言解释器是一个名为 java.exe 的命令行应用程序。要运行 Java 字节码可执行程序,只需将该 java 程序的名称传递给 java 解释器。在使用 Java 解释器时不要指定 .class 扩展名。解释器仅接受类文件,因此添加 .class 扩展名将产生一个错误。要运行这个 HelloWorld 程序,请打开命令提示符,将目录切换到您编译 HelloWorld.java 文件的位置。这个字节码可执行文件 HelloWorld.class 应该就在该目录中。然后执行下面这个命令:
Java 解释器尝试执行 HelloWorld 程序的 main() 方法。将 void 作为返回类型的 Java 方法等同于 Visual Basic 中的 Sub 。带有其他返回类型的方法等同于 Visual Basic 中的 Function 。
Java 解释器可能会报告运行时错误,这通常会终止程序执行。与在 Visual Basic 中一样,Java 运行时错误要比编译时错误更难于调试,不过没有编译时错误出现得那么频繁。在确实发生运行时错误时,您可以从容不迫地处理这些错误,因为 Java 程序在托管环境中执行,这减少了“失控代码”造成整个机器紧急停机的可能性。
从 Visual Basic 角度看 Java 101
既然您对 Java 代码的外观以及如何编译并在测试机器上运行有了初步的了解,那么您应准备深入了解 Java 语言的结构和语法,包括 Java 编程环境和 Java 原始数据类型。由于您熟悉使用 Visual Basic 进行编程,所以您可以通过比较进行学习。我们将就 Java 平台的基础组件与 Visual Basic 编程框架底层相似组件的关系和区别来讨论 Java 平台的基础组件。如果您是一个 C/C++ 程序员,则可以跳过本节,然后学习教程“C/C++开发人员的 Java 编程”。
Visual Basic 执行环境
Visual Basic 是一种高级编程语言;它的目的是使人们可以轻松地开发计算机程序。计算机不能理解高级语言;只能理解低级的机器语言――可以直接在计算机处理器上执行的二进制指令序列。因此,必须将用高级语言编写的程序转换成机器语言程序 ―― 可执行程序,然后才能在计算机上执行。不同的计算机使用不同的机器语言。在一台机器上运行的可执行程序将不能在另一台使用不同机器语言的计算机上运行。
将高级编程语言转换为机器语言可执行程序采用两种方法:编译和解释。编译会将整个高级语言程序转换为整个机器语言程序,然后可以全部执行机器语言程序。解释会将高级语言程序逐行转换为机器指令;在转换并执行一行后,才到达下一行。编译和解释在逻辑上是等价的,但编译程序的执行速度一般比解释程序要快。Visual Basic 程序由名为编译器的程序编译成机器语言可执行程序。
Java 执行环境
与 Visual Basic 程序类似,Java 程序也是进行编译的。但与 Visual Basic 程序不同的是,Java 程序并非被编译成特定于平台的机器语言。而是被编译成与平台无关的语言,称为 字节码。字节码与机器语言类似,但设计字节码的目的并不是在真正的物理计算机上运行。而是由被称为 Java 虚拟机(Java virtual machine,JVM)的程序运行,Java 虚拟机模拟真实的机器。
简单地说,JVM 是一个解释器,它将 Java 字节码转换为在底层的、物理机器上执行的真实机器语言指令。更具体的说,术语 Java 虚拟机一般用来指任何执行 Java 类文件的程序。Java 解释器程序 java.exe 是一个具体的 JVM 实现。
Java 平台使用虚拟机层来保证用 Java 语言编写的程序是平台无关的。Java 程序一旦编译成字节码,就可以在任何拥有 JVM 的系统上运行。这些系统包括 UNIX、Linux、Windows 以及许多其他系统。用其他语言编写的程序在每个平台上执行时,都必须重新编译,而 Java 程序只需编译一次。
数据类型
Java 语言包含两种不同的数据类型:程序员定义的类(或作为 SDK 或第三方类库的一部分而可以使用的类)和 Java 运行库直接理解的“原始”类型( boolean 、 char 、 byte 、 short 、 int 、 long 、 float 和 double )。大多数 Java 原始类型在 Visual Basic 中有等价的类型,而用户定义的类在很大程度上与 Visual Basic 中的类相似。表 1 列出 Java 语言中原始数据类型和在 Visual Basic 中等价的类型。
表 1. Java 语言原始类型及与其等价的 Visual Basic 类型
Java 原始类型 | 范围 | Visual Basic 类型 | 注释 |
boolean | true,false | Boolean | 布尔类型的有效值只有 true 和 false。 |
char | 0 - 65535 | String (of length 1) | Java 语言使用 Unicode 对字符进行编码。 |
byte | 8 位整数(有符号) | Byte | ? |
short | 16 位整数(有符号) | Integer | ? |
int | 32 位整数(有符号) | Long | ? |
long | 64 位整数(有符号) | N/A | ? |
float | 32 位浮点数 | Single | ? |
double | 64 位浮点数 | Double | ? |
N/A | ? | Variant | Java 语言没有 Variant 类型。 |
N/A | ? | Date | Java 语言没有原始日期类型。可以 用 Date 类代替。 |
N/A | ? | Currency | Java 语言没有原始货币类型。可以 用 BigDecimal 类代替。 |
清单 2 展示了在两种语言中声明原始类型的一些示例。
清单 2. 声明原始类型
2 Option Explicit // Note that all Java
3 Dim LetterJ As String, I As Integer, x As Byte // variables must be declared
4 Dim Point1 As Single, Point2 As Double // before they can be used
5 Dim IsEmpty As Boolean char letterJ = 'j';
6 LetterJ = "j" int i = 0;
7 I = 0 byte x = 12
8 X = 12 boolean isEmpty = false;
9 IsEmpty = False float point1 = 1.1F;
10 Point1 = 1.1 double point2 = 0.0025;
11 Point2 = 0.0025
12
运算符
Visual Basic 中的运算符和 Java 语言中的运算符有许多类似性,也有很多重要区别。Java 语言使用的运算符集合与 C 和 C++ 使用的相同。表 2 列出这两种语言中最常用的运算符。
表 2. Visual Basic 中的运算符和 Java 语言中的运算符Java 运算符 描述 用法 Visual basic 运算符 注释 ++
增量运算符
++num num++
N/A 这个一元运算符允许增量非浮点数。 --
减量运算符
--numnum--
N/A 这个一元运算符员允许减量非浮点数。 *
/
乘 除
num * num
num / num
*
/
? /
整除 num/num
\
Java 语言使用相同的运算符进行整除和算术除法。如果操作数是整数,则执行整除运算。 %
取模运算 num % num
Mod
? +
-
加减 num + num
num - num
+
-
? +
字符串连接 str + str
&
? <
<=
小于小于等于 expr < expr
expr <= expr
<
<=
? >
>=
大于大于等于 expr > expr
expr >= expr
>
>=
? !=
不等于
expr != expr
<>
? ==
等于(原始类型) expr == expr
=
? ==
等于(对象) obj == obj
Is
? !
逻辑非 !boolean
Not
Visual Basic 对整数按位取反和对布尔表达式逻辑取反采用相同的运算符。而 Java 语言采用不同的运算符。 ~
按位取反 ~int
Not
? &
按位与布尔与 int & int
expr & expr
And
? |
按位或布尔或 int | int
expr | expr
Or
? ^
按位异或布尔异或 int ^ int
expr ^ expr
Xor
? &&
条件与 if (expr &&expr)...
And
Java 语言对条件与和逻辑布尔与采用不同的运算符。 ||
条件或 if (expr || expr) ...
Or
Java 语言对条件或和逻辑布尔或采用不同的运算符。 N/A 模式匹配 str Like pattern
Like
您可以使用 Java String 对象的方法来完成 Visual Basic 运算符的功能。
Visual Basic 函数和子过程与 Java 方法的比较
Visual Basic 允许定义函数和子过程。函数和子过程的主要区别是子过程不返回值,而函数返回值。在 Java 技术中,函数被称为 方法。Java 语言没有与 Visual Basic 中的子过程等价的用法。但是,在 Java 语言中,可以使用关键字 void 定义不返回值的方法,这种方法大致等价于子过程。只能将 Java 方法声明位某个类的成员;不能在 Java 类之外定义方法。在清单 3 展示的例子中,一个 Java 方法返回值而另一个不返回值。
清单 3. Java 方法的返回类型
2
3 // This method doesn't return a value
4 public void myMethod1(String arg) {
5 ...
6 }
7
8
9 // This method returns an integer
10
11 public int myMethod2(String arg) {
12 int i;
13
14 ...
15
16 return i ;
17 }
18
数组
在 Java 语言中,数组是具有属性的对象,其中最重要的是 长度 属性,您可以使用该属性确定数组的大小。Java 数组的索引值始终从 0 开始,数组的声明大小包括第 0 个元素。因此,大小为 100 的数组意味着有效索引是从 0 到 99。另外,您还可以将用于表示数组的方括号([ ])与数组类型而非数组名绑定起来。Java 语言允许采用数组字面值,这样可以将数组初始化为一组预定义的值。清单 4 展示了一些例子。
清单 4. 数组
2 'An array with 100 integers // An array of 100 integers
3 Dim a(99) As Integer int[] a = new int[100];
4 'An array of Strings initialized // An array of Strings initialized
5 b = Array("Tom","Dick", "Harry") String[] b = {"Tom","Dick", "Harry"};
6
7 'Iterating through an array // Iterating through an array of length 100
8 ' of length 100 int [] c = new int [100];
9 Dim c(99) As Integer for (int i = 0; i <.length; i++) {
10 For i=0 To UBound(c) c[i] = i;
11 c(i) = i }
12 Next
13
字符串
Visual Basic 使用 String 数据类型来表示字符串。您可通过 String 类的对象来表示 Java 字符串。Visual Basic 和 Java 字符串字面值由一系列加引号的字符表示。在 Java 语言中,可以采用两种方法创建 String 对象:使用字符串字面量,或使用 构造函数。 String 对象是固定的,这意味着在赋予某个 String 一个初始值后,就不能改变它。换句话说,如果您要更改 String 引用的值,则需要将一个新 String 对象赋值给该引用。由于 Java 字符串是对象,所以可以通过 String 类定义的 接口与这些字符串进行交互。String 类型包含很多接口以及不少有用的方法。
清单 5 演示了一些最常用的方法。请尝试编译并运行该例子。记住将源文件命名为 StringTest.java,并且不要忘记文件名的大小写很重要。
清单 5. Java 语言中的字符串
2 * The StringTest class simply demonstrates
3 * how Java Strings are created and how
4 * String methods can be used to create
5 * new String objects. Notice that when you
6 * call a String method like toUpperCase()
7 * the original String is not modified. To
8 * actually change the value of the original
9 * String, you have to assign the new
10 * String back to the original reference.
11 */
12 public class StringTest {
13 public static void main(String[] args) {
14 String str1 = "Hi there";
15 String str2 = new String("Hi there");
16 // Display true if str1 and str2 have the value
17 System.out.println(str1.equals(str2));
18 // A new uppercase version of str1
19 System.out.println(str1.toUpperCase());
20 // A new lowercase version of str2
21 System.out.println(str1.toLowerCase());
22 System.out.println(str1.substring(1,4));
23 // A new version of str1 w/o any trailing whitespace chars
24 System.out.println(str1.trim());
25 // Display true if str1 start with "Hi"
26 System.out.println(str1.startsWith("Hi"));
27 // Display true if str1 ends with "there"
28 System.out.println(str1.endsWith("there"));
29 // Replace all i's with o's
30 System.out.println(str1.replace('i', 'o'));
31 }
32 }
33
main() 方法
作为应用程序从命令行运行的 Java 类必须定义一个 main() 方法才能运行。在 Java 代码中, main() 方法遵循严格的命名约定。采用下列方式声明 main() 方法:
注:您可以将 public 和 static 修饰符互换位置,可以将 String 数组命名为任何您希望的形式。但是,上述格式是约定俗成的。并非所有的类都需要 main() 方法 ―― 只有从命令行运行的那些类才需要该方法。典型的 Java 应用程序有一个类包含 main() 方法,而其他一些支持类都没有 main() 方法。
包
诸如 Java 语言之类的面向对象语言非常有利于类的重用。由于大多数程序员使用简单的描述性名称(如 Invoice 或 User )对其类进行命名,所以,当您从各种来源重用类时,名称冲突的可能性很高。Java 语言解决该问题的方法是,让每个类术语一个 包。您可以同时使用具有相同名称但属于不同包中的类。要将类与包关联起来,必须在类源代码的第一行代码进行包声明。请看下面的例子:
2
按照约定,将反向的 Internet 域名(例如, com.yourco.somepackage )作为包名称的前缀。要使用不同包中的类,有两种选择。一种选择是使用类的完全限定名称,包括包。清单 6 展示了一个例子。
清单 6. 使用全限定的类名
2 public static void main(String[] args) {
3 Java.util.Date today = new java.util.Date();
4 System.out.println("The date is " + today);
5 }
6
另一种选择是在源文件中对在另一个包中的类使用 import 语句。这种情况就不再需要完全限定名,如清单 7 所示。
清单 7. 使用 import 语句
2 public class PackDemo1 {
3 public static void main(String[] args) {
4 Date today = new Date();
5 System.out.println("The date is " + today);
6 }
7
可以使用通配符导入包中的所有类。如果要使用同一包中的几个类,那么这种方法很有用,如清单 8 所示。
清单 8. import 语句与通配符一起使用
2 public class PackDemo1 {
3 public static void main(String[] args) {
4 Date today = new Date();
5 System.out.println("The date is " + today);
6 }
7
打包以供重用
在 Visual Basic 中,可以编写代码并将其构建为一个动态链接库(DLL),在文件系统中,DLL 由扩展名为 .dll 的文件表示。其他程序可以引用 DLL 以使用 DLL 中包含的代码。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 包的一部分,那么编译器将为最终的 .class 模块创建一个目录树。该目录树就建立在包名称的基础上。本例中目录树为 com\mycompany\test,并且包名称中的点号被转换为目录边界。
现在打开一个命令提示符窗口,然后创建一个目录(例如 c:\javapack)。切换到该目录( cd javapack )。使用您最喜欢的文本编辑器,将清单 9 中的代码添加到一个名为 Test.java 的新文件中。
清单 9. 使用包的例子
2 public class Test
3 {
4 public static void main(String[] args) {
5 System.out.println("In test");
6 }
7 }
8
现在使用下面的命令编译 Test.java。( -d 选项应指向您为这个例子创建的目录):
现在在 c:\javapack 目录下应该有一个名为 com 的子目录。事实上,您可以看到编译所产生的 comTest.class 文件的完全限定名称是 Test.class。注意包名称( com.mycompany.test )如何转换为对应目录结构(com\mycompany\test),该目录结构以您使用 -d 选项指定的目录作为根目录。
下面我们将展示如何打包 Test.class 以方便其他类重用。从 c:\javapack 目录运行下列命令:
这个命令将创建一个名为 Test.jar 的文件,它包含 com 子目录下的所有类。
运行下列命令来使用 Test.jar 文件中的类:
注意您必须使用全限定类名来从命令行运行该命令,而且还要注意使用 -classpath 选项来指向 Test.jar 文件这个方式。或者,您可以把 Test.jar 文件添加到 CLASSPATH 环境变量中,该变量是分号分隔的 JAR 文件和目录的列表,Java 编译器和 JVM 使用这些目录查找需要加载的类。
其他区别
我们已经了解了 Java 语言和 Visual Basic 的主要语法区别。其他一些区别是:
全局变量:与 Visual Basic 不同,Java 语言不提供任何方法来声明全局变量(或方法)。
GoTo :尽管 Java 语言将 goto 保留为一个关键字,但它没有与 Visual Basic 使用的类似的 GoTo 语句。
自由放置变量:只要需要,您可以在任何地方声明 Java 变量。您不需要在程序块的顶部对变量分组,而在 Visual Basic 中必须这样。
继承:Visual Basic 不允许定义扩展其他类功能的类。Java 语言允许定义继承除类的私有成员外的所有成员的类。这些新类可以扩展它们继承的类的行为并且替换被继承成员的行为。(在下一节将了解有关继承的更多信息。)
面向对象编程简介
Java 编程语言是面向对象的语言。Visual Basic 具有许多对象特性,但不是严格的面向对象语言。本节将讨论如何用 Visual Basic 构建一个类,然后讨论如何用 Java 语言构建等价的类。
使用类
您可以将 类视为您定义的数据类型。类的变量实例称为 对象。与其他变量类似,对象具有一个类型、一组属性和一组操作。对象的类型由从中实例化对象的类表示的。对象的属性表示其值或状态。对象的操作是可能的函数集,您可以在对象中调用这些函数来更改其状态。
请考虑 Visual Basic 的 Integer 基本数据类型,它表示一个整数。您可以使用该类型名称创建整数实例的变量。每个 Integer 变量都有一个属性,表示变量存放的整数。每个 Integer 变量还有相同的操作集,您可以使用该操作集来更改变量的状态(或值)。对 Integer 变量执行的一些操作包括加 (+)、减 (-)、乘 (*)、除 (\) 和取模 (Mod)。
定义 Visual Basic 类
现在,我们来研究您可能要开发自己类型的情形――自己开发的类型是一个 Visual Basic 语言不支持作为基本类型的复杂对象。假定您是为财务机构开发软件的小组成员,您的工作是开发表示一般银行帐户的代码。一个银行有很多帐户,但每个帐户都有相同的基本属性和操作的集合。特别是,帐户包含一个余额和一个 ID 号。清单 10 中的 Visual Basic 代码定义了帐户类。它定义了三种操作: Deposit 、 Withdrawal 和 InitAccount (用于初始化余额和帐号)。注意如何通过使用私有变量和您定义的称为 Balance 的属性允许该类的用户获得余额。
清单 10. 定义 Visual Basic 类
2
3 Private theAccountNumber As Integer
4
5 Public Sub InitAccount (number As Integer, initBal As Currency)
6
7 theAccountNumber = number
8
9 theBalance = initBal
10
11 End Sub
12
13 Public Sub Deposit (amount As Currency)
14
15 theBalance = theBalance + amount
16
17 End Sub
18
19 Public Sub Withdrawal (amount As Currency)
20
21 theBalance = theBalance - amount
22
23 End Sub
24
25 Public Property Get Balance() As Currency
26
27 Balance = theBalance
28
29 End Property
30
31 Public Property Get AccountNumber() As Integer
32
33 AccountNumber = theAccountNumber
34
35 End Property
36
37
定义 Java 类
清单 11 用 Java 语言实现 Account 类。
清单 11. Java 语言 Account 类
2
3 private double balance;
4
5 private int number;
6
7 public Account(int number, double balance) {
8
9 number = argNumber;
10
11 balance = argBalance;
12
13 }
14
15 public void deposit(double amnt) {
16
17 balance += amnt;
18
19 }
20
21 public void withdrawal (double amnt) {
22
23 balance -= amnt;
24
25 }
26
27 public double getBalance() {
28
29 return balance;
30
31 }
32
33 public int getNumber() {
34
35 return number;
36
37 }
38
39 }
40
41
如果您所见,定义 Java 类与定义 Visual Basic 类不同。这两种语言各自的帐户类反映了以下主要区别:
在 Java 代码中,您不需要单独的方法来初始化 Account 的实例。使用 构造函数即可。如其名称的含义,您使用它来构建类的实例。构造函数必须与定义它的类具有相同的名称,而且它可以带参数。您可以为一个类创建多个构造函数,如果您没有提供构造函数,则您会自动使用不带参数的默认构造函数。您可以像清单 11 那样使用构造函数:
Account myAccount = new Account(12345, 0.00);
与 Visual Basic 不同,Java 语言对属性没有特殊规定。按照约定,Java 属性是私有域,您通常提供一组称为访问器(accessor)的方法来访问包含属性的域。返回属性值的方法称为 getter,设置属性值的方法称为 setter。下面是 setter 方法的例子:
public void setIntProperty(int argIntProperty) {intProperty = argIntProperty;}
在 Visual Basic 中,类成员的默认访问修饰符不是 public (稍后介绍访问修饰符的更多信息)。
使用对象的带来的好处
在诸如 Java 语言之类的面向对象语言中使用类和对象会带来三个主要好处: 封装、 继承和 多态 :
封装(或信息隐藏)是指将对象视作一个“黑盒”;即使用对象时,无须了解(或关心)对象是如何实现的。通过类中定义的方法(运算符)所定义的接口使用对象,可以确保更改类实现而不破坏使用该类对象的任何代码。
多态是指将不同特性与相同名称关联的能力,以及根据上下文选择正确特性的能力。最常见多态例子是方法重载,您可以定义使用名称相同的几种方法,只要这些方法所带的参数不同即可。
继承 是指通过编写扩展现有类的新类来重用代码。例如,假设您希望编写新类来表示支票帐户。因为支票帐户是一个特殊种类的银行帐户,所以可以编写一个扩展 Account 类(或作为 Account 类的子类)的 CheckingAccount 类。然后, CheckingAccount 类自动获得 Account 类的状态和所有运算符(函数)。您只需要添加特定于 CheckingAccount 类的新状态和运算符即可。例如,您可以添加一个 cashCheck() 函数执行兑现支票帐户写的支票的操作。如有必要,还可以更改子类的状态和行为。例如,可能允许用户透支其支票帐户,因此您需要重载透支函数。
深入了解 Java 类
既然您了解面向对象编程框架中类和对象的一般角色,那么您应深入了解有关 Java 平台的类结构和实现的以下细节:
类成员:类成员总是 域或 方法。域代表数据,而方法代表操作。类可以定义任何数量的成员。
访问修饰符:使用 访问修饰符声明类成员,访问修饰符指定在定义该成员的类之外对成员的可访问性。例如,绝对不能访问声明为 private 的成员,但可访问声明为 public 的成员。
对象:类仅仅是定义而已。代码中实际使用的是称为 对象的类的实例。您将了解如何从类创建对象。
构造函数: 构造函数是用于创建对象的特殊运算符。一般来说,如果不能创建类的对象,则这个类就没有多大用处。构造函数非常重要,因为它们提供创建新类实例的能力。
this 关键字 :Java 对象隐含引用自身。了解 this 关键字如何引用自身很重要。
类成员
就 成员而言,Java 类是一个定义属性和操作的独立代码模块。域和方法都是成员的例子。
域是在类的内部声明的变量。Java 域有两种变体: 实例变量和 类变量。实例变量与类的每个实例相关,每个实例都有自己的实例变量副本。类变量(用 static 关键字声明)与整个类相关,类与单个类变量共享所有的类实例。例如, BankAccount 中的 balance 域是一个实例域,因为每个 BankAccount 实例有自己的 balance,它独立于其他每个 Account 对象的 balance。在另一方面,您可以声明一个 interest 域作为类域,因为每个 BankAccount 对象都采用相同的利率。
方法是类内部的函数。Java 方法有两种变体: 实例方法和 类方法。每个类实例获得它自己实例方法的副本,但只有一个类方法的副本,所有类实例都共享它。您可以使用 static 关键字声明类方法。使用实例方法对实例变量进行操作,使用类方法对类变量进行操作。例如, BankAccount 中的 deposit() 方法是一个实例方法,因为每个 BankAccount 都有自己的 balance 域, deposit() 方法对该域进行更改。您可以将 setInterest() 方法声明为类方法,因为每个 BankAccount 都共享一个 setInterest() 方法可以更改的 interest 域。
清单 12 中的 BankAccount 有五个域: balance (是实例域)、 interest (是类域)。三个成员是方法: deposit() 和 withdraw() 是实例方法, setInterest() 是类方法。注意,您使用对象名称来访问实例变量,使用类名称来访问类成员。
清单 12. BankAccount 类
2
3 public class BankAccount {
4
5 private float balance; // an instance field
6
7 private static float interest; // a class, or static, field
8
9 // an instance method
10
11 public void deposit(float amount) {
12
13 balance += amount;
14
15 }
16
17 // an instance method
18
19 public void withdraw(float amount) {
20
21 balance -= amount;
22
23 }
24
25 // a class, or static, method
26
27 public static void setInterest(float interestRate) {
28
29 interest = interestRate;
30
31 }
32
33 public static void main(String[] args) {
34
35 // create a new account object
36
37 BankAccount account = new BankAccount();
38
39 // deposit $250.00 into the account
40
41 account.deposit(250.00F);
42
43 // set interest rate for all BankAccount objects
44
45 BankAccount.setInterest(5.0F);
46
47 }
48
49 }
50
51
访问修饰符
与 Visual Basic 类似,Java 语言允许设置类成员的可视性。Java 成员使用 public 修饰符表明可以在类的内部和外部访问某个成员。Java 使用 private 修饰符表明只能在类的内部访问该成员。在类的外部是不能访问私有成员的。
请再次考虑 BankAccount 类。假定您希望使用 BankAccount 对象的程序员通过使用 deposit() 和 withdraw() 方法来更改余额。您要将这些方法声明为 public ,因此可以在 BankAccount 类的代码之外调用这些方法。但是,您不希望其他程序员直接更改 balance 域,因此 balance 域的访问修饰府要采用 private 。
您可能搞不明白默认访问级别(即没有使用 public 或 private 修饰符声明的类成员的访问级别)是哪个级别。您可能认为默认访问级别是 public ,即 Visual Basic 中的默认访问级别。而实际上,Java 语言中的默认访问级别称为 包访问,因为只有同一个包中的类才能访问这些类成员。如果要将某个成员声明为包访问,请不要使用访问修饰符关键字
Java 可以定义一个以上的访问级别,称为 protected 。如果希望某个成员可以在子类中访问,则请使用 protected 修饰符。创建对象
如果您研究一下清单 12 中 BankAccount 类的 main() 方法,就会明白下列代码创建了一个新 BankAccount 对象:
BankAccount account = new BankAccount();
首先,声明一个类型为 BankAccount 的对象(即变量)。您能会猜到, new 关键字留出足够的内存来创建一个新对象。新对象实际上由以下语句创建的: BankAccount() 。该语句看起来像一个方法调用。然而,清单 12 没有声明此名称的方法,因此您可能想知道这个语句是干什么的。
实际上,这个语句是一个构造函数调用。没有构造函数就不能创建 Java 对象,因此,如果您编写的类没有构造函数,编译器就创建一个默认的构造函数。这就是可以调用 BankAccount() 的原因,即使您在 BankAccount 类中没有显式编写构造函数也是如此。
Visual Basic 支持构造函数的概念,方法是允许为每个类定义一个称为 Class_Initialize 的过程,与 Java 语言不同,它不允许传递参数。
Java 构造函数没有返回类型;所有构造函数都返回定义它的类的新对象。每个 Java 构造函数都必须与声明它的类有完全相同的名称。否则,构造函数声明与方法声明几乎一样。特别是,构造函数可以带参数,就像 Java 方法一样。
严格地说,构造函数不是一种方法,因为方法是类成员,而构造函数不是。类成员与域和方法一样,都在子类中继承。而构造函数是绝对不会被继承的。
显式引用
Java 语言使用 this 关键字引用当前的对象。您可以使用 this 关键字显式引用当前类中的域、方法和构造函数。
this 关键字的常见用法是解决变量范围问题。例如, BankAccount 类有一个称为 balance 的字段。我们假定您要编写一个称为 setBalance(float balance) 的方法,它设置对象的 balance 域。问题出在 setBalance(float balance) 域的内部,当您引用 balance 时,您实际上引用 balance 参数,而不是 balance 域。您可以通过使用 this 关键字显式引用该域,如清单 13 所示。
清单 13. "this" 关键字
2
3 public void setBalance(float balance) {
4
5 this.balance = balance;
6
7 }
8
继承
继承是面向对象编程最重要的优点之一。为了最有效地使用继承,正确了解继承非常重要。继承涉及以下重要概念:
extends 关键字 :继承在声明类时定义。使用 extends 关键字来指定您所编写的类的超类。
构造函数:构造函数不在子类中继承,但经常在子类构造函数中调用超类的构造函数。
重载/覆盖: 重载是编写名称相同但参数不同的几个方法。 覆盖是指更改子类中继承的方法的实现。
Object 类 :所有 Java 对象都最终继承自 Object 类,该类定义每个 Java 一定要具备的基本功能。
接口: 接口是一种行为的描述,但并不实现该行为。
扩展类
在 Visual Basic 中,类不能从任何其他类继承,但 Java 语言允许单继承。继承是重用代码的一种方式。当类 A 继承自(或 extends)类 B 时,类 A 就自动继承类 B 的所有 public 和 protected 成员。如果类 A 与类 B 在同一个包中,则类 A 还继承所有具有默认(或 包)访问权的成员。重要的是要注意,子类始终不会继承它们扩展的类的私有成员。
一旦您扩展了某个类,就可以添加新域和方法,这些新域和方法定义将新类与超类区别开来的属性和操作。另外,您可以 覆盖在子类中必须具有不同行为的超类的操作。
定义类时,可以显式扩展这个类。要扩展类,在该类名后跟一个 extends 关键字,然后是要扩展的类的名称。如果您没有显式扩展类,则 Java 编译器自动扩展类 Object 。这样,所有 Java 对象最终都是 Object 类的子类。
一个扩展例子
假定您要创建一个新的 CheckingAccount 类。 CheckingAccount 是一个特殊类型的 BankAccount 。换句话说, CheckingAccount 与 BankAccount 有相同的属性和操作。但是, CheckingAccount 还新增了一个操作――兑现支票。因此,您可以定义 CheckingAccount 类,使它扩展 BankAccount 并添加了一个 cashCheck() 方法,如清单 14 所示。
清单 14. 扩展类
2
3 public void cashCheck(float amount) {
4
5 withdraw(amount);
6
7 }
8
9 }
10
11
子类构造函数
构造函数不是真正的类成员,因而不对构造函数进行继承。 BankAccount 构造函数创建 BankAccount 对象,因此,不能在 CheckingAccount 类中使用它来创建 CheckingAccount 对象。但是,可以从超类中使用构造函数来初始化继承这个超类的子类的一部分。换句话说,您经常需要在子类构造函数中调用超类构造函数来对子类对象进行部分初始化。使用 super 关键字,后接代表要调用的超类构造函数的参数的参数化列表就可以做到这一点。如果您要在某个构造函数中使用 super 关键字来调用超类的构造函数,则它必须是构造函数体中的第一个语句。
例如,您需要编写 CheckingAccount 构造函数来初始化 CheckingAccount 对象。您要使用初始余额来创建 CheckingAccount 对象,因此您传入的是金额。这恰恰与 BankAccount 类中的构造函数非常类似,因此,您使用该构造函数来为您做所有这些工作,如清单 15 所示。
清单 15. 子类构造函数
2
3 public CheckingAccount(float balance) {
4
5 super(balance);
6
7 }
8
9 public void cashCheck(float amount) {
10
11 withdraw(amount);
12
13 }
14
15 }
16
17
您还可以使用 super 关键字来显式地在子类中引用超类的成员。
重载和覆盖
Java 语言允许定义几个同名的方法,只要这些方法的参数不同即可。例如,清单 16 又定义了一个 cashCheck() 方法,该方法带有两个参数,一个是要兑现的支票金额,一个是收取服务的费用。这就叫做方法 重载。
清单 16. 方法重载
2
3 withdraw(amount);
4
5 }
6
7 public void cashCheck(float amount, float fee) {
8
9 withdraw(amount+fee);
10
11 }
12
13
在创建子类时,您经常要 覆盖 从超类继承的方法的行为。例如,假定 CheckingAccount 和 BankAccount 之间的一个不同之处是从 CheckingAccount 提取金额时要收取费用。您需要在 CheckingAccount 类中覆盖该 withdraw() 方法,以便收取 0.25 美元费用。可以通过使用 super 关键字,根据 BankingAccount 的 withdraw() 方法来定义 CheckingAccount 的 withdraw() 方法,如清单 17 所示。
清单 17. 方法覆盖
2
3 super.withdraw(amount+0.25F);
4
5 }
6
7
Object 类
Object 类是 Java 类层次结构中的特殊类。所有 Java 类最终都是 Object 类的子类。换句话说,Java 语言支持集中式根类层次结构, Object class 类是该层次结构中的根类。Visual Basic 中也存在相似的概念, Object 变量以后可以实例化为任何类型的类。
由于所有 Java 对象都继承自 Object 类,所以,可以为任何 Java 对象调用在 Object 中定义的方法,获得类似的行为。例如,如果 Object 类定义了 toString() 方法,该方法返回代表该对象的 String 对象。您可以为任何 Java 对象调用 toString() 方法,获得该对象的字符串表示。大多数类定义都覆盖 toString() 方法,以便它可返回该特定类的特定字符串表示。
在 Java 类层次结构根部的 Object 还有一个含义,即所有对象都能向下强制类型转换(cast down)到 Object 对象。在 Java 语言中,您可以定义获得 Object 类的对象的数据结构,这些数据结构可以存放任何 Java 对象。
接口
我已经提过,Java 类只允许单继承,这意味着 Java 类只能扩展一个类。Java 语言的设计者感到多重继承太复杂,因此他们改为支持 接口。 接口类似于不能实例化的方法,它定义有方法,但实际上并不实现这些方法。
声明接口的方法与声明类相似,不同之处是使用 interface 关键字而非 class 关键字。接口可以扩展任意数量的超接口。接口内的方法不包含实现。接口方法只是简单的方法定义;它们没有方法体。这与 Visual Basic 使用的接口概念相同;接口由属性和方法声明组成,没有代码。
Account 接口
清单 18 中的代码展示如何为银行帐户编写定义一组功能的基本 Account 接口。注意,在接口中声明的方法没有方法体。
清单 18. Account 接口
2
3 public static final float INTEREST = 0.35F;
4
5 public void withdraw(float amount);
6
7 public void deposit(float amount);
8
9 }
10
11
实现接口
Java 类只可扩展一个类,但它可以 实现任意数量的接口。当类实现接口时,它必须实现该接口中定义个每个方法。
清单 19 定义了一个实现 Account 接口的 SavingsAccount 类。由于 Account 接口定义两个方法―― withdraw(float amount) 和 deposit(float amount) ,所以, SavingsAccount 类必须提供这两个类的实现。 SavingsAccount 类仍可以扩展另一个类,而且它可以实现任何其他接口,只要它们定义的成员与 Account 接口不同即可。
清单 19. 实现接口
2
3 private float balance;
4
5 public SavingsAccount(float balance) {
6
7 this.balance = balance;
8
9 }
10
11 public void cashCheck(float amount, float fee) {
12
13 withdraw(amount+fee);
14
15 }
16
17 public void withdraw(float amount) {
18
19 balance += balance;
20
21 }
22
23 public void deposit(float amount) {
24
25 balance -= balance;
26
27 }
28
29 }
30
31
小结
到此为止,您已经掌握了 Java 语言的基本组成,并能编写简单的 Java 程序。特别是,您应能:
编写带有 main() 方法的 Java 类,编译并运行它。
编写 Java 接口并编译它。
为您的类编写一个或多个构造函数。
编写扩展另一个类并实现一个或多个接口的类。
通过 new 关键字和构造函数调用来创建和使用对象。
您应有足够的信心来研究和编写更高级的 Java 代码。最好使用 Java 平台自带的类着手进行。获得使用这种语言经验的非常好的方法是浏览 API 文档,然后使用这些类开始编写程序。
ASP/COM 和 J2EE 应用程序模型
接下来研究不同的应用程序体系结构,您可以使用这些体系结构开发 J2EE 应用程序并将其与相应的 ASP/COM 应用程序体系结构关联起来。
使用 Java Servlets 编程:基础知识
编写 Java Servlets 就可以对来自 URL 的请求进行可编程控制。典型的 servlet 调用类似于以下步骤:
客户机向 Web 服务器发出请求,将 servlet 命名为 URL 的一部分 ―― 例如,
<FORM action="/myWebApp/LoginServlet" method="POST"> 。
Web 服务器将该请求转发给应用程序服务器,该服务器查找 servlet 类的实例。
Web 容器调用 servlet。(servlet 的一个实例被加载到内存中,每个请求都调用不同线程上的一个实例。)
注意,HTML 表单中 servlet 的 URL 包括 servlet 的名字和称为 上下文根(在上例中是 /myWebApp )的前缀。上下文根大致等价于 IIS 虚拟目录。
图 2 说明了这些步骤。
图 2. Servlet 调用
Servlets 扩展 HttpServlet 类。如有需要,可以覆盖 下列 HttpServlet 类的方法:
init() :在应用程序服务器加载 servlet 时调用
destroy() :在应用程序服务器卸载 servlet 时调用
doGet() :通过 HTTP GET 调用 servlet 时调用
doPost() :通过 HTTP POST 调用 servlet 时调用
编写 servlet 涉及编写代码来处理 HTTP 请求,处理任何参数并直接返回任何内容,或委派另一种资源(如 JSP 页面)来处理响应。不建议直接从 servlet 返回 HTML,因为管理 Java 类内的 HTML 代码很不灵活。
当应用程序服务器调用 doGet() 或 doPost() 方法时,以下两个对象将作为参数传递进来:
HttpServletRequest 允许访问任何请求参数以及 HTTP 请求中引发 servlet 调用的其他信息。
HttpServletResponse 充当返回到客户机的沟通渠道,允许 servlet 直接返回内容或返回其他 HTTP 返回代码(错误、重定向等)。
清单 20 和 21 展示了两个 Hello World servlet。清单 20 直接返回内容,清单 21 使用 JSP 页面返回内容。
清单 20. HelloWorld servlet:直接返回内容
2 /**
3 * Handles all HTTP POST requests
4 *
5 * @param request Object that encapsulates the request to the servlet
6 * @param response Object that encapsulates the response from the servlet
7 */
8 public void doPost(
9 javax.servlet.http.HttpServletRequest request,
10 javax.servlet.http.HttpServletResponse response)
11 throws ServletException, IOException {
12 try {
13 PrintWriter out = response.getWriter();
14 out.println("<html>");
15 out.println("Hello World");
16 out.println("</html>");
17 } catch (Throwable t) {
18 ...
19 }
20 }
21 }
22
注意,您从响应对象获得 PrintWriter ,并一次一行将 HTML 输出到客户机。
清单 21. HelloWorld servlet:使用 JSP 页面返回内容
2 /**
3 * Handles all HTTP POST requests
4 *
5 * @param request Object that encapsulates the request to the servlet
6 * @param response Object that encapsulates the response from the servlet
7 */
8 public void doPost(
9 javax.servlet.http.HttpServletRequest request,
10 javax.servlet.http.HttpServletResponse response)
11 throws ServletException, IOException {
12 try {
13 RequestDispatcher rd = getServletContext().getRequestDispatcher("helloworld.jsp");
14 rd.forward(request, response);
15 } catch (Throwable t) {
16 ...
17 }
18 }
19 }
20
RequestDispatcher 是您希望将请求转发给的资源的包装器。注意,要同时包括原始请求和响应对象,以便目标资源可以访问它们。从 getServletContext() 返回的 ServletContext 允许服务器与底层应用程序进行通信来获得 RequestDispatcher 。注意,所有 servlet 都通过 etServletContext() 方法访问其 ServletContext 。
使用 JSP 技术编程:基础知识
使用 JSP 技术您就具有使用 Java 语言编写服务器端脚本的能力。在 JSP 页面返回到客户机前,它是一个组合页面,由 HTML 和应用程序服务器处理的 Java 代码组成。在页面返回到客户机前,应用程序服务器处理嵌入代码以生成静态内容。与 .aspx 文件类似,JSP 页面通常与 HTML 文件很相似,只是增加了一些额外的标签和 Java 代码片断。
在第一次请求 JSP 页面时,应用程序服务器将每个 JSP 页面转换为特殊的 servlet。该 servlet 编译并加载到内存中。只要 JSP 源未被修改,这个 servlet 就对要获得该页面的请求提供服务。在源修改后,此过程再次开始,并生成一个新版本的 servlet。
您可以在 JSP 页面中使用几种特殊的 JSP 标签,用户还可以定义自己开发的标签的行为。您还可以将大段的 Java 代码添加到页面的不同部分。J2EE 运行时环境创建了许多变量,称为 隐含变量―― 可用于这些 Java 代码的片断。隐含变量的例子有:
request:与页面的特定调用相关的 HttpServletRequest
response:与页面的特定调用相关的 HttpServletResponse
out:与 HttpServletResponse 相关的 PrintWriter
清单 22 展示了 JSP 页面的一个例子,该页面既包含 HTML 也包含 Java.代码。 标签之间的 Java 代码部分称为 scriptlet。
清单 22. HelloWorld servlet:使用 JSP 页面返回内容
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 端,“意大利面条式”代码方法使用单个 .aspx 文件来同时包含应用逻辑和 HTML 表单。(不建议对现实中的例子采用这种方法,因为这样所有表示逻辑和应用逻辑都将是单个文件,从而您就不能重用用于验证用户凭证的代码。)该代码的轮廓看起来类似清单 23 中所示的代码。(为简单起见,我们省略了所有错误处理代码。)
清单 23. ASP 中的“意大利面条式”代码
2 '--Code to validate users credentials goes here
3 ...
4 END FUNCTION %>
5 <html>
6 <head>
7 <title>Login example</title>
8 </head>
9 <body>
10 <% IF (REQUEST.SERVERVARIABLES("REQUEST_METHOD") = "GET") THEN %>
11 <form action="login.asp" method="post">
12
13 <!-- login form fields go here -->
14
15 </form>
16 <% ELSE userName = REQUEST.FORM("username") password = REQUEST.FORM("password")
17 IF (ValidateUser(userName, password)) THEN
18 RESPONSE.REDIRECT("mainpage.asp") ELSE ... END IF END IF %>
19 <font color="red"><b><%=LoginMessage%></b></font>
20 </body>
21 </html>
22
正如清单 24 所示,您可以在 J2EE 中采用相同的方法,使用单个 JSP 页面来同时包含表单和应用逻辑。
清单 24.J2EE 中的“意大利面条式”代码
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
与 ASP 模型类似,JSP 模型不是事件驱动的,因此您需要检查表单是否被发送回去了,方法是检查该请求,并且如果不是 POST 请求时则添加表单的 HTML。如果它是 POST 请求,您将使用在 JSP 中声明的一个方法来验证登录。注意,使用 )对方法无效。还要注意如何使用 if/then/else 编程结构来方便地添加或去除较大的 HTML 块。与在 ASP 例子中一样,不推荐将此方法用于 J2EE 开发。表示代码(HTML)和应用逻辑的混合仅允许很少的重用,并且使得代码难于维护。
改进的“意大利面条式”代码
在 ASP 端,可以以前一个例子为基础建立一种更好的方法,不过除了 login.asp 文件外,还使用只包含验证逻辑的一个 include ASP 文件。这样您就可以重用其他 ASP 文件中的代码。
J2EE 端的一种更好方法是将应用逻辑转移到一个 Java Servlet,从而使 JSP 页面仅限于使用 HTML 组件。现在验证逻辑独立于显示表单的页面,这是一种改进,而 JSP 页面仅限于使用 HTML 组件也是一种改进。清单 25 显示了如何将应用逻辑放到 servlet 中从而简化 JSP 页面。
清单 25. J2EE:JSP 页面中改进的“意大利面条式”代码
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 中改进的“意大利面条式”代码
2 /**
3 * Handles all HTTP POST requests
4 *
5 * @param request Object that encapsulates the request to the servlet
6 * @param response Object that encapsulates the response from the servlet
7 */
8 public void doPost(
9 javax.servlet.http.HttpServletRequest request,
10 javax.servlet.http.HttpServletResponse response)
11 throws ServletException, IOException {
12 try {
13 String userName = request.getParameter("username");
14 String password = request.getParameter("password");
15 if (validateUser(userName, password)) {
16 response.sendRedirect("mainpage.jsp");
17 }
18 ...
19 } catch (Throwable t) {
20 ...
21 }
22 }
23 private boolean validateUser(String userName, String password) {
24 ...
25 }
26 }
27
清单 26 中的 servlet 是表单提交的目标,并且充当一个控制器――处理用户输入并基于该输入调出适当的页面。注意 HttpServlet 父类允许您通过提供可重载的方法( doGet() 和 doPost() )来同时处理 GET 和 POST 请求。
这种方法的主要缺点在于,凭证验证代码(它很可能要访问数据库)是 J2EE 例子中 servlet 的一部分。如果不同的页面需要使用这个逻辑,您就必须重复它。重复的代码更难于维护和更易于出错,因为您必须跟踪多个副本。
模型-视图-控制器(MVC)方法
下面我们将展示如何对这个例子使用更纯的 MVC 方法。在 ASP 端,这体现为将凭证验证逻辑转移到一个单独 COM 组件,然后您在 ASP 文件中进行访问。在 J2EE 端,凭证验证代码将转移到一个单独的类中,然后从 servlet 访问该类。清单 27 中的代码片断显示了该 servlet 的外观。
清单 27. J2EE: Java Servlet 中的 MVC
2 public class LoginServlet extends HttpServlet {
3 /**
4 * Handles all HTTP POST requests
5 *
6 * @param request Object that encapsulates the request to the servlet
7 * @param response Object that encapsulates the response from the servlet
8 */
9 public void doPost(
10 javax.servlet.http.HttpServletRequest request,
11 javax.servlet.http.HttpServletResponse response)
12 throws ServletException, IOException {
13 try {
14 String userName = request.getParameter("username");
15 String password = request.getParameter("password");
16 UserValidation user = new UserValidation();
17 if (user.validate(userName, password)) {
18 response.sendRedirect("mainpage.jsp");
19 }
20 ...
21 } catch (Throwable t) {
22 ...
23 }
24 }
25 }
26
清单 28 显示了位于一个单独的类中的凭证验证代码。
清单 28. J2EE:用户验证类中的 MVC
2 public class UserValidation {
3 public boolean validate(String username, String password) {
4 ...
5 }
6 }
7
凭证验证在 servlet 使用的类中进行。现在该 servlet 不必直接了解验证如何进行。只要您保持相同的公共接口不变,就可以随意更改 UserValidation 而不需改变 servlet 代码。您还可以在非 Web 应用程序中重用该代码。
其他 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 组件能够使用应用服务器中的服务来管理(数据库或其他)事务,管理方法级的安全,以及提供群集和高速缓存。将此类任务委托给应用服务器,您可以把精力集中在业务逻辑上,而不必担心系统级的问题。
J2EE 数据访问
下面我们将介绍 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 例子
2 Class.forName("COM.ibm.db2.jdbc.app.DB2Driver");
3 // Step 2. Identify the database and connect to it
4 Connection conn = DriverManager.getConnection("jdbc:db2:SAMPLE","userid","password");
5 // Step 3. Execute query
6 Statement stmt = conn.createStatement();
7 stmt.execute("SELECT * FROM USERID.EMPLOYEE");
8 // Step 4. Get the results
9 ResultSet rs = stmt.getResultSet();
10 while (rs.next()) {
11 String firstCol = rs.getString(1);
12 int secondCol = rs.getInt(2);
13 ...
14 }
15 // Step 5. Disconnect from the database
16 conn.close();
17
清单 29 中的 Step 1 动态地加载适当的驱动程序。这个例子使用的是 DB2;其他 JDBC 驱动程序具有不同的完全限定名称。Step 2 提供特定于驱动程序的 String 指出要连接到哪个数据库。每种 JDBC 驱动程序都有自己的 String 格式。DB2 使用的格式是 "jdbc:db2:DBNAME" ,因此在此例中系统要连接到一个名为 SAMPLE 的数据库。例子中还提供了身份验证信息,以便数据库服务器能够对连接请求进行身份验证。在 Step 4 中,注意例子中如何循环遍历结果集,直到 next() 方法返回 false 。您还必须知道每列的类型,并对每列和每种类型调用适当的 getXXXX(n) 方法。还要注意您传递给 getXXXX(n) 方法的整数是列编号。
在 ASP 页面使用 ADO 但没有连接池时,以上方法与典型的 ASP 页面采用的方法很相似,因为程序员负责打开和关闭数据库连接。
连接池
连接池通过对可以同时打开的数据库连接数目规定上限,可以增加应用程序的可伸缩性,因而防止由于过多的连接请求而造成数据库服务器的过度运行。对于 ASP 来说,如果您选择的数据库(如 DB2、SQL Server 或 Oracle)有 OLEDB 提供程序,则可以利用连接池,从而就不必直接管理数据库连接。
有了 JDBC,您可以将自己从直接管理连接的工作中解放出来。J2EE 应用程序服务器被要求为它们支持的所有数据库提供连接池。它们不使用某些高级的 JDBC API 调用来完成该任务。当您的代码在应用程序服务器的监控下运行时,您就不必直接与数据库服务器交互,以便为每个配置的数据库维护连接池。然后,应用程序从该池中请求连接,并在使用完这些连接之后,将其返回到该池。大多数应用程序服务器都有相当高级的选项对池进行管理,包括“收回”连接的能力,即从出错的应用程序(没有在给定时间内将连接返回池中)取回连接。
使用连接池的应用程序的典型流程如下:
查找应用服务器资源库中的 DataSource (用于访问特定的连接池)。(每个应用服务器都有一个资源库,其中包含所有已配置的连接和其他资源,比如消息队列和 SMTP 提供程序。您可以使用名为 JNDI 的标准 J2EE API 来访问资源库。)(这个步骤只需执行一次。)
从池中获得连接(在需要时提供身份验证信息)。
执行查询和/或更新。
处理结果。
将连接返回池中。
清单 30 展示了使用连接池的一个例子。
清单 30. 一个连接池例子
2 InitialContext ic = new InitialContext();
3 DataSource ds = (DataSource) ic.lookup("jdbc/MyDS");
4 // Step 2. Get a connection from the pool
5 Connection conn = ds.getConnection();
6 // Step 3. Execute query
7 Statement stmt = conn.createStatement();
8 stmt.execute("SELECT * FROM USERID.EMPLOYEE");
9 // Step 4. Get the results
10 ResultSet rs = stmt.getResultSet();
11 while (rs.next()) {
12 String firstCol = rs.getString(1);
13 int secondCol = rs.getInt(2);
14 ...
15 }
16 // Step 5. Return connection to the pool
17 conn.close();
18
Step 1 假设应用服务器已经配置了名为 "jdbc/MyDS" 的 DataSource 。这样就封装了一个特定的 JDBC 驱动程序和数据库。您还可以向 DataSource 定义添加身份验证信息,这样应用程序就不必提供该信息。您可以使用 InitialContext 类(它是 JNDI API 的一部分)来查找应用服务器控制的各种资源。Step 3 至 Step 5 与前一个例子相同,不过要注意 Step 5 中对 close() 方法的调用仅把连接返回池中,而不是关闭该数据库连接。
J2EE 应用程序状态管理
在编写 J2EE Web 应用程序时,要管理应用程序的状态,有一组丰富的类和接口可供选择。我们将介绍这些类和接口,并讨论如何在 Java Servlets 和 JSP 页面中使用这些类和接口。不过,我们将首先介绍 范围的概念,它是理解 J2EE 中的应用程序状态管理的关键。
范围
从程序员的角度看,状态管理涉及临时存储数据和在需要时检索它们。在 J2EE 中,您可以选择多个“存储位置”,每个位置具有它自己规则,这些规则控制任何存储的数据得有效时间。持续时间从处理某个特定页面所需的时间(临时存储一些数据),到应用程序运行生命期(长期存储数据)不等。J2EE 中的“存储位置”选择称为特定存储请求或检索的 范围。该范围决定了您将把数据附加到哪些 J2EE 对象,以及那些数据将在多长时间内可用。可用的范围是:
会话:这类似于 ASP 中的会话范围。只要会话还是活动的,您就可以在该用户会话范围内放置任何 Java 对象并检索它。J2EE 应用程序使用 HttpSession 接口(类似于 ASP 中的 SESSION )。可以使用 String 作为标签将对象添加到会话中,并使用相同的标签来检索它。
请求:在 J2EE 中, HttpServletRequest 对象允许您向它附加数据,这非常类似 HttpSession interface 接口。当多个资源处理单个请求时,这是很有用的。例如,某个 Java Servlet 可能是一个 HTML 表单提交的目标,然后它将请求转发给一个 JSP 页面以完成它。在这个例子中,该 sevlet 能够向 HttpRequest 对象附加数据,并且 JSP 页面能够访问它。注意在这种场景中,该 servlet 和 JSP 页面都使用相同的 HttpRequest 对象。
应用程序:所有 J2EE Web 应用程序在部署之前都打包到一个具有 .war 扩展名的文件中。该文件的格式是标准的,因此您可以把同一个应用程序部署到不同的应用服务器。单个 .war 文件中的所有 J2EE 组件都被认为是同一个应用程序的组成部分,并且共享共同的应用程序上下文。这是通过 ServletContext 接口向开发人员公开的,这个接口(就像 HttpSession 和 HttpRequest 口一样)允许您附加和删除任何 Java 对象。只要应用程序还在运行,添加到 ServletContext 的项就可用,并且会在单独会话的创建和销毁过程中保留下来。这类似于 ASP 中的 APPLICATION 对象。
页面:页面上下文在处理单个页面的过程中可用。如,JSP 页面顶部的 Java scriptlet 能够在 PageContext 中放置对象,然后相同页面中的其他 scriptlet 就可以访问它。
管理应用程序状态
现在您已经对范围有了更好的了解,下面我们可以深入地讨论管理 J2EE Web 应用程序中的状态的机制。非常好的实践是,对于任何临时的状态存储,您都应该确定需要存储该数据多长时间,然后使用满足需要的、具有最短生存时间的范围。例如,假设您需要某个 JSP 页面中的数据,该 JSP 页面是从某个 servlet 转发的请求的目标。虽然会话状态和应用程序状态也满足您的需要,但是在这两种情况下,数据都会在使用完之后悬置在那里。这样不必要地增加了当前应用程序的资源需求。对于这个例子,请求范围能够满足需要,却不会在您不需要之后还将数据悬置在那里。
管理 JSP 页面中的状态
在 JSP 脚本环境中,所有范围对象都是用隐含变量来表示的。您可以在自己的 sciptlet 中使用这些变量,而不需要显式地声明它们。可以使用的隐含变量有:
session: 实现 HttpSession 接口的类的一个实例。
application:实现 HttpSession 接口的类的一个实例。
request:实现 HttpServletRequest 接口的类的一个实例。
pageContext: PageContext 类的一个实例。
清单 31 展示了如何在 JSP scriptlet 中针对不同的范围添加和删除对象。
清单 31. JSP 页面中的状态管理
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 中的状态管理
2 public void doGet(
3 HttpServletRequest request, HttpServletResponse response)
4 throws ServletException, IOException {
5 performTask(request, response);
6 }
7 public void doPost(HttpServletRequest request, HttpServletResponse response)
8 throws ServletException, IOException {
9 performTask(request, response);
10 }
11 /**
12 * Handles all HTTP GET and POST requests
13 *
14 * @param request Object that encapsulates the request to the servlet
15 * @param response Object that encapsulates the response from the servlet
16 */
17 public void performTask(
18 javax.servlet.http.HttpServletRequest req,
19 javax.servlet.http.HttpServletResponse res)
20 throws ServletException, IOException {
21 try {
22 // This is how you create a session if is has not been created yet
23 // Note that this will return the existing session if you've
24 // already created it
25 HttpSession session = req.getSession();
26
27 //This is how the application context is retrieved in a servlet
28 ServletContext application = getServletContext();
29
30 String foo = "I am a Foo";
31 session.setAttribute("Foo", foo);
32
33 // Place object in session scope
34 session.setAttribute("Foo", foo);
35 // Retrieve from session scope
36 String sessionFoo = (String) session.getAttribute("Foo");
37 // Place object in application scope
38 application.setAttribute("Foo", foo);
39 // Retrieve from application scope
40 String applicationFoo = (String) application.getAttribute("Foo");
41 // Place object in request scope
42 request.setAttribute("Foo", foo);
43 // Retrieve from request scope
44 String requestFoo = (String) request.getAttribute("Foo");
45
46 ...
47
48 } catch (Throwable t) {
49 // All errors go to error page
50 throw new ServletException(t.getMessage());
51 }
52 }
53
JSP 页面和 sevlet 中的会话之间的区别在于,在 sevlet 中,您必须显式地创建和检索它,而在 JSP 页面中,这是自动为您完成的。注意 pageContext 仅适用于 JSP 页面,而不适用于 servlet;还要注意 servlet API 如何提供了两个要覆盖的方法来处理 GET 和 POST HTTP 请求。这个例子提供了一个名为 performTask() 的通用方法来同时处理这两种类型的请求,不过您可以基于触发 servlet 调用的 HTTP 类型而使用不同的处理。
编程式导航
在代码中从一个资源导航到另一个资源的情况在 Web 应用程序中是相当普遍的。导航与应用程序状态密切相关,因为当您在一系列资源中导航时,可能需要维护状态信息。与 ASP 比较而言,J2EE 提供更多的选项进行编程式导航。
ASP 使用 RESPONSE.REDIRECT() 函数支持编程式导航。J2EE 支持以下形式的编程式导航:
使用 HttpServletResponse.sendRedirect() 方法 :这会导致向客户端浏览器返回一个特殊的 HTTP 返回代码(连同要重定向的页面),然后客户端浏览器又对重定向的目标发出新的请求。如果需要在这两个请求之间共享数据,那就必须将数据存储在会话或应用程序范围中。这类似于 ASP 中的 RESPONSE.REDIRECT() 函数。
使用 servlet 中的 RequestDispatcher.forward() 方法或 JSP 页面中的特殊标签: 这会导致调用此方法的资源终止,同时终止对作为转发目标的资源的调用。对客户端浏览器来说,这看起来就像是单个请求。例如,如果将请求从 servlet 转发到 JSP 页面,则您可能要附加该 servlet 中的某些处理结果,以便这些结果可以在 JSP 页面中显示出来。
使用 RequestDispatcher.include() 方法或 JSP 页面中的特殊标签 :这会导致调用此方法的资源挂起,同时挂起对目标资源的调用。当目标资源完成时,挂起的资源又继续处理。对客户端浏览器来说,这看起来就像是单个请求。
清单 33 包括了 JSP 页面中这些导航方法的一些例子。
清单 33. JSP 页面中的编程式导航
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
8 as part of the processing of this page -->
9 <jsp:include page="anotherPage.jsp" flush="true"/>
10
清单 34 展示了 servlet 中这些导航方法的一些例子。
清单 34. servlet 中的编程式导航
2 throws ServletException, IOException {
3 ...
4 // Redirecting to another resource
5 response.sendRedirect("anotherResource.jsp");
6 ...
7 }
8 // This code snippet shows the use of a RequestDispatcher to forward to another resource
9 public void doGet(HttpServletRequest request, HttpServletResponse response)
10 throws ServletException, IOException {
11 ...
12 // Get a RequestDispatcher instance
13 RequestDispatcher rd = getServletContext().getRequestDispatcher("anotherResource.jsp");
14 // Forward the request
15 rd.forward(request, response);
16 ...
17 }
18 // This code snippet shows the use of a RequestDispatcher to include another resource
19 public void doGet(HttpServletRequest request, HttpServletResponse response)
20 throws ServletException, IOException {
21 ...
22 // Get a RequestDispatcher instance
23 RequestDispatcher rd = getServletContext().getRequestDispatcher("anotherResource.jsp");
24 // Forward the request
25 rd.include(request, response);
26 // Continue processing the request
27 ...
28 }
29
Cookie
在 ASP 中, cookie 的处理使用了 REQUEST 和 RESPONSE 对象的 Cookies 属性。 RESPONSE 对象用于创建 cookie,而 REQUEST 对象用于检索 cookie 值。为方面起见,在代码中使用了多值 cookie,尽管多值 cookie 被编码到 HTTP 响应表头时,但是可以转换为单值 cookie。
在 J2EE 中,cookie 由 Cookie 类表示,并通过 HttpServletRequest 和 HttpServletResponse 接口来访问。您可以使用 HttpServletRequest 接口访问现有的 cookie,使用 HttpServletResponse 接口来创建 cookie。
清单 35 演示了如何在 J2EE 应用程序中使用 cookie。您可以在 Java Servlet 或 JSP 页面的 scriptlet 中使用这些代码片断。
清单 35. 在 J2EE Web 应用程序使用 cookie
2 // The first parameter of the constructor is the name of the cookie
3 // and the second parameter is the cookie's value
4 Cookie myCookie = new Cookie("myCookie", "someValue");
5 // Set the age of the cookie - by default cookies will be stored in memory
6 // by the browser and will be destroyed when the user closes the browser
7 // The setMaxAge() methods takes an integer as its parameter which represents
8 // the age in seconds. In this example we're specifying an age of 24 hours for
9 // the cookie
10 myCookie.setMaxAge(86400);
11 // Add cookie to the response
12 response.addCookie(myCookie);
13 // Here's an example of retrieving cookies from the HttpServletRequest interface
14 // Note that the cookies are returned as an array of Cookie objects
15 Cookies[] myCookies = request.getCookies();
16 // The getCookies method will return null if there are no cookies
17 if (myCookies != null) {
18 for (int i = 0; i < myCookies.length; i++) {
19 Cookie eachCookie = myCookies[i];
20 // Do something w/each cookie
21 ....
22 }
23 }
24
结束语
感谢您使用这个关于 J2EE 开发的入门教程。我们已尽力提供足够的信息使您走上 J2EE 之路,并且使您的开放标准编程之旅尽可能的顺利。