Tim deBoer
(deboer@ca.ibm.com), WebSphere Studio 开发团队, IBM 多伦多实验室 Gary Karasiuk (karasiuk@ca.ibm.com), Java 解决方案设计师, IBM 多伦多实验室
2002 年 1 月 01 日
本文解释 J2EE 和 WebSphere Application Server 用来构建和装入类的复杂技术。学习当使用 WebSphere Studio Application Developer 构建项目时如何创建大部分 J2EE 规范以及如何避免 ClassNotFoundException。
©2001 International Business Machines Corporation. All rights reserved.
简介
一般 J2EE 尤其是 WebSphere® Application Server 使用复杂的技术来构建和装入类。象许多开发者一样,您可能想知道它们是如何组织在一起的,如何设计项目才能节省时间并且充分利用开发环境。
本文提供有关 J2EE 规范以及如何使用它在 WebSphere Studio Application Developer 中构建项目的信息。除了创建基本的 J2EE 应用程序,我们还将研究一些 Application Developer 的最佳实践和高级功能部件。最后,我们将为您提供一个扎实的基础以便处理“可怕的”ClassNotFoundException。
J2EE 模块
J2EE 规范描述三种类型的模块:Web 模块、EJB 模块和应用程序客户机模块。当部署到 J2EE 应用程序服务器时,通常会将所有这些模块都压缩到单个 J2EE 应用程序 EAR 文件中。在以下每一节中会讨论一种模块以及如何使用 Application Developer 来构建它。
Web 模块
Web 模块包含 HTML、图像、JSps、Java类和 servlet,以及创建 Web 应用程序所需的所有其它资源。象其它模块一样,Web 模块包含一个部署描述符。在 Web 模块中,部署描述符 web.xml 具有 servlet 初始化和映射信息以及用于在应用程序服务器中运行 Web 模块的其它设置。
Web 模块有两个特殊的 Java 代码文件夹: WEB-INF/classes 和 WEB-INF/lib 。classes 文件夹可以包含“松散(loose)”Java 类(不在 JAR 文件中的类),并且可以将它用于 Web 应用程序范围内的 servlet 或实用程序类。通常对于这个文件夹使用特殊的类装入器,因此如果对类进行更改,则应用程序服务器会自动将它们重新装入。lib 文件夹可以包含也是由 Web 应用程序使用的 JAR 文件(而不是 ZIp 文件!)。应该将第三方 JAR 文件和其它实用程序 JAR 文件放入这个文件夹。如果 JAR 文件被其它 Web 或 EJB 模块使用,则按照下面的 企业应用程序一节中的说明将它们移动到“企业应用程序”项目中。
在 Application Developer 中,Web 模块由 Web 项目表示,它包含两个文件夹:source 和 webApplication。webApplication 文件夹包含扩展形式的完整 J2EE Web 模块。source 文件夹用于存放 .java 文件,因为它们常常不是部署的 Web 模块的一部分。当您在这个文件夹中创建 Java 资源时,会自动编译它们并将它们放入 webApplication/WEB-INF/classes 文件夹。这会使 Web 项目始终保持同步,并准备好测试或导出。
如果从 WAR 文件导入了 Web 模块,则可能注意到 lib 文件夹中的 projectname_classes.jar 文件。这个文件包含导入的 WAR 文件的原始内容。如果 WAR 文件包含源代码,则删除该文件,因为类将在 classes 文件夹中冗余地出现。
EJB 模块
EJB 模块包含 EJB bean、其特定于服务器的部署代码、部署描述符和助手类(可选的)。它们包含应用程序的业务逻辑,并且在一般情况下由 Web、Application Client 和其它 EJB 模块调用。
在 Application Developer 中,EJB 模块由 EJB 项目表示。这些项目还有两个文件夹,bin 和 ejbModule。EJB 模块的源代码保存在 ejbModule 文件夹中。当更改和生成部署代码时,将这个文件夹中的 Java 类编译到 bin 文件夹中。将剩余的资源(例如,部署描述符)也复制到 bin 文件夹中。与 Web 项目的 webApplication 文件夹类似,bin 文件夹总是包含完整的已部署的 EJB 模块。与 Web 项目不同的是,不应该以任何手工方式修改 bin 文件夹,否则可能丢失更改。在 ejbModule 文件夹中进行所有更改,将会自动编译这些更改或将它们复制到 bin 文件夹中。
如果从 EJB JAR 文件中导入 EJB bean,则可能注意到位于您项目根目录中的 Xxx_importedClasses.jar 文件。这个文件包含导入的 EJB JAR 文件的原始内容。如果 JAR 包含源代码,则删除该文件,因为类将在 bin 文件夹中冗余地出现。
应用程序客户机模块
使用应用程序客户机模块以包含全功能客户机 Java 应用程序(非基于 Web),它连接到并使用在服务器中定义的 J2EE 资源。通过将客户机代码放入应用程序客户机模块而不是简单 JAR 文件,应用程序客户机将得益于服务器的资源(它不需要将类路径重新指定到 J2EE 和服务器 jar 文件)以及更方便的 JNDI 查询(服务器填充初始上下文和其它参数)。
在 Application Developer 中,应用程序客户机模块由应用程序客户机项目表示。对于大部分模块来说,可以象在 Java 项目中创建独立的 Java 应用程序一样工作。
企业应用程序(J2EE 应用程序)
企业应用程序是一个或多个 Web、EJB 或应用程序客户机模块的组合。作为这些其它模块的超集,它可以包含可能是多个模块组合的完整的应用程序。除了是一个有效的组合机制外,企业应用程序还在完整的应用程序级别上部署和维护代码,与作为单个代码片段相比,这更加容易。企业应用程序也可以重设被包含的模块的部署描述符内部的设置,以更实用的方式来组合或部署它们。
企业应用程序可能包含所含模块使用的 JAR 文件。这允许在应用程序级别上共享代码,并且还是放置由多个 Web 或 EJB 模块使用的实用程序 JAR 文件的最佳位置。通过将这些 JAR 文件放入企业应用程序而不是全局类路径,它们同时也符合 J2EE 规范,在移动到新服务器时不需要特殊发布和设置。
在 Application Developer 中,企业应用程序是由企业应用程序项目表示的。因为没有直接将源代码构建到企业应用程序,所以这些项目没有子文件夹。
WebSphere 类装入器
WebSphere Application Server 使用几个类装入器来遵循 J2EE 规范。除了使用类路径环境变量定位并装入类的常规类装入器之外,还有许多正在工作的其它类装入器。
下面的图 1 显示了 WebSphere 中正在工作的类装入器的简化图。每个椭圆代表一个类装入器,方括号中的文本描述了类装入器在何处查找类。
图 1. WebSphere 类装入器
在顶部,常规 Java 系统类装入器使用类路径环境变量来装入类。第二个类装入器是特定于 WebSphere 的并且使用 ws.ext.dirs 环境变量来装入类。(有关如何更改这个环境变量的信息,请参阅“发行版说明”。在刷新中,您将能在服务器实例编辑器中更改这个变量。)除了装入所有用户代码之外,这个装入器还装入在运行时所需的所有 WebSphere 和 J2EE 类。最后,由一个或多个模块类装入器来装入运行在服务器中的模块。它们遵循前面讨论的 J2EE 类装入规则来从应用程序装入类和 JAR 文件。
上面的图 1 中最重要的是将每个类装入器定义成其上面的类装入器的子类。无论何时需要装入类,类装入器通常将请求委派给其父类装入器。如果父类装入器无法找到该类,则原始类装入器试图装入该类。请求只能在树中上行而不能下行。如果请求 WebSphere 类装入器查找 J2EE 模块中的类,它不能下行至模块类装入器来查找该类,并且将出现 ClassNotFoundException。一旦由类装入器装入一个类,则它尝试要装入的任何新类将重用同一个类装入器,或者沿着该路径上行直到找到该类为止。
两种情况下,您可能遇到问题。
- WebSphere、J2EE 和任何全局类无法“看见”包含在应用程序中的类。如果将 JAR 文件添加到全局类路径或
ws.ext.dirs 属性中,则它就不再取决于模块中的类了。
- 如果需要全局地将数据库驱动程序或实用程序 JAR 添加到类路径,则必须将它们添加到
ws.ext.dirs 属性而不是添加到全局类路径。如果错误地将它们添加到全局类路径,则它们将无法装入(例如)J2EE JAR 文件中的连接池类。这将再次作为 ClassNotFoundException 出现在数据库驱动程序上,但是,对数据库驱动程序类而言,向下查看 J2EE 类是错误的。
查找资源(属性文件、图像等)的公共可移植技术是使用类装入器的 findResourceXX 方法。基于前面的讨论,您可以看出为什么对这个作业使用正确的类装入器是重要的。例如,如果使用 WebSphere 类装入器,您将不能在模块中找到任何资源。
WebSphere 类装入器隔离方式
正如前面提到的,WebSphere 使用几个类装入器来从部署到服务器的模块中装入应用程序代码。这些类装入器的数量和功能取决于已在 WebSphere 服务器配置中指定的类装入器隔离方式(也称为模块可见性方式)。有四种设置可供选择:
-
Server? 对于整个服务器都使用同一个类装入器。
-
Application? 对于每个企业应用程序使用独立的类装入器。
-
Module? 对每个模块使用单独的类装入器。
-
Compatibility-- 使用与 WebSphere Application Server 3.0.x 和 3.5.x 相同的类装入器模式。这允许代码具有跨越企业应用程序边界的可见性。不赞成使用该方式,除非您正在从旧的服务器中迁移代码。
构建更大的 ? 模块相互依赖性
当在企业应用程序内构建跨越多个模块的应用程序时,请确保一个模块中的类可以看到另一个模块中它所使用的类。对于想使用企业应用程序之外的 JAR 文件的模块来说也是如此,并且对于编译时和运行时都必须完成该操作。
在编译时,更新类路径是很简单的。您只需要编辑项目的属性然后更改 Java 构建路径以包含其它项目或 JAR 文件,但且慢动手!我们将介绍一种更简便的方法,它只用一步就在编译时和运行时更新它。常规情况下,不要手工更改 J2EE 项目的 Java 构建路径。下列步骤将自动更新并维护构建路径,然后与运行时保持同步。
在运行时,当引用其它模块时,应用程序必须遵循 J2EE 规范。如上面的“WebSphere 类装入器”所述,不要简单地将一个项目放在其中一个全局类路径上,因为这可能会对类装入产生严重影响,并且使所有其它应用程序都能看见您的代码,后果同样严重。
您也不应该依赖于 WebSphere 类装入器隔离方式。如果您的服务器以(例如)Application 方式运行,则可能不必做任何更改就能使应用程序在运行时工作。不要依赖于它!以后,可能需要将应用程序部署到在 module 方式下运行的服务器中,并且您将发现应用程序不再工作。无论使用哪种隔离方式,请遵循下面建议的步骤以确保您的应用程序正确。
解决方案将利用 J2EE 模块是 JAR 文件这一事实。所有 JAR 文件可能具有一个 META-INF 文件夹,它包含 MANIFEST.MF 文件,该文件可能包含引用其它 JAR 文件的类路径。通过将一个其它模块作为一个项加入清单的类路径,当前模块可以利用其包含的类。还可以从企业应用程序中将 JAR 文件添加到清单。对这个简单解决方案的唯一限制是,其它模块决不能依赖于 Web 模块中的类,因为 Web 模块不是用位于其根处的类构建的。换句话说,模块不可能使用清单以依赖 Web 模块中的类。
Application Developer 提供了更新清单文件的简单方法。在 Navigator 或 J2EE 视图中的 J2EE 项目上单击右键,然后选择 Edit Module Dependencies。这会产生一个对话框(参阅下面的图 2),它显示了一个复选框列表,其中包含这个项目可依赖的所有其它模块和 JAR 文件。通过选择列表中的模块或 JAR 文件,可以同时更新清单和构建路径。这确保清单和构建路径总是同步的,并且提供了更改您项目相互依赖性的更简便方法。
图 2. 编辑模块相互依赖性对话框
应该将 JAR 文件放在哪里?
如果仅在单个 Web 应用程序中使用 JAR 文件,则应该总是将 JAR 文件放在 Web 项目的 webApplication/WEB-INF/lib 文件夹中。这个文件夹中的 JAR 文件将自动添加到 Java 构建路径,并且当移动到另一个服务器时,将不需要任何进一步的设置。
如果 JAR 文件由同一个应用程序中的多个模块使用,则将 JAR 文件放入企业应用程序中。您将需要使用 Edit Module Dependencies 功能部件来设置清单文件和 Java 构建类路径。
如果仍然想将 JAR 文件放置在全局类路径上,(我们强烈建议您不要使用这个方法)您必须确定它是否应该放在全局类路径或 ws.ext.dirs 上。这个判定很简单。如果 JAR 文件需要访问任何 J2EE 或 WebSphere 类或者已经添加到 ws.ext.dirs 中的任何其它 JAR,还必须将它放置在 ws.ext.dirs 属性上。否则,您可以自由使用这两种属性。
最后要记住的一件事是 JAR 文件在路径中的位置越高,其包含的内容就越少。如果与 JAR 文件有牢固的相互依赖性,则更新使用该 JAR 文件的每个项目的 Java 构建路径。将它添加到全局类路径或 ws.ext.dirs 属性还意味着,必须单独地从应用程序发布 JAR 文件,并且当移动到另一个服务器时,必须再次设置服务器类路径。
使用一个全局类路径的唯一好处是次要的 ? 您可以在服务器上节省少量的磁盘空间。重大的缺点是您的应用程序现在很脆弱,其他人可能更改您所依赖的类。例如,假设您依赖某些第三方记录类,并且因为几乎所有应用程序都使用这些记录类,所以您决定在全局类路径上部署它们。使用记录类的版本 1 来测试应用程序。六个月后,部署了另一个应用程序,它需要记录类的版本 2,因此更新了日志记录 JAR。现在,您的应用程序运行在一个从来没有测试过的环境中。
类还是 JAR?
您需要作出的另一个决定是使用松散类还是使用 JAR。使用松散类的一个很大的好处是容易调试和部署它们。使用松散类,可以通过按 Ctrl-S 进行更改,如果它在可重装入的类路径上(象 WEB-INF/classes ),则可以立即使用它。如果它不在一个可重装入的类路径上,并且如果您正在使用 WebSphere Application Server,则只需要重新启动项目,然后就可使更改生效了。要完成这个任务,在 Navigator 中的项目上单击右键,然后从弹出菜单中选择 Restart project。
如果您正在使用 JAR 文件,则还需要完成收集所有类和重构建 JAR 文件的其它构建步骤。您的更改不能很快生效。使用 JAR 文件的好处是使部署更清晰一点,并且更接近期望的生产环境。
如果处于项目的早期阶段,此时经常更改助手类,则您会发现使用松散类将更方便。如果类仅由单个 Web 模块使用,则将类放入 WEB-INF/classes 文件夹。为了使用类,总是使用包,因为当 Java 规范处理缺省包中的类时是不精确的。
如果类由 EJB 使用,则让您的一个 EJB 项目保留助手类(将它们放在 ejbModule 文件夹下),让需要这些助手类的其它 EJB 模块依赖于该 EJB 项目。将您的所有助手类都收集到单个 EJB 项目中,因为在项目之间不建立循环相互依赖性是 非常重要的。好的项目结构规定相互依赖性必须始终采用树的形式。一旦助手类成熟并且没有太多更改时,就切换至 JAR 方法。
如果实用程序类由企业应用程序和常规 Java 应用程序共同使用将会怎样?最简单的方法是使用这些类的 JAR。您可以用一个更复杂的替代方法:
- 创建一个简单的 Java 项目以存储实用程序类。
- 手工更新依赖于这个项目的项目 Java 构建路径。
- 手工更新服务器实例以包含简单 Java 项目的 bin 文件夹。
开发时类路径对比运行时类路径
在编译时,Java 编译器需要知道您的代码引用的每个类或 JAR 文件,这样就可以安全地对这些类进行编译和类型检查。Java 编译器使用项目级别属性 Java Build path 作为这个信息的唯一源。其它操作(如 Edit Module Dependencies)会操纵构建路径,但是构建路径是权威性源。
在运行时,应用程序服务器使用完全不同的机制来查找和装入类。运行时不知道任何 Java 构建路径,因此您的应用程序可能正确编译,但是在运行时仍然有 ClassNotFoundException。我们希望本文可以揭开这个过程的一些秘密。
作者简介
|
|
|
Tim deBoer是 IBM 多伦多实验室的 WebSphere Studio Application Developer“服务器工具”团队的一名软件开发人员。与他的团队成员一起,他目前负责 WebSphere 和 Tomcat 测试环境和 EJB 测试客户机。可以通过 deboer@ca.ibm.com与 Tim 联系。
|
|
|
|
Gary Karasiuk目前是 WebSphere Studio Application Developer 的解决方案设计师。他已经在 IBM 多伦多实验室工作了二十多年,主要从事应用程序开发工具、面向对象的编程和咨询工作。可以通过 karasiuk@ca.ibm.com与 Gary 联系。
|
|