安装 WebSphere应用服务器

IBM WebSphere Application Server提供了从电子商务的构建、发布到管理的能力。其标准版提供了开放、标准的平台。WebSphere Application Server包括Servlet运行引擎、高性能的数据库连接程序,提供预连接、会话和状态管理的应用服务,WebSphere还支持XML文档结构。另外,WebSphere还支持JSP的1.0版本,Servlet的2.1版本。有高速的数据库缓冲联结池(支持DB2 Universal Database,Oracle,Microsoft SQL Server等数据库。)   WebSphere可以安装在NT、95/98操作系统平台上,下面介绍如何在Windows NT 上安装与配置WebSphere应用服务器。   安装WebSphere必须要求有128兆内存以上,要是256兆就更好了。操作系统要求Windows NT(Pack 3)。安装之前,必须要有Web服务器,可以是如下之一:  IBM HTTP 服务器版本 1.3.3 Windows NT 版(WebSphere应用服务器安装光盘包括IBM HTTP 服务器)   Apache Server 版本 1.3.2 Windows NT 版   Domino 版本 5.0 Windows NT 版   Lotus Domino Go Webserver 版本 4.6.2.5 Windows NT 版   Microsoft Internet Information Server 版本 3.x 和 版本 4.0 Windows NT 版   Netscape Enterprise Server 版本 3.01 和 版本 3.51 Windows NT 版(建议使用版本 3.5.1)   Netscape FastTrack Server 版本 3.01 Windows NT 版   还需要Java 开发组件(JDK),这里,我们建议使用与 WebSphere应用服务器一起提供的 JDK1.1.6。还需要Java servlet API,WebSphere应用服务器包括 JSDK 版本 2.0 或更高版本。 当然,还需要Web浏览器和appletviewer或者支持JDK1.1 的浏览器。例如: Netscape Navigator 4.06 或 4.0.7、或者带有修正包的 Microsoft Internet Explorer 4.01 或更高版本、或者Sun HotJava 1.1 或更高版本。有一些旧的浏览器不能够正确地处理启用本机语言支持的文本。如果在用户界面上看到外来字符,例如“sEnable”,而不是“Enable”,可以通过升级浏览器校正。 第一步:安装Web服务器:   如果打算安装 IBM HTTP 服务器,就必须在安装 WebSphere应用服务器之前安装它。WebSphere应用服务器安装光盘有IBM HTTP Server。安装WebSphere应用服务器会更改 Web 服务器 httpd.conf 文件。但是,如果Web 服务器是在安装 WebSphere应用服务器之后安装的,就不会进行更改,并且 WebSphere应用服务器也不能够正确运行。安装 IBM HTTP 服务器之后,需要一些配置以启用 SSL 支持。具体如何配置,参阅 IBM HTTP 服务器文档以获得指示信息。   对于计划安装其它的Web 服务器(如Apache Server),也请在安装 WebSphere应用服务器之前安装它。 第二步:安装之前:   在安装之前,如果系统中有旧版本的WebSphere,要先把旧的版本卸载了。然后再安装新的版本。卸装之前,最好先备份 WebSphere应用服务器版本 1.x 文件。 (1) 文件备份   从Windows NT 卸装以前版本的 WebSphere应用服务器之前,确保要移植的文件已经或者将要保存。安装 WebSphere应用服务器版本 2.0 时显示出的图形用户界面备份 WebSphere应用服务器目录中的文件,包括类、领域、Servlet、属性文件,其中,属性文件包括servlet.properties、admin_port.properties、rules.properties、jvm.properties、aliases.properties、connmgr.properties、userprofile.properties。如果有文件驻留在这四个目录之外(例如,如果在 WebSphere应用服务器 安装中创建自己的目录),在安装 WebSphere应用服务器 版本 2.0. 之前,在 WebSphere应用服务器 安装之外的位置备份文件。 (2) 卸载前一版本   对于 Windows NT,使用开始 --> 控制面板中的添加/删除选项,或从开始 --> 程序 --> IBM WebSphere --> WebSphere应用服务器版本1.x 卸装。   注意:当已安装了某版本的 WebSphere应用服务器,它将复制Web 站点配置文件作为备份文件,然后修改原始配置文件。当使用 Web 服务器时这个已被修改过的文件就成为活动的配置文件。当卸装 WebSphere应用服务器时,不会恢复以前的配置文件,它仍然是备份文件。为了使这些设置再次活动,必须将它们从备份文件转换为活动的 Web 服务器配置文件。   另外,需要清除 CLASSPATH。安装版本2.0之前要删除这些信息。对于在 Windows NT 上使用Go Webserver 的Web服务器来说,当安装 WebSphere应用服务器时能自动卸装Go Webserver 上的 Java 支持。其它的请查阅有关文档。 第三步:安装WebSphere服务器:   在即将安装 WebSphere应用服务器之前,请确保已经: (1)备份所有未通过安装程序自动备份的文件 (2) 安装您计划使用的 Web 服务器。Web 服务器必须在安装 WebSphere应用服务器之前安装。 (3) 安装之前要确保Web 服务器已经停止运行。   注意:在WebSphere应用服务器的安装期间,如果指定使用 IBM HTTP 服务器或 Apache Server,将提示您确认 Web 服务器 httpd.conf 文件的位置。   在 Windows NT 上,插入 WebSphere应用服务器安装光盘,转至以 Windows NT 操作系统命名的子目录,运行可执行安装程序(setup.exe)。一系列面板将指导您完成安装。 配置 WebSphere应用服务器   下面介绍如何配置WebSphere应用服务器,以使它和它的组件以能够协同工作。完成这些任务之后,WebSphere应用服务器通过缺省的配置就能够运行几乎所有的功能。 (1) 配置 Apache Server 如果使用Apache Server作为Web服务器,在httpd.conf文件中加入此行: AddModule mod_app_server.c。 (2) 使用数据库。   要保证WebSphere应用服务器与关系数据库(如 Oracle 或 DB2)的连接:将.zip 文件加到文件 /properties/bootstrap.properties 中的 java.classpath 属性。也可以使用 WebSphere应用服务器的管理器界面的 Java 引擎页面来指定文件。同样,确保 java.classpath 包含用于数据库连接的有效的驱动程序。 (3) 运行模式   ose.mode 属性是用来控制WebSphere应用服务器是作为Web 服务器的一部分(进程内),还是在独立模式下运行(进程外)。该属性在 /properties/bootstrap.properties 文件中。ose.mode 缺省值是 out。Apache Server 或 IBM HTTP 服务器要求设置 ose.mode 属性必须为 out,就是说WebSphere应用服务器必须运行在独立模式下。对于其它 Web 服务器,可以将ose.mode 为 in 并且作为 Web 服务器的一部分运行 WebSphere应用服务器。 但是通常都不这么做。如有必要需要复位 ose.mode就是说将ose.mode的值设为out。例如,如果从作为 Webserver 一部分运行的 Netscape Enterprise Server (ose.mode=in)转换为 IBM HTTP 服务器(要求 ose.mode=out),就别忘了在运行 IBM HTTP 服务器之前,要将ose.mode 属性更改为 out。   除了可以手工编辑 bootstrap.properties 文件之外,管理器界面的 Java 引擎页面提供了一个简单的方法来锁住该ose.mode属性值。 Java 引擎页面提供了一个可用来指示是以 Web 服务器的一部分(ose.mode=in)或以独立模式(ose.mode=out)运行 WebSphere应用服务器的单选按钮。   作为 Web 服务器一部分运行 WebSphere应用服务器为Servlet 和其它应用程序提供较高的性能,但安全性较差。作为 Web 服务器的一部分运行 WebSphere应用服务器,允许当关闭 Web 服务器时 WebSphere应用服务器自动停止。在独立模式下运行 WebSphere应用服务器需要其它步骤。 启动和停止 WebSphere应用服务器 在启动 Web 服务器的时候,WebSphere应用服务器会自动启动。   如果将 WebSphere应用服务器作为Web 服务器的一部分运行,当关闭 Web 服务器时 WebSphere应用服务器 将自动停止。如果以独立模式运行,WebSphere应用服务器不会自动停止。   当在Windows NT 上进程外运行 WebSphere应用服务器 时,停止 Web 服务器之后,需要手动停止 WebSphere Servlet 服务以停止 WebSphere应用服务器。方法是:从开始 --> 设置 --> 控制面板 --> 中选择 WebSphere Servlet 服务,并按“停止”按钮。 安装的检查和故障寻找   要验证 WebSphere应用服务器是否已安装好并正确配置,可调用 WebSphere应用服务器提供的 snoop servlet。使用Web 浏览器在地址栏中输入: http://your.server.name/servlet/snoop。Snoop Servlet 应该回送客户机发送的 HTTP 请求及 servlet 的初始化参数。SnoopServlet 和其它 servlet 的代码位于/servlets 目录。如果Servlet失败:   (1) 如果你原来通过手工编辑更改过 .properties 文件或更改了WebSphere应用服务器的配置,先看看这些文件以是否引入非法的或不正确的值。特别要检查 /properties/bootstrap.properties 文件。   (2) 为Web 服务器打开本地日志和跟踪。先找到 WebSphere应用服务器 bootstrap.properties 文件。然后把ose.trace.enabled 和ose.trace.to.webserver 属性都设为true。停止 Web 服务器并重启动。如果 WebSphere应用服务器运行在独立模式,当停止 Web 服务器时它不会相应停止。检查 Web 服务器出错日志及 WebSphere应用服务器 /logs 目录下的日志。看看错误原因。   (3) 启用调试控制台并重新启动 Web 服务器。WebSphere应用服务器的调试控制台是用来收集和查看跟踪及监控数据的。例如,从调试控制台,可以作为一组启动和停止列在收集和监控服务器数据中的监控程序。在调试控制台的服务器控制台中,允许查看 servlet 的 stdout 和 stderr 流。缺省情况下,并没有启动控制台。在WebSphere应用服务器 debug.properties 文件中设置将debug.server.console.enabled 属性设置为为true,然后重新启动Web 服务器使得改动生效。或者,也可以在Web中运行 http://your.server.name/servlet/DebugConsoleServlet来启用调试控制台。在 Windows NT 上,要成功地查看调试控制台,必须配置 Windows NT 以允许一个或多个服务与 Windows 桌面交互。如果使用作为 Windows NT 服务运行的 Web 服务器:   选择开始 --> 设置 --> 控制面板 --> 服务。   选择 Web 服务器相应的服务。   单击启动按钮。   在结果对话框中,选择允许服务与桌面交互的复选框。   重新启动 Web 服务器以使更改生效。   对于微软的IIS服务器,对与 Web 服务器相关的每个服务(如 Web 发布和 FTP服务),执行以上过程。这些服务必须允许与 Windows NT 桌面交互。如果 WebSphere应用服务器运行时未启动任何 Web 服务器相关的进程,则需要为WebSphere Servlet 服务执行以上过程,从而允许服务与桌面交互。 <

小小豆叮

运用加密技术保护Java源代码

俞良松 (javaman@163.net)
软件工程师,独立顾问和自由撰稿人
2001 年 10 月

Java程序的源代码很容易被别人偷看。只要有一个反编译器,任何人都可以分析别人的代码。本文讨论如何在不修改原有程序的情况下,通过加密技术保护源代码。

一、为什么要加密?
对于传统的C或C++之类的语言来说,要在Web上保护源代码是很容易的,只要不发布它就可以。遗憾的是,Java程序的源代码很容易被别人偷看。只要有一个反编译器,任何人都可以分析别人的代码。Java的灵活性使得源代码很容易被窃取,但与此同时,它也使通过加密保护代码变得相对容易,我们唯一需要了解的就是Java的ClassLoader对象。当然,在加密过程中,有关Java Cryptography Extension(JCE)的知识也是必不可少的。

有几种技术可以“模糊”Java类文件,使得反编译器处理类文件的效果大打折扣。然而,修改反编译器使之能够处理这些经过模糊处理的类文件并不是什么难事,所以不能简单地依赖模糊技术来保证源代码的安全。

我们可以用流行的加密工具加密应用,比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。这时,最终用户在运行应用之前必须先进行解密。但解密之后,最终用户就有了一份不加密的类文件,这和事先不进行加密没有什么差别。

Java运行时装入字节码的机制隐含地意味着可以对字节码进行修改。JVM每次装入类文件时都需要一个称为ClassLoader的对象,这个对象负责把新的类装入正在运行的JVM。JVM给ClassLoader一个包含了待装入类(比如java.lang.Object)名字的字符串,然后由ClassLoader负责找到类文件,装入原始数据,并把它转换成一个Class对象。

我们可以通过定制ClassLoader,在类文件执行之前修改它。这种技术的应用非常广泛——在这里,它的用途是在类文件装入之时进行解密,因此可以看成是一种即时解密器。由于解密后的字节码文件永远不会保存到文件系统,所以窃密者很难得到解密后的代码。

由于把原始字节码转换成Class对象的过程完全由系统负责,所以创建定制ClassLoader对象其实并不困难,只需先获得原始数据,接着就可以进行包含解密在内的任何转换。

Java 2在一定程度上简化了定制ClassLoader的构建。在Java 2中,loadClass的缺省实现仍旧负责处理所有必需的步骤,但为了顾及各种定制的类装入过程,它还调用一个新的findClass方法。

这为我们编写定制的ClassLoader提供了一条捷径,减少了麻烦:只需覆盖findClass,而不是覆盖loadClass。这种方法避免了重复所有装入器必需执行的公共步骤,因为这一切由loadClass负责。

不过,本文的定制ClassLoader并不使用这种方法。原因很简单。如果由默认的ClassLoader先寻找经过加密的类文件,它可以找到;但由于类文件已经加密,所以它不会认可这个类文件,装入过程将失败。因此,我们必须自己实现loadClass,稍微增加了一些工作量。

二、定制类装入器
每一个运行着的JVM已经拥有一个ClassLoader。这个默认的ClassLoader根据CLASSPATH环境变量的值,在本地文件系统中寻找合适的字节码文件。

应用定制ClassLoader要求对这个过程有较为深入的认识。我们首先必须创建一个定制ClassLoader类的实例,然后显式地要求它装入另外一个类。这就强制JVM把该类以及所有它所需要的类关联到定制的ClassLoader。Listing 1显示了如何用定制ClassLoader装入类文件。


【Listing 1:利用定制的ClassLoader装入类文件】

  // 首先创建一个ClassLoader对象
  ClassLoader myClassLoader = new myClassLoader();

  // 利用定制ClassLoader对象装入类文件
  // 并把它转换成Class对象
  Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );

  // 最后,创建该类的一个实例
  Object newInstance = myClass.newInstance();

  // 注意,MyClass所需要的所有其他类,都将通过
  // 定制的ClassLoader自动装入

如前所述,定制ClassLoader只需先获取类文件的数据,然后把字节码传递给运行时系统,由后者完成余下的任务。

ClassLoader有几个重要的方法。创建定制的ClassLoader时,我们只需覆盖其中的一个,即loadClass,提供获取原始类文件数据的代码。这个方法有两个参数:类的名字,以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记是true,我们只需在返回JVM之前调用resolveClass。


【Listing 2:ClassLoader.loadClass()的一个简单实现】

      public Class loadClass( String name, boolean resolve )
      throws ClassNotFoundException {
    try {
      // 我们要创建的Class对象
       Class clasz = null;

      // 必需的步骤1:如果类已经在系统缓冲之中,
      // 我们不必再次装入它
      clasz = findLoadedClass( name );

      if (clasz != null)
        return clasz;

      // 下面是定制部分
      byte classData[] = /* 通过某种方法获取字节码数据 */;
      if (classData != null) {
        // 成功读取字节码数据,现在把它转换成一个Class对象
        clasz = defineClass( name, classData, 0, classData.length );
      }

      // 必需的步骤2:如果上面没有成功,
      // 我们尝试用默认的ClassLoader装入它
      if (clasz == null)
        clasz = findSystemClass( name );

      // 必需的步骤3:如有必要,则装入相关的类
      if (resolve && clasz != null)
        resolveClass( clasz );

      // 把类返回给调用者
      return clasz;

    } catch( IOException ie ) {
      throw new ClassNotFoundException( ie.toString() );
    } catch( GeneralSecurityException gse ) {
      throw new ClassNotFoundException( gse.toString() );
    }
  }

Listing 2显示了一个简单的loadClass实现。代码中的大部分对所有ClassLoader对象来说都一样,但有一小部分(已通过注释标记)是特有的。在处理过程中,ClassLoader对象要用到其他几个辅助方法:

  • findLoadedClass:用来进行检查,以便确认被请求的类当前还不存在。loadClass方法应该首先调用它。
  • defineClass:获得原始类文件字节码数据之后,调用defineClass把它转换成一个Class对象。任何loadClass实现都必须调用这个方法。
  • findSystemClass:提供默认ClassLoader的支持。如果用来寻找类的定制方法不能找到指定的类(或者有意地不用定制方法),则可以调用该方法尝试默认的装入方式。这是很有用的,特别是从普通的JAR文件装入标准Java类时。
  • resolveClass:当JVM想要装入的不仅包括指定的类,而且还包括该类引用的所有其他类时,它会把loadClass的resolve参数设置成true。这时,我们必须在返回刚刚装入的Class对象给调用者之前调用resolveClass。

三、加密、解密
Java加密扩展即Java Cryptography Extension,简称JCE。它是Sun的加密服务软件,包含了加密和密匙生成功能。JCE是JCA(Java Cryptography Architecture)的一种扩展。

JCE没有规定具体的加密算法,但提供了一个框架,加密算法的具体实现可以作为服务提供者加入。除了JCE框架之外,JCE软件包还包含了SunJCE服务提供者,其中包括许多有用的加密算法,比如DES(Data Encryption Standard)和Blowfish。

为简单计,在本文中我们将用DES算法加密和解密字节码。下面是用JCE加密和解密数据必须遵循的基本步骤:

  • 步骤1:生成一个安全密匙。在加密或解密任何数据之前需要有一个密匙。密匙是随同被加密的应用一起发布的一小段数据,Listing 3显示了如何生成一个密匙。
    
    【Listing 3:生成一个密匙】
    
      // DES算法要求有一个可信任的随机数源
      SecureRandom sr = new SecureRandom();
    
      // 为我们选择的DES算法生成一个KeyGenerator对象
      KeyGenerator kg = KeyGenerator.getInstance( "DES" );
      kg.init( sr );
    
      // 生成密匙
      SecretKey key = kg.generateKey();
    
      // 获取密匙数据
      byte rawKeyData[] = key.getEncoded();
    
      /* 接下来就可以用密匙进行加密或解密,或者把它保存
         为文件供以后使用 */
      doSomething( rawKeyData );
    
  • 步骤2:加密数据。得到密匙之后,接下来就可以用它加密数据。除了解密的ClassLoader之外,一般还要有一个加密待发布应用的独立程序(见Listing 4)。
    
    【Listing 4:用密匙加密原始数据】
    
        // DES算法要求有一个可信任的随机数源
        SecureRandom sr = new SecureRandom();
    
        byte rawKeyData[] = /* 用某种方法获得密匙数据 */;
    
        // 从原始密匙数据创建DESKeySpec对象
        DESKeySpec dks = new DESKeySpec( rawKeyData );
    
        // 创建一个密匙工厂,然后用它把DESKeySpec转换成
        // 一个SecretKey对象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
        SecretKey key = keyFactory.generateSecret( dks );
    
        // Cipher对象实际完成加密操作
        Cipher cipher = Cipher.getInstance( "DES" );
    
        // 用密匙初始化Cipher对象
        cipher.init( Cipher.ENCRYPT_MODE, key, sr );
    
        // 现在,获取数据并加密
        byte data[] = /* 用某种方法获取数据 */
    
        // 正式执行加密操作
        byte encryptedData[] = cipher.doFinal( data );
    
        // 进一步处理加密后的数据
        doSomething( encryptedData );
  • 步骤3:解密数据。运行经过加密的应用时,ClassLoader分析并解密类文件。操作步骤如Listing 5所示。
    
    【Listing 5:用密匙解密数据】
    
        // DES算法要求有一个可信任的随机数源
        SecureRandom sr = new SecureRandom();
    
        byte rawKeyData[] = /* 用某种方法获取原始密匙数据 */;
    
        // 从原始密匙数据创建一个DESKeySpec对象
        DESKeySpec dks = new DESKeySpec( rawKeyData );
    
        // 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
        // 一个SecretKey对象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
        SecretKey key = keyFactory.generateSecret( dks );
    
        // Cipher对象实际完成解密操作
        Cipher cipher = Cipher.getInstance( "DES" );
    
        // 用密匙初始化Cipher对象
        cipher.init( Cipher.DECRYPT_MODE, key, sr );
    
        // 现在,获取数据并解密
        byte encryptedData[] = /* 获得经过加密的数据 */
    
        // 正式执行解密操作
        byte decryptedData[] = cipher.doFinal( encryptedData );
    
        // 进一步处理解密后的数据
        doSomething( decryptedData );

四、应用实例
前面介绍了如何加密和解密数据。要部署一个经过加密的应用,步骤如下:

  1. 步骤1:创建应用。我们的例子包含一个App主类,两个辅助类(分别称为Foo和Bar)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其他应用也就不在话下。
  2. 步骤2:生成一个安全密匙。在命令行,利用GenerateKey工具(参见GenerateKey.java)把密匙写入一个文件:
    % java GenerateKey key.data
  3. 步骤3:加密应用。在命令行,利用EncryptClasses工具(参见EncryptClasses.java)加密应用的类:
    % java EncryptClasses key.data App.class Foo.class Bar.class
    该命令把每一个.class文件替换成它们各自的加密版本。
  4. 步骤4:运行经过加密的应用。用户通过一个DecryptStart程序运行经过加密的应用。DecryptStart程序如Listing 6所示。
    
    【Listing 6:DecryptStart.java,启动被加密应用的程序】
    
    import java.io.*;
    import java.security.*;
    import java.lang.reflect.*;
    import javax.crypto.*;
    import javax.crypto.spec.*;
    
    public class DecryptStart extends ClassLoader
    {
      // 这些对象在构造函数中设置,
      // 以后loadClass()方法将利用它们解密类
      private SecretKey key;
      private Cipher cipher;
    
      // 构造函数:设置解密所需要的对象
      public DecryptStart( SecretKey key ) throws GeneralSecurityException,
          IOException {
        this.key = key;
    
        String algorithm = "DES";
        SecureRandom sr = new SecureRandom();
        System.err.println( "[DecryptStart: creating cipher]" );
        cipher = Cipher.getInstance( algorithm );
        cipher.init( Cipher.DECRYPT_MODE, key, sr );
      }
    
      // main过程:我们要在这里读入密匙,创建DecryptStart的
      // 实例,它就是我们的定制ClassLoader。
      // 设置好ClassLoader以后,我们用它装入应用实例,
      // 最后,我们通过Java Reflection API调用应用实例的main方法
      static public void main( String args[] ) throws Exception {
        String keyFilename = args[0];
        String appName = args[1];
    
         // 这些是传递给应用本身的参数
        String realArgs[] = new String[args.length-2];
        System.arraycopy( args, 2, realArgs, 0, args.length-2 );
    
        // 读取密匙
        System.err.println( "[DecryptStart: reading key]" );
        byte rawKey[] = Util.readFile( keyFilename );
        DESKeySpec dks = new DESKeySpec( rawKey );
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
        SecretKey key = keyFactory.generateSecret( dks );
    
        // 创建解密的ClassLoader
        DecryptStart dr = new DecryptStart( key );
    
        // 创建应用主类的一个实例
        // 通过ClassLoader装入它
        System.err.println( "[DecryptStart: loading "+appName+"]" );
        Class clasz = dr.loadClass( appName );
    
        // 最后,通过Reflection API调用应用实例
        // 的main()方法
    
        // 获取一个对main()的引用
        String proto[] = new String[1];
        Class mainArgs[] = { (new String[1]).getClass() };
        Method main = clasz.getMethod( "main", mainArgs );
    
        // 创建一个包含main()方法参数的数组
        Object argsArray[] = { realArgs };
        System.err.println( "[DecryptStart: running "+appName+".main()]" );
    
        // 调用main()
        main.invoke( null, argsArray );
      }
    
      public Class loadClass( String name, boolean resolve )
          throws ClassNotFoundException {
        try {
          // 我们要创建的Class对象
          Class clasz = null;
    
          // 必需的步骤1:如果类已经在系统缓冲之中
          // 我们不必再次装入它
          clasz = findLoadedClass( name );
    
          if (clasz != null)
            return clasz;
    
          // 下面是定制部分
          try {
            // 读取经过加密的类文件
            byte classData[] = Util.readFile( name+".class" );
    
            if (classData != null) {
              // 解密...
              byte decryptedClassData[] = cipher.doFinal( classData );
    
              // ... 再把它转换成一个类
              clasz = defineClass( name, decryptedClassData,
                0, decryptedClassData.length );
              System.err.println( "[DecryptStart: decrypting class "+name+"]" );
            }
          } catch( FileNotFoundException fnfe ) {
          }
    
          // 必需的步骤2:如果上面没有成功
          // 我们尝试用默认的ClassLoader装入它
          if (clasz == null)
            clasz = findSystemClass( name );
    
          // 必需的步骤3:如有必要,则装入相关的类
          if (resolve && clasz != null)
            resolveClass( clasz );
    
          // 把类返回给调用者
          return clasz;
        } catch( IOException ie ) {
          throw new ClassNotFoundException( ie.toString()
    );
        } catch( GeneralSecurityException gse ) {
          throw new ClassNotFoundException( gse.toString()
    );
        }
      }
    }
    对于未经加密的应用,正常执行方式如下:
    % java App arg0 arg1 arg2
    对于经过加密的应用,则相应的运行方式为:
    % java DecryptStart key.data App arg0 arg1 arg2

DecryptStart有两个目的。一个DecryptStart的实例就是一个实施即时解密操作的定制ClassLoader;同时,DecryptStart还包含一个main过程,它创建解密器实例并用它装入和运行应用。示例应用App的代码包含在App.java、Foo.java和Bar.java内。Util.java是一个文件I/O工具,本文示例多处用到了它。完整的代码请从本文最后下载。

五、注意事项
我们看到,要在不修改源代码的情况下加密一个Java应用是很容易的。不过,世上没有完全安全的系统。本文的加密方式提供了一定程度的源代码保护,但对某些攻击来说它是脆弱的。

虽然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。降低这种风险的办法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。

另外还要记住的是,大多数JVM本身并不安全。狡猾的黑客可能会修改JVM,从ClassLoader之外获取解密后的代码并保存到磁盘,从而绕过本文的加密技术。Java没有为此提供真正有效的补救措施。

不过应该指出的是,所有这些可能的攻击都有一个前提,这就是攻击者可以得到密匙。如果没有密匙,应用的安全性就完全取决于加密算法的安全性。虽然这种保护代码的方法称不上十全十美,但它仍不失为一种保护知识产权和敏感用户数据的有效方案。

参考资源

  • 在运行时刻更新功能模块。介绍了一个利用类库加载器ClassLoader 实现在运行时刻更新部分功能模块的Java程序,并将其与C/C++中实现同样功能的动态链接库方案进行了比较。
  • Java 技巧 105:利用 JWhich 掌握类路径。展示一个简单的工具,它可以清楚地确定类装载器从类路径中载入了什么 Java 类。
  • 要了解更多的 Java 安全信息,请阅读 java.sun.com的 Java Security API 页。
  • 如何封锁您的(或打开别人的) Java 代码。Java 代码反编译和模糊处理的指南。
  • 使您的软件运行起来:摆弄数字。真正安全的软件需要精确的随机数生成器。
  • 下载本文代码:EncryptedJavaClass_code.zip
<

小小豆叮

利用RMI实现JAVA分布式应用

Java RMI (Remote Method Invocation 远程方法调用)是用Java在JDK1.1中实现的,它大大增强了Java开发分布式应用的能力。Java作为一种风靡一时的网络开发语言,其巨大的威力就体现在它强大的开发分布式网络应用的能力上,而RMI就是开发百分之百纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是RPC的Java版本。但是传统RPC并不能很好地应用于分布式对象系统。而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP是专为Java的远程对象制定的协议。因此,Java RMI具有Java的"Write Once,Run Anywhere"的优点,是分布式应用系统的百分之百纯Java解决方案。用Java RMI开发的应用系统可以部署在任何支持JRE(Java Run Environment Java,运行环境)的平台上。但由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非Java语言书写的对象进行通信。本文拟从程序的角度举例介绍怎样利用RMI实现Java分布式应用。 一、RMI系统运行机理 RMI应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户机调用这些远程对象的方法。而典型的客户机程序则从服务器中得到一个或多个远程对象的引用,然后调用远程对象的方法。RMI为服务器和客户机进行通信和信息传递提供了一种机制。 在与远程对象的通信过程中,RMI使用标准机制:stub和skeleton。远程对象的stub担当远程对象的客户本地代表或代理人角色。调用程序将调用本地stub的方法,而本地stub将负责执行对远程对象的方法调用。在RMI中,远程对象的stub与该远程对象所实现的远程接口集相同。调用stub的方法时将执行下列操作:(1) 初始化与包含远程对象的远程虚拟机的连接;(2) 对远程虚拟机的参数进行编组(写入并传输);(3) 等待方法调用结果;(4) 解编(读取)返回值或返回的异常;(5) 将值返回给调用程序。为了向调用程序展示比较简单的调用机制,stub将参数的序列化和网络级通信等细节隐藏了起来。在远程虚拟机中,每个远程对象都可以有相应的skeleton(在JDK1.2环境中无需使用skeleton)。Skeleton负责将调用分配给实际的远程对象实现。它在接收方法调用时执行下列操作:(1) 解编(读取)远程方法的参数;(2) 调用实际远程对象实现上的方法;(3) 将结果(返回值或异常)编组(写入并传输)给调用程序。stub和skeleton由rmic编译器生成。 利用RMI编写分布式对象应用程序需要完成以下工作:(1) 定位远程对象。应用程序可使用两种机制中的一种得到对远程对象的引用。它既可用RMI的简单命名工具rmiregistry来注册它的远程对象,也可以将远程对象引用作为常规操作的一部分来进行传递和返回。(2)与远程对象通信。远程对象间通信的细节由RMI处理,对于程序员来说,远程通信看起来就像标准的Java方法调用。(3)给作为参数或返回值传递的对象加载类字节码。因为RMI允许调用程序将纯Java对象传给远程对象,所以,RMI将提供必要的机制,既可以加载对象的代码又可以传输对象的数据。在RMI分布式应用程序运行时,服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器上的注册服务程序中用远程对象的名字查找该远程对象,然后调用它的方法。 二、对象序列化 在RMI分布式应用系统中,服务器与客户机之间传递的Java对象必须是可序列化的对象。不可序列化的对象不能在对象流中进行传递。对象序列化扩展了核心Java输入/输出类,同时也支持对象。对象序列化支持把对象编码以及将通过它们可访问到的对象编码变成字节流;同时,它也支持流中对象图形的互补重构造。序列化用于轻型持久性和借助于套接字或远程方法调用(RMI)进行的通信。序列化中现在包括一个 API(Application Programming Interface,应用程序接口),允许独立于类的域指定对象的序列化数据,并允许使用现有协议将序列化数据域写入流中或从流中读取,以确保与缺省读写机制的兼容性。 为编写应用程序,除多数瞬态应用程序外,都必须具备存储和检索 Java对象的能力。以序列化方式存储和检索对象的关键在于提供重新构造该对象所需的足够对象状态。存储到流的对象可能会支持 Serializable(可序列化)或 Externalizable(可外部化)接口。对于Java对象,序列化形式必须能标识和校验存储其内容的对象所属的 Java类,并且将该内容还原为新的实例。对于可序列化对象,流将提供足够的信息将流的域还原为类的兼容版本。对于可外部化对象,类将全权负责其内容的外部格式。序列化 Java 对象的目的是:提供一种简单但可扩充的机制,以序列化方式维护 Java对象的类型及安全属性;具有支持编组和解编的扩展能力以满足远程对象的需要;具有可扩展性以支持 Java 对象的简单持久性;只有在自定义时,才需对每个类提供序列化自实现;允许对象定义其外部格式。 三、分布式应用的实现和运行步骤 编写Java RMI分布式应用程序的步骤主要包括以下几步: (1) 将远程类的功能定义为Java接口。在Java中,远程对象是实现远程接口的类的实例。在远程接口中声明每个要远程调用的方法。远程接口具有如下特点:1) 远程接口必须声明为public。如果不这样,则除非客户端与远程接口在同一个包内,否则当试图装入实现该远程接口的远程对象时会得到错误结果。2) 远程对象扩展java.rmi.Remote接口。3) 除了所有应用程序特定的例外之外,每个方法还必须抛出java.rmi.RemoteException例外。4) 任何作为参数或返回值传送的远程对象的数据类型必须声明为远程接口类型,而不是实现类。 (2) 编写和实现服务器类。该类是实现(1)中定义的远程接口。所以在该类中至少要声明实现一个远程接口,并且必须具有构造方法。在该类中还要实现远程接口中所声明的各个远程方法。 (3) 编写使用远程服务的客户机程序。在该类中使用java.rmi.Naming中的lookup()方法获得对远程对象的引用,依据需要调用该引用的远程方法,其调用方式和对本地对象方法的调用相同。 实现了服务器和客户机的程序后,就是编译和运行该RMI系统。其步骤有: (1) 使用javac编译远程接口类,远程接口实现类和客户机程序。 (2) 使用rmic编译器生成实现类的stub和skeleton。 (3) 启动RMI注册服务程序rmiregistry。 (4) 启动服务器端程序。 (5) 启动客户机程序。 四、实例分析 本文以一个实例来说明怎样编写RMI分布式应用系统。该实例用于实现一个三层的Client/Server程序,即包括数据库服务器、应用服务器和客户机三部分。其功能是让应用服务器把客户机发出的数据库查询请求传送到数据库服务器,数据库服务器再把数据库查询操作的结果送回应用服务器,然后送到客户端显示。整个系统包括服务器端和客户端程序,但这只是逻辑上的划分,其实它们都位于同一个机器上,即Web服务器。在客户端是一个Applet,通过它获得远程对象的引用,并调用远程对象的方法。而应用服务器上的程序其主要的功能是完成对数据库连接的配置、数据库的查询操作和把查询结果转换成可序列化的RecSet对象,当客户端调用远程对象的远程方法时就把它作为参数或返回值传递给客户端,实现服务器与客户机的通信。客户端得到查询结果后把它显示出来。在应用服务器和数据库服务器之间是通过Java的JDBC来连接的,本作者使用的是Sybase公司提供的JDBC Driver-jConnect5.0来实现对数据库的连接的。应用服务器同时也是Web服务器,因此只要上网的计算机通过浏览器就可以下载客户端的Applet程序到本地机上运行。具体实现步骤和部分程序代码如下。 <

小小豆叮

JDBC接口技术

-------------------------------------------------------------------------------- JDBC是一种可用于执行SQL语句的JavaAPI(ApplicationProgrammingInterface应用程序设计接口)。它由一些Java语言编写的类和界面组成。JDBC为数据库应用开发人员、数据库前台工具开发人员提供了一种标准的应用程序设计接口,使开发人员可以用纯Java语言编写完整的数据库应用程序。 一、ODBC到JDBC的发展历程 说到JDBC,很容易让人联想到另一个十分熟悉的字眼“ODBC”。它们之间有没有联系呢?如果有,那么它们之间又是怎样的关系呢? ODBC是OpenDatabaseConnectivity的英文简写。它是一种用来在相关或不相关的数据库管理系统(DBMS)中存取数据的,用C语言实现的,标准应用程序数据接口。通过ODBCAPI,应用程序可以存取保存在多种不同数据库管理系统(DBMS)中的数据,而不论每个DBMS使用了何种数据存储格式和编程接口。 1.ODBC的结构模型 ODBC的结构包括四个主要部分:应用程序接口、驱动器管理器、数据库驱动器和数据源。 应用程序接口:屏蔽不同的ODBC数据库驱动器之间函数调用的差别,为用户提供统一的SQL编程接口。 驱动器管理器:为应用程序装载数据库驱动器。 数据库驱动器:实现ODBC的函数调用,提供对特定数据源的SQL请求。如果需要,数据库驱动器将修改应用程序的请求,使得请求符合相关的DBMS所支持的文法。 数据源:由用户想要存取的数据以及与它相关的操作系统、DBMS和用于访问DBMS的网络平台组成。 虽然ODBC驱动器管理器的主要目的是加载数据库驱动器,以便ODBC函数调用,但是数据库驱动器本身也执行ODBC函数调用,并与数据库相互配合。因此当应用系统发出调用与数据源进行连接时,数据库驱动器能管理通信协议。当建立起与数据源的连接时,数据库驱动器便能处理应用系统向DBMS发出的请求,对分析或发自数据源的设计进行必要的翻译,并将结果返回给应用系统。 下图是ODBC程序的基本流程: 2.JDBC的诞生 自从Java语言于1995年5月正式公布以来,Java风靡全球。出现大量的用java语言编写的程序,其中也包括数据库应用程序。由于没有一个Java语言的API,编程人员不得不在Java程序中加入C语言的ODBC函数调用。这就使很多Java的优秀特性无法充分发挥,比如平台无关性、面向对象特性等。随着越来越多的编程人员对Java语言的日益喜爱,越来越多的公司在Java程序开发上投入的精力日益增加,对java语言接口的访问数据库的API的要求越来越强烈。也由于ODBC的有其不足之处,比如它并不容易使用,没有面向对象的特性等等,SUN公司决定开发一Java语言为接口的数据库应用程序开发接口。在JDK1.x版本中,JDBC只是一个可选部件,到了JDK1.1公布时,SQL类包(也就是JDBCAPI)就成为Java语言的标准部件。 二、JDBC技术概述 JDBC是一种可用于执行SQL语句的JavaAPI(ApplicationProgrammingInterface,应用程序设计接口)。它由一些Java语言写的类、界面组成。JDBC给数据库应用开发人员、数据库前台工具开发人员提供了一种标准的应用程序设计接口,使开发人员可以用纯Java语言编写完整的数据库应用程序。 通过使用JDBC,开发人员可以很方便地将SQL语句传送给几乎任何一种数据库。也就是说,开发人员可以不必写一个程序访问Sybase,写另一个程序访问Oracle,再写一个程序访问Microsoft的SQLServer。用JDBC写的程序能够自动地将SQL语句传送给相应的数据库管理系统(DBMS)。不但如此,使用Java编写的应用程序可以在任何支持Java的平台上运行,不必在不同的平台上编写不同的应用。Java和JDBC的结合可以让开发人员在开发数据库应用时真正实现“WriteOnce,RunEverywhere!” Java具有健壮、安全、易用等特性,而且支持自动网上下载,本质上是一种很好的数据库应用的编程语言。它所需要的是Java应用如何同各种各样的数据库连接,JDBC正是实现这种连接的关键。 JDBC扩展了Java的能力,如使用Java和JDBCAPI就可以公布一个Web页,页中带有能访问远端数据库的Ap <

小小豆叮

编写一个JAVA的队列类

队列是设计程序中常用的一种数据结构。它类似日常生活中的排队现象,采用一种被称为“先进先出”(LIFO)的存储结构。数据元素只能从队尾进入,从队首取出。在队列中,数据元素可以任意增减,但数据元素的次序不会改变。每当有数据元素从队列中被取出,后面的数据元素依次向前移动一位。所以,任何时候从队列中读到的都是队首的数据。 根据这些特点,对队列定义了以下六种操作: enq(x) 向队列插入一个值为x的元素; deq() 从队列删除一个元素; front() 从队列中读一个元素,但队列保持不变; empty() 判断队列是否为空,空则返回真; clear() 清空队列; search(x) 查找距队首最近的元素的位置,若不存在,返回-1。 Vector类是JAVA中专门负责处理对象元素有序存储和任意增删的类,因此,用Vector可以快速实现JAVA的队列类。 public class Queue extends java.util.Vector { public Queue() { super(); } public synchronized void enq(Object x) { super.addElement(x); } public synchronized Object deq() { /* 队列若为空,引发EmptyQueueException异常 */ if( this.empty() ) throw new EmptyQueueException(); Object x = super.elementAt(0); super.removeElementAt(0); return x; } public synchronized Object front() { if( this.empty() ) throw new EmptyQueueException(); return super.elementAt(0); } public boolean empty() { return super.isEmpty(); } public synchronized void clear() { super.removeAllElements(); } public int search(Object x) { return super.indexOf(x); } } public class EmptyQueueException extends java.lang.RuntimeException { public EmptyQueueException() { super(); } } 以上程序在JDK1.1.5下编译通过 <

小小豆叮

用Java绘制K线

Java语言中的Applet(Java小程序)和Application(Java应用程序)是在结构和功能上都存在很大差异的两种不同的编程方式。Applet应用于Web页上,可做出多姿多彩的页面特效,给网站增辉添色;Application则与其他编程语言(如VB、VC)一样,可编制各种应用程序。

---- 本文要讨论的是第一种情况,在Web页上用Java Applet绘制K线图。

---- K线是股市行情分析中的一种参数指标,用股票每日的开盘价、最高价、最低价、收盘价及成交量等数据进行作图,配合五日、十日均线便可反映出一个阶段内该支股票的涨跌走势。

---- Java Applet所做的K线图多应用于证券类型的网站,以方便网上股民的分析操作。不过由于我国网速的关系,目前有些证券类网站把K线图做成了图片以节省下载的时间。当然,其绘制K线所用的工具(Java Applet)及绘制方法还是相同的。

---- 在编写绘制K线的Java Applet时,有几处关键问题要解决,即:1)多线程的使用;2)布局管理器的选择;3)数据输入、输出流的应用;4)设置、监听鼠标与键盘事件。

---- 下面通过一个实例,来详细阐述整个编程过程和方法。

---- 一、使用多线程

---- 一个完整的Java Applet包含四个方法,Init()、Start()、Stop()、Destroy()(即:初始化、开始、停止、清除),构成一个完整的生命周期,其运行次序也是由上而下顺序执行。

---- 在绘制K线图的过程中,除了要绘制窗体及代表股票升降的阴线、阳线矩形外,还要监听鼠标事件并同时绘制一个自由移动的“十字”游标,来定位显示所检索股票每日的各种价格数值(如开盘价、收盘价等);这时,为了避免闪烁和减少重新计算的等待时间,除了重绘跟随鼠标移动的“十字”游标外,对页面的布局及阴线、阳线矩形等不进行重绘;这就需要应用多线程来完成该项任务。

---- 1.关于多线程的一些基本概念:

---- 多线程实际上就是启动另一个进程,其运行的过程独立于主程序之外,并从主程序的Start()方法载入、由Run()方法调用执行。实现多线程的方法有两种,即:创建Thread类的子类(类的继承)和实现runnable接口。

---- 为便于使用,Java把所有有关线程的功能封装成Java类库中的一个类:Java.lang.Thread。通过这个类,Java可以创建、控制和终止线程,完成有关多线程的所有操作。

---- 在Java语言中,一个子类只能继承一个超类(父类),由于我们所要编写的Java Applet是应用于网页中的,首先必须继承浏览器类(java.applet);因此,在本例中我们通过实现runnable接口的方法来实现多线程,实现的语句如下:

---- public class StockApplet extends java.applet.Applet implements Runnable

---- 2.多线程的应用:

---- 首先,在Init()方法中对要创建的线程(M_pointThread)进行定义及初始化:

    Thread M_pointThread=null;
---- 然后,在Start()和Stop()方法中加入以下代码:

---- //当打开浏览器页面开始载入Java Applet代码时,执行start()方法

    public void start()    
    {
if (M_pointThread==null)   //如果线程尚未产生
{
M_pointThread=new Thread(this);  //则创建一个新线程
M_pointThread.start();   //启动该线程
          }
     }
//在当前页面关闭或转向其他Web页面时,
调用stop()方法,以释放系统资源
     public void stop()
     {
	         if (M_pointThread!=null)  
 //如果线程尚在运行
	          {
            M_pointThread.stop(); 
  //停止该线程

            M_pointThread=null;    
//释放线程所占资源
	           }
      }

---- 3.编写线程运行的代码:
public void run()
{
   M_graphics=getGraphics();
   M_graphics.setXORMode(Color.white);  
 //采用异或方法绘制“十字”游标
   while(true)
   {
	   try
 	 {
if(MouseMove==true)   //侦测到鼠标的移动后,
执行以下代码
{   //当鼠标位于以下区域(Java Applet布局)
内时,执行画线操作
if(x0 >50 && x0< 600){M_graphics.drawLine
(x0,30,x0,380);}
if(y0 >30 && y0 <380){M_graphics.drawLine
(50,y0,600,y0);}
if(X >50 && X< 600){M_graphics.drawLine
(X,30,X,380);}
if(Y >30 && Y< 380){M_graphics.drawLine
(50,Y,600,Y);}
MouseMove=fase;
x0=X;     //传递当前座标参数
		   y0=Y; 
        }
}catch(NullPointerException npe){}  
  //捕获可能出现的异常
  }

---- 二、布局管理器

---- 本例中的Java Applet运行时,要在页面上Java Applet的运行区域内绘制相应的“标签”来显示不同的数值,并要加入一个“文本框”来接收输入的股票代码。对这些“标签”和“文本框”位置及大小的设定,便属于布局管理的应用范畴。

---- Java语言中提供的布局管理器种类有:边界式布局、卡片式布局、流式布局和网格式布局等,各有不同的特点,可根据实际需要选用;但有最大自由设计空间的是“无布局管理器”——即不使用任何布局格式,而通过手工方式添加组件到页面布局的绝对位置上。本例中使用的便是“无布局管理器”。

---- 在使用“无布局管理器”时,首先要作出声明,即:

setLayout(null);

然后用reshape()方法指定组件的具体位置和尺寸,
基本语句如下所示:
Label label1=new Label();   
      //定义标签
	this.add(label1);  
              //把标签加入布局管理器
	label1.reshape(10,10,30,30);   
    //在指定位置绘制标签
---- 对于多个标签和文本框,参照此格式添加即可。

---- 三、输入、输出数据流

---- Java Applet在客户端浏览器上运行时,要从服务器端读取股票的相关数据进行做图,这就涉及到Java数据流的运用。

---- Java.io包提供了多个用于与各种I/O设备交换信息的类,其最顶层的两个类便是输入流类(InputStream)和输出流类(OutputStream)。

---- 为了便于实现,在本例中我们先在服务器端把股票数据库(如:show128.dbf)中的有关字段写入一文本文件中(其格式为:第一行为股票的汉字名称,以后每一行内包括:开盘日期、开盘价、最高价、最低价、收盘价、成交量),并以该支股票的代码作为文件名(如:600001.txt);然后,再用Java Applet从服务器端的文本文件中读取数据。这样,就把对服务器端数据库的访问转化为对文件的I/O操作,实现的语句如下:

URL urlc=new URL("http://127.0.0.1/temp
/"+FileName+".txt"); 
	BufferedReader 
bis=new BufferedReader
(new InputStreamReader(urlc.openStream()));
---- 注:第一条语句中的http://127.0.0.1/temp/ 为在本机执行调试操作的URL地址,在上传到服务器上后要做相应的修改;FileName为要读取的文本文件名。

---- 第二条语句定义了读取输入数据流的方法。

---- 四、监听键盘及鼠标事件

---- 在Java Applet小程序中,单击按钮、键入文本、使用鼠标或执行任何与界面相关的动作时,就发生一个事件,小程序就会作出适当的反应。

---- 在本例中,我们通过使用action()方法来获得Java Applet小程序运行时所发生的事件。语句格式如下:

public boolean action(Event evt,Object arg)
	{
		FileName=text1.getText();   
//提取文本框中输入的参数
		ReadData();
//调用读数据流数据的方法
		return true; 
     //处理完毕,不需要其他方法再做处理
	}
---- 上面的action()方法中含有两个参数:一个是Event类的一个对象evt;另一个是Object类的一个对象arg。Event对象告诉我们发生了哪种事件,而Object对象将进一步告诉我们有关该事件的情况。每当有Event监听的事件发生时,Java Applet便自动调用该action()方法。

---- 至此,在掌握了上述编程中的一些要点后,我们便可以成功地编制出一个完整的绘制K线的Java Applet小程序了(完整的源代码附后)。

---- 五、编制HTML文档(Stock.html)

---- 程序编制完成后,另外要做的是设置在Web页面上调用Java Applet的页面,即编制HTML文档。Stock.html文档的示例代码如下:

< HTML >< HEAD >< TITLE >股票K线图< /TITLE >< /HEAD >
< body bgcolor=#00ffff >
< APPLET 
code=StockApplet.class codeBase=C:\javatemp\Project1\ 
 name=StockApplet 
style="FONT-FAMILY: sans-serif;
 HEIGHT: 410px; WIDTH: 610px"  >
	< /APPLET >
	< /body >
< /HTML >
---- 在< APPLET >< /APPLET >标签对内,是对Java Applet小程序的调用代码。其中的StockApplet.class是经编译后生成的class文件,codeBase指向的是class文件所存放的路径。如果class文件与HTML文档存放在同一目录下,则可以省略这一项。

---- 至此,编制Java Applet小程序的工作便全部完成。

---- 在个人计算机上进行调试时,首先要安装个人Web服务,然后,把StockApplet.class与HTML文档一起拷贝到本机的WWW服务目录下(如:C:\Inetpub\wwwroot\),打开浏览器,在地址栏内输入http://127.0.0.1/Stock.html,回车,进行测试操作(注意:此时要在Stock.html文档中删除codeBase一项,否则会产生class文件未找到的错误)。

---- 调试通过后,便可上传至服务器投入运行了。 <

小小豆叮

实现JAVA的动态类载入机制

作 为 充 分 利 用Java 的 动 态 类 载 入 机 制 的 最 好 例 子, 带 有Java 扩 展 的Web 浏 览 器 根 据 请 求 从 网 络 或 本 地 文 件 系 统 中 动 态 加 载Java applet( 遵 循 一 定 规 则 的Java 小 应 用 程 序 类), 然 后 在 本 地 系 统 中 执 行 它, 大 大 增 强 了 主 页 的 功 能。

---- 其 实,Java 本 身 就 是 一 种 极 具 动 态 性 的 语 言。 类 似Windows 的 动 态 链 接 库(DLL),Java 应 用 程 序 总 是 被 编 译 成 若 干 个 单 独 的class 文 件, 程 序 执 行 时 根 据 需 要 由Java 虚 拟 机 动 态 载 入 相 应 的 类。 这 种 机 制 使 编 写 动 态 的 分 布 式 应 用 程 序 成 为 可 能: 我 们 可 以 在 客 户 端 编 写 自 己 的 类 载 入 器, 而 真 正 执 行 的 程 序 却 存 放 在 本 地、 局 域 网 或 世 界 另 一 端 的 主 机 上。 下 面 将 介 绍 如 何 在 自 己 的 应 用 程 序 中 实 现Java 的 动 态 类 载 入 机 制。

与 动 态 类 载 入 有 关 的 系 统 类

---- 为 支 持 动 态 类 载 入 机 制, 在 系 统 类 组java.lang 中 提 供 了 两 个 类:Class 类 和ClassLoader 类。

---- 1、 类java.lang.Class。 在Java 虚 拟 机 中, 每 一 个 类 或 接 口 都 是 由Class 类 来 操 纵 的, 它 不 能 被 显 式 的 实 例 化, 必 须 用 其 他 方 法 来 获 取Class 类 的 对 象。 动 态 类 载 入 机 制 的 关 键 一 步 在 于 如 何 获 得 指 定 类 的Class 类 型 的 对 象。 相 关 方 法 主 要 有:

---- public static Class forName(String className)

---- 这 是 一 个 静 态 方 法, 它 获 取 指 定 名 字 的 类 的Class 类 型 对 象, 类 名 可 以 是 象“sun.applet.Applet” 这 样 的 字 符 串, 但 不 能 带 有 路 径 或 网 络 地 址 等 信 息。 这 是 从 本 地 系 统 中 动 态 载 入 类 的 最 方 便 的 办 法。

---- public Object newInstance()

---- 这 是 最 重 要 的 一 个 方 法, 它 建 立 由Class 类 型 对 象 描 述 的 指 定 类 的 实 例。

---- 下 面 是 一 个 用forName() 和newInstance() 方 法 实 现 动 态 类 载 入 的 代 码,share 类 包 含 一 个 接 口, 详 细 内 容 将 在 第 三 部 分 中 解 释。

try{
//根据类名建立Class类型的对象。
Class cc =Class.forName("类名"));
//建立被载入类类的实例并强制类型转换,
 值赋给share类型的变量。
share oo=((share)cc).newInstance();
//调用该类的方法进行工作。
}
catch (Exception ex){
//如果发生例外,则进行相应处理。
};

---- 2、 类java.lang.ClassLoader。 这 是 一 个 抽 象 类, 如 果 打 算 运 用 它, 必 须 继 承 它 并 重 写 它 的loadClass() 方 法。 其 主 要 方 法 有:

---- protected ClassLoader();

---- 这 是 一 个 建 构 元, 可 以 用 它 建 立 一 个ClassLoader 类 的 实 例。 注 意 继 承 这 个 类 的 类 必 须 重 写 这 个 方 法, 而 不 能 使 用 缺 省 的 建 构 元。

---- protected abstract Class loadClass(String name, boolean resolve)

---- 载 入 指 定 的 类 数 据, 建 立Class 类 型 的 对 象 并 根 据 需 要 解 析 它。 这 是 一 个 抽 象 方 法, 大 家 必 须 在 自 己 的 子 类 中 重 写 这 个 方 法, 重 写 的 规 则 可 以 参 考 第 三 部 分 的 例 子。

---- protected final Class defineClass(byte data[], int offset, int length)

---- 将 字 节 数 组 中 的 数 据 定 义 为Class 类 型 的 对 象, 字 节 数 组 的 格 式 由 虚 拟 机 规 定。

---- protected final Class findSystemClass(String name)

---- 根 据 指 定 的 类 名 载 入 类, 它 会 自 动 在 当 前 目 录 和 环 境 变 量“CLASSPATH” 指 定 的 路 径 中 寻 找, 如 果 找 不 到, 则 会 抛 出ClassNotFoundException 例 外。

---- protected final void resolveClass(Class c)

---- 通 过 载 入 与 指 定 的 类 相 关 的 所 有 类 来 解 析 这 个 类, 这 必 须 在 类 被 使 用 之 前 完 成。

扩 充ClasslLader 类 以 实 现 动 态 类 载 入

---- 理 解 动 态 类 载 入 机 制 的 最 好 办 法 是 通 过 例 子, 下 面 这 个 完 整 的 例 子 由 四 个 类 组 成, 分 别 解 释 如 下:

---- 1、MyClassLoader 类 是ClassLoader 类 的 子 类, 它 重 写 了loadClass 方 法, 实 现 了 将 网 络 上 用URL 地 址 指 定 的 类 动 态 载 入, 取 得 它 的Class 类 型 对 象 的 功 能。 读 者 可 根 据 自 己 载 入 类 的 具 体 方 式 改 写 下 面 的 代 码。

import java.io.*;
import java.util.*;
import java.net.*;

public class MyClassLoader extends ClassLoader {	
	//定义哈希表(Hashtable)类型的变量,
     用于保存被载入的类数据。
	Hashtable loadedClasses;

	public MyClassLoader() {
		loadedClasses = new Hashtable();
	}

public synchronized Class loadClass(String className,
		boolean resolve) throws ClassNotFoundException {
		Class newClass;
		byte[] classData;
		
		//检查要载入的类数据是否已经被保存在哈希表中。
		newClass = (Class) loadedClasses.get(className);
		//如果类数据已经存在且resolve值为true,则解析它。
		if (newClass != null){
			if (resolve) 
				resolveClass(newClass);
			return newClass;
		}

---- /* 首 先 试 图 从 本 地 系 统 类 组 中 载 入 指 定 类。 这 是 必 须 的, 因 为 虚 拟 机 将 这 个 类 载 入 后, 在 解 析 和 执 行 它 时 所 用 到 的 任 何 其 他 类, 如java.lang.System 类 等, 均 不 再 使 用 虚 拟 机 的 类 载 入 器, 而 是 调 用 我 们 自 制 的 类 载 入 器 来 加 载。*/

		try {
			newClass = findSystemClass(className);
			return newClass;
		} catch (ClassNotFoundException e) {
			System.out.println(className+" is not a system class!");
		}

		//如果不是系统类,
         则试图从网络中指定的URL地址载入类。
		try {
			//用自定义方法载入类数据,
             存放于字节数组classData中。
			classData = getClassData(className);
			//由字节数组所包含的数据建立一个class类型的对象。
			newClass = defineClass(classData, 0, classData.length);
			if (newClass == null)
				throw new ClassNotFoundException(className);
		} catch (Exception e) {
			throw new ClassNotFoundException(className);
		}

		//如果类被正确载入,
         则将类数据保存在哈希表中,以备再次使用。
		loadedClasses.put(className, newClass);
		//如果resolve值为true,则解析类数据。
		if (resolve){
			resolveClass(newClass);
			}
		return newClass;
     }
	//这个方法从网络中载入类数据。
protected byte[] getClassData(String className)
throws IOException {
	byte[] data;
	int length;
		try {
			//从网络中采用URL类的方法
             载入指定URL地址的类的数据。
			URL url = new URL(className.endsWith(".class") ?
                     className : className + ".class");
			URLConnection connection = url.openConnection();
			InputStream inputStream = connection.getInputStream();
			length = connection.getContentLength();

			data = new byte[length];
			inputStream.read(data); 
			inputStream.close();
			return data;
		} catch(Exception e) {
			throw new IOException(className);
		}
	}
}

---- 2、 由 于Java 是 强 类 型 检 查 语 言, 通 过 网 络 载 入 后 的 类 被 实 例 化 后 只 是 一 个Object 类 型 的 对 象, 虚 拟 机 并 不 知 道 它 包 含 那 些 方 法, 应 从 哪 个 方 法 开 始 执 行。 因 此, 可 以 被 动 态 载 入 的 类 必 须 继 承 某 一 个 抽 象 类 或 实 现 某 一 个 接 口, 因 为 父 类 只 能 有 一 个, 所 以 通 常 用 实 现 特 定 接 口 的 办 法。 下 面 的 代 码 定 义 了 一 个 接 口 类share 和 它 的 方 法start()。

public interface share {
	public void start(String[] option);
}

---- 3、TestClassLoader 类 通 过 使 用MyClassLoader 类 的loadClass() 方 法, 将 指 定URL 地 址 的 类 载 入 并 在 本 地 系 统 执 行 它, 实 现 了 类 的 动 态 载 入。 注 意 在 执 行 被 载 入 类 的 方 法 前 一 定 要 将 它 进 行 强 制 数 据 类 型 转 换。

public class TestClassLoader {
	public static void main(String[] args){
		MyClassLoader ll = new MyClassLoader();
      Class cc;
		Object oo;
      String ss = "http://kyzser.ydxx/classLoader/Tested.class";

      if (args.length != 0) ss = args[0];
      try {
		System.out.println("Loading class "+ss+"...");
			//使用重写的方法loadClass()载入类数据。
				cc = ll.loadClass(ss);
				System.out.println("Creat instance...");
				//创建Object类型的类实例。
				oo = cc.newInstance();
				System.out.println("Call start() method..."); 
 				//强制类型转换并执行被载入类中的方法。
				((share) oo).start(args);
        	}catch (Exception e) {
        	System.out.println("Caught exception : "+e);
    		}
}
}

---- 4、Tested 类 很 简 单, 可 以 将 它 放 在 任 何WEB 服 务 器 上, 但 应 注 意 能 动 态 载 入 且 被 执 行 的 类, 一 定 要 实 现 预 先 定 义 的 接 口 中 的 方 法。 下 面 的 例 子 实 现 了 接 口share 的start 方 法。

public class Tested implements share{
	public void start(String[] option){
		//填写程序代码。
	}
}

动 态 类 载 入 机 制 的 几 点 应 用

---- 1、 开 发 分 布 式 应 用。 这 对 开 发 远 程 的 客 户 端 应 用 程 序 最 有 用, 客 户 端 仅 需 要 安 装 一 些 基 本 的 系 统 和 一 个 能 实 现 动 态 类 载 入 机 制 的 类, 需 要 本 地 系 统 不 存 在 的 功 能 时, 仅 需 要 从 网 络 动 态 载 入 并 执 行 相 应 类 即 可 获 得 特 定 功 能。 因 为 客 户 端 所 使 用 的 总 是 软 件 的 最 新 版 本, 所 以 不 再 存 在 软 件 的 升 级 和 维 护 问 题, 即 实 现 了 所 谓 的“ 零 管 理” 模 式。

---- 2、 对.class 文 件 加 密。 由 于Java 的 字 节 码(bytecode) 容 易 被 反 编 译, 大 部 分 开 发Java 应 用 程 序 的 公 司 均 担 心 自 己 的 成 果 被 别 人 不 劳 而 获。 其 实 可 以 将 类 文 件 进 行 适 当 的 加 密 处 理, 执 行 时 使 用 自 己 的 类 载 入 器 进 行 相 应 的 解 密, 就 可 以 解 决 这 个 问 题。

---- 3、 使 第 三 方 开 发 者 易 于 扩 展 你 的 应 用。 从 前 面 可 知, 所 有 可 以 被 你 的 类 载 入 器 动 态 载 入 并 被 执 行 的 类, 必 须 继 承 你 定 义 的 类 或 实 现 你 定 义 的 接 口, 这 样, 你 可 以 制 订 一 些 规 则, 使 其 他 开 发 者 不 必 了 解 你 的 应 用 程 序 也 可 以 扩 充 功 能。

---- 当 然, 有 利 必 有 弊, 在 网 络 中 使 用 动 态 类 载 入 的 主 要 缺 陷 在 于 安 全 性, 很 难 保 证 不 载 入 不 怀 好 意 的 代 码, 这 个 问 题 要 靠Java 的 安 全 管 理 器 和 适 当 的 加 密 算 法 来 解 决, 已 超 出 本 文 的 讨 论 范 围。

<

小小豆叮

在Applet中应用JDBC访问数据库

Applet是用Java语言编写的小应用程序,它能够嵌入在HTML中,并由WWW浏览器来解释执行。但是,如何在Applet中处理Internet世界中大量的数据和分布在网络各个角落的各种各样的数据库资源呢?这就要使用JDBC。 一、 JDBC的工作原理 JDBC(Java DataBase Connectivity)是用于执行SQL语句的Java应用程序接口,由一组用Java语言编写的类与接口组成。JDBC是一种规范,它让各数据库厂商为Java程序员提供标准的数据库访问类和接口,这样就使得独立于DBMS的Java应用程序的开发工具和产品成为可能。JDBC是利用JDBC-ODBC桥通过ODBC来访问数据库的,如下图所示: 二、 JDBC编写数据库程序的方法 1. 建立数据源 建立数据源是指建立ODBC数据源。 2. 建立连接 与数据库建立连接的标准方法是调用方法Drivermanger.getConnection(String url,String user,String password)。Drivermanger类用于处理驱动程序的调入并且对新的数据库连接提供支持。 3. 执行SQL语句 JDBC提供了Statement类来发送SQL语句,Statement类的对象由createStatement方法创建;SQL语句发送后,返回的结果通常存放在一个ResultSet类的对象中,ResultSet可以看作是一个表,这个表包含由SQL返回的列名和相应的值,ResultSet对象中维持了一个指向当前行的指针,通过一系列的getXXX方法,可以检索当前行的各个列,从而显示出来。 三、JDBC编写数据库程序的实现 为了便于问题的说明,作如下假设。运行环境:Windows 98 (附加Personal Web Server(PWS))、IE4.0或以上浏览器、Access97;程序编辑、编译环境:VJ++6.0。 1.建立数据源interweb,其连接的数据库是使用Access 97建立的interweb,其中的表为t_interdata,结构如下: 字段名 类型 长度 bh 文本 10 //编号 mc 文本 20 //名称 dj 货币 自动 //单价 2.实现程序 用VJ++6.0建立applet小程序Applet1.java,并且如下修改其中的代码 import java.awt.*; import java.applet.*; import java .sql.*; public class Applet1 extends Applet { public void init() { resize(400,300); } public void paint(Graphics g) //此方法用于显示输出 { this.setBackground(Color.lightGray ); //定义背景颜色 this.setForeground(Color.red); //定义前景颜色 String url="jdbc:odbc:interweb"; String ls_1="select * from t_interdata"; Try //异常处理模块 { Class.forName("com.ms.jdbc.odbc.JdbcOdbcDriver"); //加载驱动程序 //建立连接 Connection con=DriverManager.getConnection(url,"sa",""); //执行SQL Statement stmt=con.createStatement(); ResultSet result=stmt.executeQuery(ls_1); //返回结果 g.drawString("编号",40,40); g.drawString("名称",80,40); g.drawString("价值",160,40); int i=10; while(result.next()) { //取各个字段的值 g.drawString(result.getString(1),40,60+i); g.drawString(result.getString(2),80,60+i); g.drawString(result.getString(3),160,60+i); i+=20; } //关闭连接 result.close(); stmt.close(); con.close(); } //捕获异常 catch(SQLException ex){} catch(java.lang.Exception ex){} } } 编译后产生Applet1.class文件,嵌入到下面的page1.html中, <

小小豆叮

Java 概 述

一、Java 的 发 展 史

         1 <

小小豆叮

Java 的i18n问题

- Java 的i18n 问 题, 即Java 的Internationalization 问 题。 指 的 是 如 何 使 应 用 程 序 能 够 同 时 支 持 多 种 语 言 的 问 题。 对 我 国 这 样 的 非 英 语 国 家 而 汉 字 又 有 多 种 编 码 方 式 的 国 家 而 言 具 有 现 实 意 义。 本 文 将 对 用java 编 制i18n 程 序 的 方 法 作 一 介 绍。

---- 实 现 目 标

---- 作 为i18n 程 序, 不 单 是 能 够 识 别 不 同 编 码 这 么 简 单。 它 应 能 解 决 如 下 问 题:

  • 能 识 别 不 同 的 编 码 方 式, 如GB 码、BIG5 码 等;
  • 与 编 码 有 关 的 元 素, 如 状 态 行, 消 息, 按 钮 的caption 等 应 在 程 序 之 外 存 储。 使 新 增 一 种 语 言 时 不 用 修 改 程 序;
  • 根 据 不 同 的 语 言 习 惯 动 态 调 整 与 语 言 相 关 的 元 素, 如 数 字、 金 额、 日 期 等 的 显 示;

---- 解 决 方 法

---- 不 同 地 区 码 的 识 别

---- Java 中 用Locale 类 识 别 不 同 的 地 区 码。 创 建Locale 类 的 实 例 时 指 定 了 语 言 代 码 和 地 区 代 码。 创 建GB 中 文 和BIG5 中 文 资 源 的Locale 类 实 例 的 语 句 分 别 如 下:zhLocale=new Locale("zh","CN");twLocale=new Locale("tw","TW")。 此 构 造 函 数 第 一 个 参 数 是ISO-639 中 定 义 的 语 言 代 码(http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt); 第 二 个 参 数 为ISO-3166 中 定 义 的 国 家 代 码(http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html)。 当 用 户 选 定 了 适 用 的 语 言 后, 应 将 此Locale 设 为 默 认 值:Locale.setDefault(new Locale("zh","CN")). 与 语 言 相 关 的 资 源 单 独 存 放

---- Java 提 供 了 两 种 方 法 存 放 与 语 言 相 关 的 资 源。 一 种 是 用 文 本 文 件; 另 一 种 是 用ListResourceBundle 资 源 类。 下 面 分 别 阐 述 两 者 的 不 同 之 处。

  • 文 本 文 件

---- 使 用 文 本 文 件 存 放 资 源 的 好 处 是 简 单 易 用。 可 以 用 任 何 文 本 编 辑 器 编 写 此 文 件, 而 且 当 修 改 资 源 时 无 须 重 新 编 译 程 序。 其 格 式 是' 键= 值' 的 列 表。 例 子 如 下:

#The list in WebTaxResource_zh_CN.properties
button1= 税 金
button2= 税 率
status1= 初 始 化 中

---- 其 中 以'#' 开 头 的 行 为 注 释 行。 对 应 每 一 种 语 言 写 一 个 这 样 的 资 源 文 件, 但 所 有 的 资 源 文 件 都 必 须 包 含 相 同 的 键。

  • ListResourceBundle 资 源 类

---- 虽 然 用 文 本 文 件 存 储 资 源 非 常 容 易, 但 它 只 能 存 储 字 符 对 象。 而 对 于 如 数 字、 自 定 义 对 象 等 它 就 无 能 为 力 了。 因 此Java 提 供 了ListResourceBundle 类。 其 缺 点 是 每 次 对 资 源 的 修 改 都 必 须 重 新 编 译 程 序。 此 类 的 结 构 如 下:

//file WebTaxResource_zh_CN.java
import java.util.*;

public class WebTaxResource_zh_CN
extends ListResourceBundle {

  static final Object[][] contents = {
    {"frametitle","工资、薪金所得适用"},
    {"label_qizhengdian","起征点:"},
    {"label_shuikuan","税款:"},
    {"label_shourue","收入额:"},
    {"checkbox_qiushouru","求收入"},
    {"checkbox_qiushuie","求税额"},
    {"lable1","简易税金计算器"},
    {"button1","工资、薪金个人所得税计算"},
    {"button_caculate","计算"},
  };

  public Object[][] getContents() {
    return contents;
  }

}

---- 其 中 两 维 的Object 数 组 存 放 的 是 键- 值 对。 每 对 中 的 第 一 个 元 素 是 键。 在 各 个 资 源 类 中 所 有 键 的 数 量 和 标 识 都 必 须 完 全 一 致。

---- 资 源 的 获 取

---- 不 同 语 言 的 资 源 存 放 的 文 件 名 都 不 相 同, 那 如 何 从 正 确 的 文 件 取 得 我 们 需 要 的 资 源 呢 ? 留 意 到 前 面 例 子 中properties 文 件 名 和ListResourceBundle 类 名 中 下 划 线 后 的 部 分 吗 ? 没 错, 它 们 就 是 在 创 建Locale 实 例 时 指 定 的 语 言 代 码 和 地 区 代 码 ! 剩 下 的 问 题 就 是 要 解 决 下 划 线 前 面 的 基 本 类 名 部 分 了。 它 是 由 一 个ResourceBundle 类 的 实 例 来 指 定 的:

ResourceBundle bundle=ResourceBundle.getBundle
("WebTaxResource",currentLocale);

---- getBundle 的 第 一 个 参 数 指 定 了 资 源 文 件 和 资 源 类 的 基 本 类 名; 第 二 个 参 数 是 你 所 创 建 的Locale 的 实 例, 指 定 了 当 前 程 序 所 有 资 源 默 认 的 语 言 代 码 和 地 区 代 码。

---- 可 见, 资 源 文 件 名 或 类 名 是 由" 基 本 类 名_ 语 言 代 码_ 地 区 代 码" 组 成 的。Java 将 先 查 找 有 无 此 名 称 的 类, 若 没 有 才 查 找 具 有 此 名 称 的properties 文 件。

---- 匹 配 了 正 确 的 资 源 文 件 名 或 类 名 后, 要 获 取 某 键 对 应 的 值 就 变 得 相 当 容 易。 例 如, 要 创 建 标 识 为" 计 算 器" 的 标 签, 只 要 调 用 以 下 语 句:

label1=new Label(bundle.getString
("label_jisuanqi"), Label.CENTER);

---- getString 方 法 的 参 数 是 资 源 文 件 中 的 键 名。 除 了getString 外,ResourceBundle 类 还 提 供 了 其 它 方 法 获 取 不 同 的 对 象, 如getStringArray、getObject 等( 因 为 在ListResourceBundle 的 实 例 中 允 许 存 在 非 字 符 对 象)。

---- 转 换 非UNICODE 资 源

---- 在Java 内 部 字 符 是 用Unicode 字 符 表 示 的。Unicode 是 一 种16bit 的 编 码, 支 持 大 多 数 地 区 的 语 言。 具 体 标 准 可 到http://www.unicode.org/index.html 查 询。 因 此, 无 论 是 用 文 本 文 件 还 是 用 资 源 类 的 方 式 存 储 资 源, 都 应 该 将 非Unicode 字 符 转 换 为Unicode 字 符。Java 为 我 们 提 供 了 转 换 的 工 具-Native2ascii。 将 含 有GB 编 码 的 汉 字 的WebTaxResource_zh.CN.properties 文 件 转 换 为 只 含Unicode 字 符 的 例 子 如 下:

native2ascii -encoding GB2321 
WebTaxResource_zh_CN.properties 
.\output\WebTaxResource_zh_CN.properties

---- 到 此 为 止, 一 个 支 持i18n 的 程 序 就 已 初 步 完 成 了。

---- 其 他 相 关 问 题

---- 正 如 实 现 目 标 中 所 讲 到, 支 持i18n 的 程 序 不 但 要 识 别 不 同 的 编 码 方 式, 还 要 根 据 不 同 的 语 言 习 惯 动 态 调 整 与 语 言 相 关 的 元 素, 如 数 字、 金 额、 日 期 等 的 显 示。 例 如 在 法 文 中 数 值123456.78 表 示 为123 456,78, 而 在 德 文 中 应 表 示 为123.456,78。 除 了 数 值 和 货 币 之 外, 不 同 语 言 有 不 同 表 示 的 元 素 还 有 日 期、 时 间 和 文 本 消 息。Java 提 供 了NumberFormat、DateFormat、MessageFormat 类 根 据 不 同 的Locale 实 例 动 态 改 变 这 些 元 素 的 显 示 模 式。 下 面 的 例 子 将 根 据 不 同 的Locale 实 例 改 变 数 值123456.78 的 显 示 方 式。

		Double amount = new Double(123456.78);
    NumberFormat numberFormatter;
    String amountOut;

    numberFormatter = NumberFormat.getNumber
    Instance(currentLocale);
    amountOut = numberFormatter.format(amount);
    System.out.println(amountOut + " " + 
     currentLocale.toString());

---- 当 然, 实 现Java 程 序 的i18n 还 有 很 多 问 题 要 考 虑, 如 不 同 语 言 的 语 法 问 题 等。 但 在Java 中, 只 有 你 不 想 做 的, 没 有 你 做 不 到 的。 遇 到 问 题 多 看 看 联 机 文 档 或 其 它 相 关 资 料, 一 般 都 能 得 到 满 意 的 答 案 的。 <

小小豆叮

关于JAVA的可移植性

概 述:Sun 的JAVA 技 术 的 强 大 的 可 移 植 性(portability) 主 要 表 现 在 三 个 各 自 独 立 的 方 面。 本 文 讨 论 了 这 三 种 可 移 植 性 的 特 点 和 它 们 的 不 足。 1. JAVA 作 为 一 种 编 程 语 言: 源 代 码 可 移 植 性 作 为 一 种 编 程 语 言,JAVA 提 供 了 一 种 最 简 单 同 时 也 是 人 们 最 熟 悉 的 可 移 植 性-- 源 代 码 移 植。 这 意 味 着 任 意 一 个JAVA 程 序, 不 论 它 运 行 在 何 种CPU、 操 作 系 统 或JAVA 编 译 器 上, 都 将 产 生 同 样 的 结 果。 这 并 不 是 一 个 新 的 概 念。 人 们 使 用C、C++ 也 可 以 产 生 同 样 的 效 果。 但 是 使 用C 或C++ 编 程 人 们 可 以 有 太 多 的 选 择, 在 许 多 细 节 上 它 都 没 有 严 格 定 义, 如: 未 初 始 化 变 量 的 值、 对 已 释 放 的 内 存 的 存 取、 浮 点 运 算 的 尾 数 值 等 等。 所 以 除 非 你 一 开 始 就 严 格 按 照 系 统 无 关 的 概 念 来 进 行 设 计, 否 则 这 种 可 移 植 性 只 能 是 一 种 理 论 上 的 设 想 而 不 能 形 成 实 践。 总 之, 尽 管C 和C++ 有 严 密 的 语 法 定 义, 它 们 的 语 意(symantics) 定 义 还 不 是 标 准 的。 这 种 语 意 上 的 不 统 一 使 得 同 一 段 程 序 在 不 同 的 系 统 环 境 下 会 产 生 不 同 的 结 果。 有 时 即 使 系 统 情 况 完 全 相 同 而 仅 仅 由 于 编 译 器 的 设 置 不 同 也 会 产 生 令 人 意 想 不 到 的 结 果。 而JAVA 就 不 同 了。 它 定 义 了 严 密 的 语 意 结 构, 而 使 编 译 器 不 承 担 这 方 面 的 工 作。 另 外,JAVA 对 程 序 的 行 为 的 定 义 也 比C 和C++ 严 格, 如: 它 提 供 了 内 存 自 动 回 收 功 能(Garbage Collection), 使 程 序 不 能 访 问 越 界 内 存; 它 对 未 初 始 化 的 变 量 提 供 确 定 值 等 等。 它 的 这 些 特 性 能 够 减 小 在 不 同 平 台 上 运 行 的JAVA 程 序 之 间 的 差 异, 也 使 得JAVA 具 有 即 使 没 有JAVA 虚 拟 机 的 存 在 的 情 况 下 比C 和C++ 更 好 的 平 台 无 关 性。 然 而, 这 些 特 点 也 有 它 不 利 的 一 面。JAVA 设 想 运 行 于 具 有32 位 字 节 长 度 且 每 字 节 为8 位 的 计 算 机 上, 这 就 使 得 那 些8 位 字 长 的 计 算 机 和 一 些 巨 型 机 不 能 有 效 的 运 行JAVA 程 序。 在 这 样 的 平 台 上 就 只 能 运 行 那 些 可 移 植 的C 和C++ 程 序 了。 2. JAVA 作 为 一 个 虚 拟 机:CPU 可 移 植 性 大 多 数 编 译 器 产 生 的 目 标 代 码 只 能 运 行 在 一 种CPU 上( 如Intel 的x86 系 列), 即 使 那 些 能 支 持 多 种CPU 的 编 译 器 也 不 能 同 时 产 生 适 合 多 种CPU 的 目 标 代 码。 如 果 你 需 要 在 三 种CPU( 如x86、SPARC 和MIPS) 上 运 行 同 一 程 序, 就 必 须 编 译 三 次。 但JAVA 编 译 器 就 不 同 了。JAVA 编 译 器 产 生 的 目 标 代 码(J-Code) 是 针 对 一 种 并 不 存 在 的CPU--JAVA 虚 拟 机(JAVA Virtual Machine), 而 不 是 某 一 实 际 的CPU。JAVA 虚 拟 机 能 掩 盖 不 同CPU 之 间 的 差 别, 使J-Code 能 运 行 于 任 何 具 有JAVA 虚 拟 机 的 机 器 上。 虚 拟 机 的 概 念 并 不 是JAVA 所 特 有 的: 加 州 大 学 几 年 前 就 提 出 了PASCAL 虚 拟 机 的 概 念; 广 泛 用 于Unix 服 务 器 的Perl 脚 本 也 是 产 生 与 机 器 无 关 的 中 间 代 码 用 于 执 行。 但 针 对 Internet 应 用 而 设 计 的JAVA 虚 拟 机 的 特 别 之 处 在 于 它 能 产 生 安 全 的 不 受 病 毒 威 胁 的 目 标 代 码。 正 是 由 于Internet 对 安 全 特 性 的 特 别 要 求 才 使 得JVM 能 够 迅 速 被 人 们 接 受。 当 今 主 流 的 操 作 系 统 如OS/2、MacOS、Windows95/NT 都 已 经 或 很 快 提 供 对J-Code 的 支 持。 作 为 一 种 虚 拟 的CPU,JAVA 虚 拟 机 对 于 源 代 码(Source Code) 来 说 是 独 立 的。 我 们 不 仅 可 以 用JAVA 语 言 来 生 成J-Code, 也 可 以 用Ada95 来 生 成。 事 实 上, 已 经 有 了 针 对 若 干 种 源 代 码 的J-Code 编 译 器, 包 括Basic、Lisp 和Forth。 源 代 码 一 经 转 换 成J-Code 以 后, JAVA 虚 拟 机 就 能 够 执 行 而 不 区 分 它 是 由 哪 种 源 代 码 生 成 的。 这 样 做 的 结 果 就 是CPU 可 移 植 性。 将 源 程 序 编 译 为J-Code 的 好 处 在 于 可 运 行 于 各 种 机 器 上, 而 缺 点 是 它 不 如 本 机 代 码 运 行 的 速 度 快。 3. JAVA 作 为 一 种 虚 拟 的 操 作 系 统(OS) 和 图 形 用 户 界 面(GUI): 操 作 系 统 可 移 植 性 即 使 经 过 重 新 编 译, 大 多 数 的 用C 和C++ 编 写 的Windows 程 序 也 不 能 在Unix 或Macintosh 系 统 上 运 行。 这 是 为 什 么 呢 ? 因 为 程 序 员 在 编 写Windows 程 序 时 使 用 了 大 量 的WindowsAPI 和 中 断 调 用, 而Windows 程 序 对 系 统 功 能 的 调 用 与Unix 和Macintosh 程 序 有 很 大 的 差 别, 所 以 除 非 将 全 套Windows API 移 植 到 其 它 操 作 系 统 上, 否 则 重 编 译 的 程 序 仍 不 能 运 行。 JAVA 采 用 了 提 供 一 套 与 平 台 无 关 的 库 函 数( 包 括AWT、UTIL、LANG 等 等) 的 方 法 来 解 决 这 个 问 题。 就 象JVM 提 供 了 一 个 虚 拟 的CPU 一 样,JAVA 库 函 数 提 供 了 一 个 虚 拟 的GUI 环 境。JAVA 程 序 仅 对JAVA 库 函 数 提 出 调 用, 而 库 函 数 对 操 作 系 统 功 能 的 调 用 由 各 不 同 的 虚 拟 机 来 完 成。JAVA 也 在 它 的OS/GUI 库 中 使 用 了 一 种“ 罕 见 名 称 符”(least-commom-denominator) 来 提 供 对 某 种 特 定 操 作 系 统 的 功 能 调 用, 即 此 功 能 只 在 特 定 环 境 下 生 效 而 在 其 它 操 作 系 统 下 则 被 忽 略。 这 样 做 的 好 处 在 于 可 以 针 对 某 操 作 系 统 生 成 拥 有 人 们 熟 悉 的 界 面 的 应 用 程 序 而 同 时 此 程 序 又 能 在 其 它 系 统 下 运 行。 缺 点 则 是 系 统 中 的 某 些 功 能 调 用 有 很 强 的 依 赖 性 因 而 在JAVA 的 虚 拟OS/API 中 难 以 实 现。 遇 到 这 种 情 况, 程 序 员 就 只 能 写 不 可 移 植 的 程 序 了。 总 之,JAVA 在 可 移 植 性 方 面 的 特 点 使 它 在Internet 上 具 有 广 泛 的 应 用 前 景。 同 时 它 本 身 具 有 的 防 病 毒 的 能 力 也 使 它 在 需 要 高 可 靠 性 的 应 用 中 占 有 一 席 之 地。 作 者: 范 仲 方 天 津 大 学 电 子 工 程 系 地 址: 天 津 市 常 德 道24 号 邮 编:300050 <

小小豆叮

共享内存在Java中的实现和应用

郭洪锋 (ghf_emai@china.com)
2001 年 9 月

1 共享内存对应应用开发的意义
对熟知UNIX系统应用开发的程序员来说,IPC(InterProcess Communication)机制是 非常熟悉的,IPC基本包括共享内存、信号灯操作、消息队列、信号处理等部分,是开发应 用中非常重要的必不可少的工具。其中共享内存IPC机制的关键,对于数据共享、系统快 速查询、动态配置、减少资源耗费等均有独到的优点。

对应UNIX系统来说,共享内存分为一般共享内存和映像文件共享内存两种,而对应 Windows,实际上只有映像文件共享内存一种。所以java应用中也是只能创建映像文件共享 内存。

在java语言中,基本上没有提及共享内存这个概念,但是,在某一些应用中,共享内 存确实非常有用,例如采用java语言的分布式应用系统中,存在着大量的分布式共享对象, 很多时候需要查询这些对象的状态,以查看系统是否运行正常或者了解这些对象的目前的一 些统计数据和状态。如果采用网络通信的方式,显然会增加应用的额外负担,也增加了一些 不必要的应用编程。而如果采用共享内存的方式,则可以直接通过共享内存查看对象的状态 数据和统计数据,从而减少了一些不必要的麻烦。

共享内存的使用有如下几个特点:

  1. 可以被多个进程打开访问;
  2. 读写操作的进程在执行读写操作时其他进程不能进行写操作;
  3. 多个进程可以交替对某一共享内存执行写操作;
  4. 一个进程执行了内存的写操作后,不影响其他进程对该内存的访问。同时其他进程对更新后的内存具有可见性。
  5. 在进程执行写操作时如果异常退出,对其他进程写操作禁止应自动解除。
  6. 相对共享文件,数据访问的方便性和效率有

另外,共享内存的使用上有如下情况:

  1. 独占的写操作,相应有独占的写操作等待队列。独占的写操作本身不会发生数据的一致性问题。
  2. 共享的写操作,相应有共享的写操作等待队列。共享的写操作则要注意防止发生数据的一致性问题。
  3. 独占的读操作,相应有共享的读操作等待队列;
  4. 共享的读操作,相应有共享的读操作等待队列。

一般情况下,我们只是关心第一二种情况。

2 共享内存在java中的实现
在jdk1.4中提供的类MappedByteBuffer为我们实现共享内存提供了较好的方法。该缓 冲区实际上是一个磁盘文件的内存映像。二者的变化将保持同步,即内存数据发生变化会立 刻反映到磁盘文件中,这样会有效的保证共享内存的实现。

将共享内存和磁盘文件建立联系的是文件通道类:FileChannel。该类的加入是JDK为 了统一对外部设备(文件、网络接口等)的访问方法,并且加强了多线程对同一文件进行存 取的安全性。例如读写操作统一成read和write。这里只是用它来建立共享内存用,它建立 了共享内存和磁盘文件之间的一个通道。

打开一个文件建立一个文件通道可以用RandomAccessFile类中的方法getChannel。该 方法将直接返回一个文件通道。该文件通道由于对应的文件设为随机存取文件,一方面可以 进行读写两种操作,另一方面使用它不会破坏映像文件的内容(如果用FileOutputStream直 接打开一个映像文件会将该文件的大小置为0,当然数据会全部丢失)。这里,如果用 FileOutputStream和FileInputStream则不能理想的实现共享内存的要求,因为这两个类同时 实现自由的读写操作要困难得多。

下面的代码实现了如上功能,它的作用类似UNIX系统中的mmap函数。

// 获得一个只读的随机存取文件对象
RandomAccessFile RAFile = new RandomAccessFile(filename,"r");

// 获得相应的文件通道
FileChannel fc = RAFile.getChannel();

// 取得文件的实际大小,以便映像到共享内存
int size = (int)fc.size();

// 获得共享内存缓冲区,该共享内存只读
MappedByteBuffer mapBuf = fc.map(FileChannel.MAP_RO,0,size);

// 获得一个可读写的随机存取文件对象
RAFile = new RandomAccessFile(filename,"rw");

// 获得相应的文件通道
fc = RAFile.getChannel();

// 取得文件的实际大小,以便映像到共享内存
size = (int)fc.size();

// 获得共享内存缓冲区,该共享内存可读写
mapBuf = fc.map(FileChannel.MAP_RW,0,size);

// 获取头部消息:存取权限
mode = mapBuf.getInt();

如果多个应用映像同一文件名的共享内存,则意味着这多个应用共享了同一内存数据。 这些应用对于文件可以具有同等存取权限,一个应用对数据的刷新会更新到多个应用中。

为了防止多个应用同时对共享内存进行写操作,可以在该共享内存的头部信息加入写操 作标志。该共享内存的头部基本信息至少有:


	int Length; // 共享内存的长度。
	int	mode;	// 该共享内存目前的存取模式。

共享内存的头部信息是类的私有信息,在多个应用可以对同一共享内存执行写操作时, 开始执行写操作和结束写操作时,需调用如下方法:


	public boolean StartWrite()
	{
		if(mode == 0) { //  标志为0,则表示可写
			mode = 1; // 置标志为1,意味着别的应用不可写该共享内存
			mapBuf.flip(); 
			mapBuf.putInt(mode); // 写如共享内存的头部信息
			return true;
		}
		else {
			return  false; // 指明已经有应用在写该共享内存,本应用不可写该共享内存
		}
	}
	
	public boolean StopWrite()
	{
		mode = 0; // 释放写权限
		mapBuf.flip(); 
		mapBuf.putInt(mode); // 写入共享内存头部信息
		return true;
	}
这里提供的类文件mmap.java封装了共享内存的基本接口,读者可以用该类扩展成自己 需要的功能全面的类。

如果执行写操作的应用异常中止,那么映像文件的共享内存将不再能执行写操作。为了 在应用异常中止后,写操作禁止标志自动消除,必须让运行的应用获知退出的应用。在多线 程应用中,可以用同步方法获得这样的效果,但是在多进程中,同步是不起作用的。方法可 以采用的多种技巧,这里只是描述一可能的实现:采用文件锁的方式。写共享内存应用在获 得对一个共享内存写权限的时候,除了判断头部信息的写权限标志外,还要判断一个临时的 锁文件是否可以得到,如果可以得到,则即使头部信息的写权限标志为1(上述),也可以 启动写权限,其实这已经表明写权限获得的应用已经异常退出,这段代码如下:


// 打开一个临时的文件,注意同一共享内存,该文件名要相同,可以在共享文件名后加后缀“.lock”。
RandomAccessFile fis = new RandomAccessFile("shm.lock","rw");
// 获得文件通道
FileChannel lockfc = fis.getChannel();
// 获得文件的独占锁,该方法不产生堵塞,立刻返回
FileLock flock = lockfc.tryLock();
// 如果为空,则表明已经有应用占有该锁
if(flock == null) {
 ...// 不能执行写操作
}
else {
...// 可以执行写操作
}
该锁会在应用异常退出后自动释放,这正是该处所需要的方法。

3 共享内存在java中的应用
共享内存在java应用中,经常有如下两种种应用:

  1. 永久对象配置。

    在java服务器应用中,用户可能会在运行过程中配置一些参数,而这些参数需要永久 有效,当服务器应用重新启动后,这些配置参数仍然可以对应用起作用。这就可以用到该文 中的共享内存。该共享内存中保存了服务器的运行参数和一些对象运行特性。可以在应用启 动时读入以启用以前配置的参数。

  2. 查询共享数据。

    一个应用(例 sys.java)是系统的服务进程,其系统的运行状态记录在共享内存中,其 中运行状态可能是不断变化的。为了随时了解系统的运行状态,启动另一个应用(例 mon.java),该应用查询该共享内存,汇报系统的运行状态。

可见,共享内存在java应用中还是很有用的,只要组织好共享内存的数据结构,共享内存就可以在应用开发中发挥很不错的作用。

<

小小豆叮

DOM文档操作和XML文件互相转换的java实现

简介:该文简要描述了DOM的概念和内部逻辑结构,给出了DOM文档操作和XML文件互相转换的java实现过程。

1. DOM简介
目前,W3C已于2000年11月13日推出了规范DOM level 2。文档对象模型(DOM)是HTML和XML文档的编程接口规范,它与平台和语言是无关的,因而可以用各种语言在各种平台上实现。该模型定义了THML和XML文件在内存中的逻辑结构(即为文档),提供了访问、存取THML和XML文件的方法。利用DOM规范,可以实现DOM 文档和XML之间的相互转换,遍历、操作相应DOM文档的内容。可以说,要自由的操纵XML文件,就要用到DOM规范。

2. DOM内部逻辑结构
DOM文档中的逻辑结构可以用节点树的形式进行表述。通过对XML文件的解析处理,XML文件中的元素便转化为DOM文档中的节点对象。DOM的文档节点有Document、Element、Comment、Type等等节点类型,其中每一个DOM文档必须有一个Document节点,并且为节点树的根节点。它可以有子节点,或者叶子节点如Text节点、Comment节点等。任何的格式良好的XML文件中的每一个元素均有DOM文档中的一个节点类型与之对应。利用DOM接口将XML文件转化成DOM文档后,我们就可以自由的处理XML文件了。

3. java中的DOM接口
DOM规范提供的API的规范,目前Sun公司推出的jdk1.4测试版中的java API遵循了 DOM level 2 Core推荐接口的语义说明,提供了相应的java语言的实现。

在org.xml.dom中,jkd1.4提供了Document、DocumentType、Node、NodeList、Element、Text等接口,这些接口均是访问DOM文档所必须的。我们可以利用这些接口创建、遍历、修改DOM文档。

在javax.xml.parsers中,jkd1.4提供的DoumentBuilder和DocumentBuilderFactory组合可以对XML文件进行解析,转换成DOM文档。

在javax.xml.transform.dom和javax.xml.transform.stream中,jdk1.4提供了DOMSource类和StreamSource类,可以用来将更新后的DOM文档写入生成的XML文件中。

4. 例程

4.1 将XML文件转化成DOM文档
这个过程是获得一个XML文件解析器,解析XML文件转化成DOM文档的过程。

Jdk1.4中,Document接口描述了对应于整个XML文件的文档树,提供了对文档数据的访问,是该步骤的目标。Document接口可以从类DocumentBuilder中获取,该类包含了从XML文档获得DOM文档实例的API。XML的解析器可以从类DocumentBuilderFactory中获取。在jdk1.4中,XML文件转化成DOM文档可以有如下代码实现:


//获得一个XML文件的解析器
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//解析XML文件生成DOM文档的接口类,以便访问DOM。
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse( new File(FileName) );

4.2 遍历DOM文档
获得接口类document实例后,可以对DOM的文档树进行访问。要遍历DOM文档,首先要获得Root元素。然后获得Root元素的子节点列表。这里通过递归的方法实现遍历的目的。


//获得Root元素
Element element = document.getDocumentElement();
//获得Root元素的子节点列表
nodelist = element.getChildNodes();
//用递归方法实现DOM文档的遍历
GetElement(nodelist);
其中GetElement方法实现如下:
	public void GetElement(NodeList nodelist){
	Node		cnode;
	int		i,len;
	String	str;
	
	if(nodelist.getLength() == 0){
		// 该节点没有子节点
		return;
	}
	for(i=0;i 1)
				System.out.println("      "+str+" "+len);
		}
	}
	}
注意:上面的代码只是显示Node类型和Text类型的对象。它们的类型标识分别是1和3。

4.3 修改DOM文档
修改DOM文档的API在DOM level 2 Core规范中做了说明,jkd1.4中的org.xml.dom中实现了这些API。修改DOM文档操作主要集中在Document、Element、Node、Text等类中,这里给出的例子中是在解析出的DOM文档中增加一系列对象,对应与在XML文件中增加一条记录。


// 获得Root对象
	Element root = document.getDocumentElement();
// 在DOM文档中增加一个Element节点
	Element booktype = document.createElement("COMPUTES");
//将该节点转化成root对象的子节点
	root.appendChild(cdrom);
//在DOM文档中增加一个Element节点
	Element booktitle = document.createElement("Title");
//将该节点转化成booktype对象的子节点
	booktype.appendChild(booktitle);
//在DOM文档中增加一个Text节点
	Text bookname = document.createTextNode("understand Corba");
//将该节点转化成bookname对象的子节点
booktitle.appendChild(bookname);

4.4 将DOM文档转化成XML文件


// 获得将DOM文档转化为XML文件的转换器,在jdk1.4中,有类TransformerFactory
// 来实现,类Transformer实现转化API。
			TransformerFactory tfactory = TransformerFactory.newInstance();
			Transformer transformer = tfactory.newTransformer();
// 将DOM对象转化为DOMSource类对象,该对象表现为转化成别的表达形式的信息容器。
			DOMSource source = new DOMSource(document);
// 获得一个StreamResult类对象,该对象是DOM文档转化成的其他形式的文档的容器,可以是XML文件,文本文件,HTML文件。这里为一个XML文件。
			StreamResult result = new StreamResult(new File(“text.xml”));
// 调用API,将DOM文档转化成XML文件。
			transformer.transform(source,result);

这里提供了该例程的完整程序,该例程在windows 2000中jdk1.4环境中运行通过。

以上给出了一个例子,读者可以从中了解到对DOM操作的思路。因为对DOM的操作均遵循了DOM规范,所以也适用于其它语言对DOM的处理。

参考资料:

  1. http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-2000-1113
  2. Java 2 Platform, Standard Edition, V1.4.0 API Specificaion
<

小小豆叮

JSP白皮书 之三

采用企业级JavaBean技术的可扩展处理

JSP页面也可以作为企业级JavaBean(EJB)体系结构中的一个中间层 。在这种情况下,JSP页面和后端资源通过EJB组件进行交互。

EJB组件管理对后端资源的访问,从而为众多的并发使用者提供可扩展的性能。对于电子商务或者其他应用,EJB管理交易和潜在的安全性。这将简化JSP页面。这个模型将为Java 2企业版(J2EE)平台所支持。

JSP页面与XML技术的集成

JSP页面可以用于生成XML和HTML页面。

对于简单的XML生成,开发人员可以包含XML标识和JSP页面的静态模板部分。对于动态XML生成,使用基于服务器的对象和生成XML输出的客户化标识。

JSP页面与XML工具不是不兼容的。尽管Sun在设计JSP规范时使得JSP页面即使手工操作对于创作者而言也是很容易的,JSP规范同时也提供了一个机制以便于创建任意JSP页面的一个XML版本。通过这种方法,XML工具能够创作和操作JSP页面。

通过将JSP标识和元件转换为与XML兼容的对等物,可以使用基于XML的工具来操作JSP页面。例如,脚本可以被包含在<%和%>之中,或者基于XML标志的中。事实上,经过下面这样简单的几步将JSP页面转换为XML页面是可能的,这些步骤包括:

增加一个JSP根元件


将元件和指示转换为XML兼容的对等物
为页面上其他的元件(通常非JSP)创建CDATA元件
通过这个与XML兼容的方法,创建HTML页面的设计者仍然拥有一个快速创建动态Web页面的易用环境,同时,基于XML的工具和服务可以与JSP页面集成并且和JSP兼容的服务器一起工作。


JSP技术的未来

JSP技术被设计为一个开放的,可扩展的建立动态Web页面的标准。开发人员可以使用JSP页面来创建可移植的Web应用,在不同的Web和应用服务器上为不同的场合所运行,而不论采用什么适合本身场合和需要的创建工具。

通过与业界领袖的合作,Sun保证JSP规范是开放的和可移植的。可以使用任何客户机和服务器平台,在任何地方编写和部署它们。将来,工具供应商和其他厂商将通过为专门的功能提供客户化的标识库而扩展平台的功能。

JSP规范的1.0版本是通向动态Web页面生成的一个开放的工业标准方法的第一步。1.0版本通过一个核心标识集、隐含对象以及开始创建动态Web页面所需的基本功能构成了该方法的基础。已经有几个Web服务器,应用服务器和开发工具供应商正在为他们的产品添加JSP1.0的支持,这样在业界已经拥有了最初的、立即的支持。

在1999晚些时候将完成的1.1版本,通过更多的XML支持、客户化标识、以及与J2EE的集成而扩展这个版本。并且供应商们可能会有选择地扩展和扩充在JSP规范中基本的、必需的功能。JSP引擎能够强有力地支持多种脚本语言和对象模型。在业界扩充和使用JSP技术能力的同时,也向Sun承诺将保证JSP技术保持平台和服务器间固有的可移植性。

上一页 <

小小豆叮

JDocHelper源代码

<

小小豆叮

JSP白皮书 之一

JavaServer PagesTM (JSP)技术为创建显示动态生成内容的Web页面提供了一个简捷而快速的方法 。JSP技术的设计目的是使得构造基于Web的应用程序更加容易和快捷,而这些应用程序能够与各种Web服务器,应用服务器,浏览器和开发工具共同工作。

本白皮书提供了JSP技术的全面概述,描述了其开发背景以及这项技术的总体目标。同时,在一个简单示例中,还描述了一个基于JavaTM技术的页面的关键组成部分。

开发基于Web的应用程序:背景

在万维网短暂的历史中,它已经从一个大部分显示静态信息的网络演化到对股票进行交易和进行购书操作的一个基础设施。在各种各样的应用程序中,对于可能使用的基于Web的客户端,看上去没有任何限制。

基于浏览器客户端的应用程序比传统的基于客户机/服务器的应用程序有几个好处。这些好处包括几乎没有限制的客户端访问和极其简化的应用程序部署和管理(要更新一个应用程序,管理人员只需要更改一个基于服务器的程序,而不是成千上万的安装在客户端的应用程序)。这样,软件工业正迅速地向建造基于浏览器客户端的多层次应用程序迈进。

这些快速增长的精巧的基于Web的应用程序要求开发技术上的改进。静态HTML对于显示相对静态的内容是不错的选择;新的挑战在于创建交互的基于Web的应用程序,在这些程序中,页面的内容是基于用户的请求或者系统的状态,而不是预先定义的文字。

对于这个问题的一个早期解决方案是使用CGI-BIN接口;开发人员编写与接口相关的单独的程序,以及基于Web的应用程序,后者通过Web服务器来调用前者。这个方案有着严重的扩展性问题——每个新的CGI要求在服务器上新增一个进程。如果多个用户并发地访问该程序,这些进程将消耗该Web服务器所有的可用资源,并且系统性能降低到极其低下的地步。

某些Web服务器供应商已经尝试通过为他们的服务器提供“插件”和API来简化Web应用程序的开发。这些解决方案是与特定的Web服务器相关的,不能解决跨多个供应商的解决方案的问题。例如,微软的Active Server PagesTM(ASP) 技术使得在Web页面上创建动态内容更加容易,但是也只能工作在微软的IIS和Personal Web Server上。

还存在其他的解决方案,但是都不能使一个普通的页面设计者能够轻易地掌握。例如,象Java Servlets这样的技术就可以使得用Java语言编写交互的应用程序的服务器端的代码变得容易。一个Java Servlets就是一个基于Java技术的运行在服务器端的程序(与Applet不同,后者运行在浏览器端)。开发人员能够编写出这样的Servlet,以接收来自Web浏览器的HTTP请求,动态地生成响应(可能要查询数据库来完成这项请求),然后发送包含HTML或XML文档的响应到浏览器。

采用这种方法,整个网页必须都在Java Servlet中制作。如果开发人员或者Web管理人员想要调整页面显示,就不得不编辑并重新编译该Java Servlet,即使在逻辑上已经能够运行了。采用这种方法,生成带有动态内容的页面仍然需要应用程序的开发技巧。

很显然,目前所需要的是一个业界范围内的创建动态内容页面的解决方案。这个方案将解决当前方案所受到的限制,即:

能够在任何Web或应用程序服务器上运行
将应用程序逻辑和页面显示分离
能够快速地开发和测试
简化开发基于Web的交互式应用程序的过程
JavaServer Pages (JSP)技术就是被设计用来满足这样的要求的。JSP规范是Web服务器、应用服务器、交易系统、以及开发工具供应商间广泛合作的结果。太阳微系统公司(Sun Microsystems Inc.)开发出这个规范来整合和平衡已经存在的对Java编程环境(例如,Java Servlet和JavaBeansTM)进行支持的技术和工具。其结果是产生了一种新的、开发基于Web应用程序的方法,给予使用基于组件应用逻辑的页面设计者以强大的功能。

下一页 <

小小豆叮

JSP白皮书 之二


Web应用开发的JavaServer Pages技术方法


在开发JSP规范的过程中,太阳微系统公司(Sun Microsystems Inc.)与许许多多主要的Web服务器、应用服务器和开发工具供应商,以及各种各样富有经验的开发团体进行合作 。其结果是找到了一种为应用和页面开发人员平衡了可移植性和易用性的开发方法。

JSP技术在多个方面加速了动态Web页面的开发:

将内容的生成和显示进行分离


使用JSP技术,Web页面开发人员可以使用HTML或者XML标识来设计和格式化最终页面。使用JSP标识或者小脚本来生成页面上的动态内容(内容是根据请求来变化的,例如请求帐户信息或者特定的一瓶酒的价格)。生成内容的逻辑被封装在标识和JavaBeans组件中,并且捆绑在小脚本中,所有的脚本在服务器端运行。如果核心逻辑被封装在标识和Beans中,那么其他人,如Web管理人员和页面设计者,能够编辑和使用JSP页面,而不影响内容的生成。
在服务器端,JSP引擎解释JSP标识和小脚本,生成所请求的内容(例如,通过访问JavaBeans组件,使用JDBCTM技术访问数据库,或者包含文件),并且将结果以HTML(或者XML)页面的形式发送回浏览器。这有助于作者保护自己的代码,而又保证任何基于HTML的Web浏览器的完全可用性。

强调可重用的组件
 

绝大多数JSP页面依赖于可重用的,跨平台的组件(JavaBeans或者Enterprise JavaBeansTM组件)来执行应用程序所要求的更为复杂的处理。开发人员能够共享和交换执行普通操作的组件,或者使得这些组件为更多的使用者或者客户团体所使用。基于组件的方法加速了总体开发过程,并且使得各种组织在他们现有的技能和优化结果的开发努力中得到平衡。


采用标识简化页面开发


Web页面开发人员不会都是熟悉脚本语言的编程人员。JavaServer Page技术封装了许多功能,这些功能是在易用的、与JSP相关的XML标识中进行动态内容生成所需要的。标准的JSP标识能够访问和实例化JavaBeans组件,设置或者检索组件属性,下载Applet,以及执行用其他方法更难于编码和耗时的功能。
通过开发定制化标识库,JSP技术是可以扩展的。今后,第三方开发人员和其他人员可以为常用功能创建自己的标识库。这使得Web页面开发人员能够使用熟悉的工具和如同标识一样的执行特定功能的构件来工作。

JSP技术很容易整合到多种应用体系结构中,以利用现存的工具和技巧,并且扩展到能够支持企业级的分布式应用。作为采用Java技术家族的一部分,以及Java 2(企业版体系结构)的一个组成部分,JSP技术能够支持高度复杂的基于Web的应用。
由于JSP页面的内置脚本语言是基于Java编程语言的,而且所有的JSP页面都被编译成为Java Servlet,JSP页面就具有Java技术的所有好处,包括健壮的存储管理和安全性。
作为Java平台的一部分,JSP拥有Java编程语言“一次编写,各处运行”的特点。随着越来越多的供应商将JSP支持添加到他们的产品中,您可以使用自己所选择的服务器和工具,更改工具或服务器并不影响当前的应用。
当与Java 2平台,企业版(J2EE)和Enterprise JavaBean技术整合时,JSP页面将提供企业级的扩展性和性能,这对于在虚拟企业中部署基于Web的应用是必需的。


JSP页面看上去象什么?
JSP页面看上去象标准的HTML和XML页面,并附带有JSP引擎能够处理和抽取的额外元件。通常,JSP元件创建插入最终页面的文本。

使用示例是描述JSP技术的最好方法。下面的JSP页面非常简单;它打印带年、当月的天,并且根据时间使用"Good Morning"和"Good Afternoon"对您表示欢迎。

该页面结合了普通的HTML和大量JSP元件组成。

对时钟JavaBeans组件的调用
对一个外部文件的包含(用于著作权信息)
JSP表达式和脚本

<%@ page language=="java" imports=="com.wombat.JSP.*" %>

Welcome



Today is




  • Day: <%==clock.getDayOfMonth() %>
  • Year: <%==clock.getYear() %>


<% if (Calendar.getInstance().get(Calendar.AM_PM) ==== Calendar.AM) { %>
Good Morning
<% } else { %>
Good Afternoon
<% } %>
<%@ include file=="copyright.html" %>



这个页面包含下面这些组件:

一个JSP指示将信息传送到JSP引擎。在这个示例中,第一行指出从该页面即将访问的一些Java编程语言的扩展的位置。指示被设置在<%@和%>标记中。
固定模板数据:所有JSP引擎不能识别的标识将随结果页面发送。通常,这些标识是HTML或者XML标识。在上面的例子中包括无序列表(UL)和H1标识。
JSP动作或者标识:这些通常作为标准或定制标识被实现,并且具有XML标识的语法。在这个例子中,jsp:useBean标识实例化服务器端的Clock JavaBean。
一个表达式:JSP引擎计算在<%==和%>标记间的所有东西。在上面的列表项中,时钟组件(Clock)的Day和Year属性值作为字符串返回,并且作为输出插入到JSP文件中。在上面的例子中,第一个列表项是日子,第二个是年份。
小脚本是执行不为标识所支持的功能或者将所有的东西捆绑在一起的小的脚本。JSP 1.0软件的内置脚本语言是基于Java语言的。在上面示例中的小脚本确定现在是上午还是下午,并且据此来欢迎用户。
这个例子可能小了一点,但是技术上却不是。从业务上可以将关键的处理封装在服务器端的组件中,并且Web开发人员能够使用熟悉的语法和工具很容易地访问这些信息。基于Java的小脚本提供了一种灵活的方式以执行其他功能,而不要求扩展的脚本语言。页面作为整体是可读和可理解的,这就使得查找或者预防问题以及
共享工作更加容易。
这些组件中的一部分在下面有更详细的描述。

JSP指示

JSP页面使用JSP指示将指令传递到JSP引擎。这其中包括:

JSP页面指示传递页面相关的信息,例如缓冲区和线程信息或者出错处理。
语言指示指定脚本语言,以及所有的扩展。
包含指示(在上面例子中有显示)可以被用来在页面中包含一个外部的文档。一个好的例子是著作权文档或者公司信息文档——在一个集中地点保存该文档并且在页面中包含比在各个JSP页面中更新要容易些。当然,被包含的文件也可能是另一个JSP文件。
标识库指示指出页面可以调用的一个客户标识库。

JSP标识

绝大多数JSP处理将通过与JSP相关的基于XML的标识完成。JSP 1.0中包含大量标准标识,这些标识作为核心标识包括:

jsp:useBean 这个标识声明对一个JavaBeans组件实例的使用。如果该组件的实例不存在,JavaBeans组件将实例化和注册这个标识。
jsp:setProperty 这个标识在组件的实例中设置一个属性的值。

jsp:getProperty 这个标识获取一个组件的实例的属性值,将其转化为字符串,并且将它放入隐含对象"out"中。

jsp:include

jsp:forward


1.1版本将包含更多的标准标识。

标识的好处在于它们易于在应用程序间使用和共享。基于标识的语法的真正威力来自于客户标识库的开发,使得工具供应商或其他人员能够为特定的要求创建和分派标识。

脚本元件

JSP页面可以在页面中包含小的脚本,称之为小脚本(scriptlets)。小脚本是一个代码片段,在请求的处理过程中被执行。小脚本可以和页面中的静态元件组合(正如上面的例子一样)起来创建动态生成的页面。

脚本在<%和%>标志中被描述。在这对标志中的所有东西都会被脚本描述语言引擎执行,在我们的例子中是主机上的Java虚拟机。

JSP规范支持所有常用的脚本元件,包括表达式和声明。

JSP页面的应用模型

JSP页面由JSP引擎执行,引擎安装在Web服务器或者使用JSP的应用服务器上。JSP引擎接受客户端对JSP页面的请求,并且生成JSP页面给客户端的响应。

JSP页面通常被编译成为Java Servlet。后者是一个标准的Java扩展,在www.java.sun.com站点有更详细的描述。页面开发人员能够访问全部的Java应用环境,以利用Java技术的扩展性和可移植性。

当JSP页面第一次被调用时,如果它还不存在,就会被编译成为一个Java Servlet类,并且存储在服务器的内存中。这使得在接下来的对该页面的调用有非常快的响应。(这避免了CGI-BIN为每个HTTP请求生成一个新的进程的问题,或是服务器端引用所引起的运行时语法分析。)

JSP页面可以包含在多种不同的应用体系结构或者模型中。JSP页面可以用于由不同协议、组件和格式所组成的联合体中。下面的小节描述了一些可能发生的情况。

一个简单应用

在一个简单实现中,浏览器直接调用JSP页面,JSP页面自己生成被请求的内容(可能会调用JDBC直接从数据库中获取信息)。JSP页面能够调用JDBC或者Java BlendTM组件来生成结果,并且创建标准的HTML,作为结果发送回浏览器。



这个模型基本上用JSP页面(编译成为Java Servlet)代替了CGI-BIN概念。这个方法拥有下列优点:

简单而快速地编程
页面作者可以很容易地根据请求和资源状态生成动态内容
这个结构在许多应用上工作良好,但不能扩展到大量的基于Web的并发客户访问稀少的企业资源,因为每个客户必须建立或者共享一个到可用内容资源的连接。例如,如果JSP页面访问数据库,可能生成许多到数据库的连接,这将影响到数据库的性能。


使用Java Servlet的一个灵活的应用

在另一种可能的配置中,基于Web的客户机可能直接对Java Servlet进行请求,Servlet生成动态内容,将结果捆绑到一个结果对象中并且调用JSP页面。JSP页面从该对象中访问动态内容,并且将结果(例如HTML)发送回浏览器。

这个方法创建了更多的可以为应用程序间共享的可重用的组件,并且可以作为更大的应用的一部分完成。但是,在处理如数据库一样的企业资源的连接时,还是存在扩展性问题。

上一页 下一页 <

小小豆叮

一个Scocket实例程序

//EchoServer1.java import java.io.*; import java.net.*; public class EchoServer1 { public static void main(String[] args ) { try { ServerSocket s = new ServerSocket(8500); Socket incoming = s.accept( ); BufferedReader in = new BufferedReader (new InputStreamReader(incoming.getInputStream())); PrintWriter out = new PrintWriter (incoming.getOutputStream(), true /* autoFlush */); out.println( "Hello! Enter BYE to exit." ); boolean done = false; while (!done) { String line = in.readLine(); if (line == null) done = true; else { out.println("Echo: " + line); if (line.trim().equals("BYE")) done = true; } } incoming.close(); } catch (Exception e) { System.out.println(e); } } } // EchoClient1.java import java.io.*; import java.net.*; public class EchoClient1 { public static void main(String args[]) { try{ if (args.length != 1){ System.out.println("USAGE: java Client servername"); return; } String connectto= args[0]; Socket connection; // connect to server if(connectto.equals("localhost")){ connection=new Socket(InetAddress.getLocalHost(),8500); } else{ connection=new Socket(InetAddress.getByName(connectto),8500); } BufferedReader input=new BufferedReader(new InputStreamReader(connection.getInputStream())); PrintWriter out = new PrintWriter(connection.getOutputStream(), true /* autoFlush */); // read information from server String info; info = input.readLine(); System.out.println(info); boolean done = false; BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String sInput; while(!done){ sInput = in.readLine(); out.println(sInput); if (sInput.equalsIgnoreCase("bye")) done = true; info = input.readLine(); System.out.println(info); } connection.close(); } catch(SecurityException e){ System.out.println("SecurityException when connecting Server!"); } catch(IOException e){ System.out.println("IOException when connecting Server!"); } } } 运行 1 运行服务器 java EchoServer1 2 运行 客户端 java EchoClient1 server_hostname   <

小小豆叮

如何让 JAVA 程序在 NT 的服务中运行

You can use the utility SRVANY.EXE, included with the NT resource Kit. Installation of a service copy SRVANY.EXE and install it as a NT services, like: INSTSRV MyServi ce c:\tools\srvany.exe configure via the Services applet("Startup..." dialog) of the Control Panel as manual or automatic set via via the Services applet the account for the service. If you ne ed access to the screen & keyboard, you must choose the LocalSystem ac count and click the "Allow Service to Interact with Desktop". run REGEDIT and create the following PARAMETERS key : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Servics\MyService Application= AppParameters= (optional) AppDirectory= AppParameters= (optional) AppDirectory=

小小豆叮

在JSP编译的时候,服务器内部做了什么?

在JSP第一次获得请求时,不管请求来自于客户端浏览器还是服务器上的servlet, JSP文件将被JSP引擎(JSP engine)转换成为一个servlet 。而这个引擎本身也是一个servlet,在JSWDK,它就是 JspServlet。

在编译时候如果发现jsp文件有任何语法错误,转换过程将中断,并向客户端发出出错信息;而如果编译成功了,则所转换产生的servlet代码被编译,然后该servlet被JSP引擎加载到内存中。此时JSP引擎还请求了jspInit()方法的执行,并对此servlet做初始化。jspInit()方法在servlet的生命周期中只被请求一次。然后jspService()方法被调用来处理客户端的请求和回复操作。对于所有的随后而来的对该JSP文件的请求,服务器将检查该.jsp文件自最后一次被存取后是否经过修改。如果没有修改则请求将交给还在内存中的servlet的jspService()方法以一种同时发生的方式加以处理。注意,由于 servlet始终驻于内存,所以响应是非常快的。
如果.jsp文件被修改了,服务器将自动地对文件重新编译,并将结果取代内存中的servlet,并继续上述处理过程。


虽然JSP效率很高,但在第一次调用时由于需要转换和编译而有一些轻微的延迟。 此外,如果在任何时候如果由于系统资源不足的原因,JSP引擎将以某种不确定的方式将servlet从内存中移去。当这种情况发生时jspDestroy()方法首先被调用, 然后servlet实例便被标记加入“垃圾收集”处理。



jsp编译原理
JSP文件的Scriptlets在编译后将被包含于该JSP servlet的service()方法。当JSP引擎处理客户端请求时,JSP Scriptlets在被请求的时候被执行。如果scriptlet产生输出,输出将在out (JSPWriter)对象中进行缓存然后最终发送到客户端。


.jsp由一个Servlet"解释"执行
这个Servlet就是/servlet/org.git.jsp.JspServlet(假如你把它放在servlet这个区域里)
第一次访问jsp文件的时候这个servlet会把.jsp编译成另一个servlet, 然后以后每次访问这个jsp的时候就直接调用那个servlet了.


jsp预编译器:
Jakarata Tomcat3.1 JSP服务器 (http://jakarta.apache.org)包含了一个 JSP编译器(JSPC),可以进行预编译,既可以定义一个web应用的文件目录,也可以指定某个特定jsp文件来进行编译。
另外,oracle也有jspc。
<

小小豆叮

使用Java开始面向对象的编程

你正在从传统的过程化的编程转向面向对象的开发模式吗?还是想要进入膨胀的Java世界呢?你不会感到孤单的.成千上万的开发者和你处在相同的情形之下.在这系列文章中,我们将使用Java语言带领你一步一步的学习面向对象的开发过程.下面是我们这个系列文章的第一篇:


一种语言是面向对象的究竟意味着什么呢?如果一种编程语言是真正的面向对象的语言,它必须支持以下的特点:

封装--隐藏实现细节
多态--将同一个消息发送给不同的对象并使各个对象以预定的方式对消息做出响应的能力
继承--拓展现存的类来生成专有类继承原来类的状态和行为的能力
动态绑定--在编程时不必知道对象的具体类型就能向它们发送消息的能力

让我们考察一下Java是如何支持这些功能的以及它又如何提供了附加的功能来使得从过程化的程序设计到面向对象的开发的转变过程相对容易.


Java中面向对象的特点
Java是由Sun Microsystems公司在九十年代中期发布的面向对象(OOP)的编程语言.你可以从Sun公司的网站上下载最新的Java开发包(JDK).Java是一种解释性的语言,这意味着其源程序首先被编译成中间代码的形式,然后在每次运行之前都要经过虚拟机的解释,它是彻头彻尾的面向对象的编程语言.

Java对程式员隐藏了许多传统的面向对象编程语言--比方说C++和Object Pascal--的复杂性和让人容易混淆的地方.例如,Java中没有了指针,Java会为程序员自动的清除引用类型,而且所有变量将被自动初始化成适当的缺省值.除了原始数据类型以外,Java中的所有东西都是对象,必要的时候,甚至可以为原始数据类型也提供封装机制.


对象简介

对象是代表现实生活中的实物的软件编程实体,比如说银行帐号,计算机用户,用户介面上的按钮,窗口菜单等等.对象是由它们的状态和行为定义的.例如,一个银行帐号拥有一种状态,诸如当前的收支状况,账户的所有人,允许的最小交易额,等等,而它的行为则包括提取,存入,收支平衡等.

一个对象的状态是由只有对象自己知道的变量定义的.Java把这些变量称为数据域或者成员变量.数据域对对象来说是私有的除非显式的使用关键字来定义它们的作用域,使它们对其它类可见.我们将在以后讨论变量作用域的问题.

一个对象的行为是由它上面的操作定义的.在Java中,这些操作被叫做方法.方法可以改变一个对象的状态,创建新对象,实现实用的功能等.



类是一个实体,它定义了一个对象的运行方式以及在对象被创建或者说实例化的时候所包含的数据.类的作用就象一个模板,一个或者多个对象可以依照它来创建.下面是使用Java面向对象的概念申明HelloWorld应用程序的例子:

public class HelloWorld
{
    private String helloMsg = "Hello World!";
    public static void main(String[] args)
    {
        HelloWorld hw = new HelloWorld();
     }

    public HelloWorld()
    {
    // 显示我们的"Hello World"消息
    System.out.println(helloMsg);
    }
}


上面的例子定义了一个模板,真实的HelloWorld对象可以从这个模板创建.你还会注意到从public static void main(String[] args)这一行开始的一段奇怪的代码.这一段代码定义的是一个特殊的方法main,它其实就是我们这个HelloWorld程序的入口点,上面的程序是一个典型的演示所有的Java应用程序如何定义它们的入口点.注意到即使是这个main入口点也被封装在类里面.对于这个例子,我们就是将它封装在HelloWorld类里.上面的程序展示了如何定义一个类,HelloWorld,以及其中的一个数据域,helloMsg和两个方法main和HelloWorld.HelloWorld方法是一种特殊的方法,这种方法被称做构造函数.我们将在后面的文章里讨论常规方法,构造函数和静态成员函数的细节和区别.

在Java中,所有与一个特殊的类有关的源代码都写在一个与类同名的拥有后缀名.java的文件里.Java编译器读取源文件并将它们翻译成平台无关的,二进制格式的代码,成为字节代码,然后将这些代码分类保存在与类同名的但是后缀为.class的文件里.你最终会为每一个类得到一个class文件.


编译并运行我们的例子程序

一旦你已经从Sun的Web站点上下载了JDK并在你的机器上安装了它,你就可以开始编译并运行Java程序了.要编译并运行我们的例子程序,将HelloWorld类的代码粘贴到你最喜欢的文档编辑器里,将文件保存为HelloWorld.java,然后,在命令提示符下,将当前路径改变到包含了这个文件的路径里.现在你就可以在命令行提示符下键入下面的命令来编译程序了:

Windows:
<你的JDK所在目录>\bin\javac HelloWorld.java

UNIX or Linux:
<你的JDK所在目录>/bin/javac HelloWorld.java

这个命令将在同一个目录里产生一个新的文件,叫做HelloWorld.class.要运行这个程序,请在命令提示符下键入下面的命令:

Windows:
<你的JDK所在目录>\bin\java HelloWorld

UNIX or Linux:
<你的JDK所在目录>/bin/java HelloWorld

你应该可以看到屏幕上显示Hello World!

总结
我们已经接触到了使用Java程序设计语言进行面向对象的编程的一些皮毛知识.下次,我们将剖析我们的例子程序,给它添加更多的功能,并讨论更多的有关对象,类和其它面向对象编程的基本概念以及用Java如何实现它们.

<

小小豆叮

OOP: 理解类和对象

上一次在"使用Java开始面向对象的编程"这篇文章中,我们学习了一个编程语言要真正成为面向对象的,它应该支持信息隐藏/封装,多态,继承和动态绑定.另外,我们知道了Java完全支持这些功能,而且知道了因为Java是一种解释性的语言并运行在虚拟机的内部,所以由Java写成的任何程序都可以在任何支持 Java虚拟机(JVM)的操作系统上运行.我们还明白了对象是代表现实生活中事物的软件-编程模型以及对象是由它们的状态和行为定义的.最后,我们知道了Java中除了原始数据对象以外一切都是对象.

因为这种程序设计风格中的这许多内容都和对象以及类有关,我们将在下面进一步的考察它们.

对象详论
使用对象的一个关键是当你在浏览系统分析文档或者设计文档的时候如何来确定它们.因为对象一般代表人,地方或者事物,所以一个确定对象的基本的方法就是找出句子中所使用的名词.这里有一个简单的例子.在句子"一个顾客可以拥有多于一个的银行帐号",我们就确定了两个对象,客户和帐号.在句子"小猫喵喵叫"中,我们能够确定一个对象,猫.

类详论
前面,我们学习了一个类是定义了对象如何动作以及当对象创建或者说实例化的时候应该包含些什么的实体.在对动物的讨论中,我们可以说,"狗儿汪汪叫,猫喵喵叫,鸭子嘎嘎叫."确定句子中的对象我们就得到了狗,猫和鸭子.至于汪汪叫,喵喵叫,嘎嘎叫,那都是我们对象发出的行为动作.

要实现这些对象,我们需要创建三个对象分别叫Dog,Cat和Duck.要实现它们的行为,我们可以为每一个这些对象创建代表每个对象发出的声音的方法,而且我们把这个方法叫做speak或者,如果我们发挥想象力的话还可以把这个方法叫做sayHello.

在程序的上下文中为了演示这些概念,让我们修改上篇文章中的HelloWorld程序,添加这三个新对象并给它们中的每一个添加sayHello方法,如下所示:

public class HelloWorld
{
    public static void main(String[] args)
    {
        Dog animal1 = new Dog();
        Cat animal2 = new Cat();
        Duck animal3 = new Duck();
        animal1.sayHello();
        animal2.sayHello();
        animal3.sayHello();
    }
}

class Dog
{
    public void sayHello()
    {
        System.out.println("Bark");
    }
}

class Cat
{
    public void sayHello()
    {
        System.out.println("Meow");
    }
}

class Duck
{
    public void sayHello()
    {
        System.out.println("Quack");
    }
}

在编译并运行了这个程序以后,输出应该如下:
Bark
Meow
Quack

看看我们的程序,我们马上就注意到了一些事情:每个对象代表了一种动物,而每个对象实现了一个相同的方法,sayHello.假设我们想要给对象更多的功能以及用来代表对象所指的动物的方法和属性.比方说,我们可以添加一个方法来确定一个动物是不是哺乳类的,或者我们添加一个方法来确定一个动物是不是肉食性的.我们可以通过给每一个对象添加这两种方法来实现或者我们也能够使用OOP的两个最强大的功能:继承和多态.

因为所有的对象都代表一个对象,所以我们将创建一个被称为"基类"或是"超类"的类,它的名字是Animal.我们然后可以让我们的对象从Animal类继承相同的特点并强制每个对象只实现与Animal类不同的功能.

Java用extends关键字指明一个类从另一个继承.让我们利用继承和多态的概念获得代码重用的好处来重建我们的程序并使得每个对象只实现与基类Animal不同的功能:

public class HelloWorld
{
    public static void main(String[] args)
    {
        Dog animal1 = new Dog();
        Cat animal2 = new Cat();
        Duck animal3 = new Duck();
        System.out.println("A dog says " +animal1.getHello()
        +", is carnivorous: " +animal1.isCarnivorous()
        +", is a mammal: " +animal1.isAMammal());

        System.out.println("A cat says " +animal2.getHello()
        +", is carnivorous: " +animal2.isCarnivorous()
        +", is a mammal: " +animal2.isAMammal());

        System.out.println("A duck says " +animal3.getHello()
        +", is carnivorous: " +animal3.isCarnivorous()
        +", is a mammal: " +animal3.isAMammal());
    }
}

abstract class Animal
{
    public boolean isAMammal()
    {
        return(true);
    }

    public boolean isCarnivorous()
    {
        return(true);
    }

    abstract public String getHello();
}

class Dog extends Animal
{
    public String getHello()
    {
        return("Bark");
    }
}

class Cat extends Animal
{
    public String getHello()
    {
        return("Meow");
    }
}

class Duck extends Animal
{
    public boolean isAMammal()
    {
        return(false);
    }

    public boolean isCarnivorous()
    {
        return(false);
    }

    public String getHello()
    {
        return("Quack");
    }
}


在编译并运行我们的程序以后,输出应该如下:
A dog says Bark, is carnivorous: true, is a mammal: true
A cat says Meow, is carnivorous: true, is a mammal: true
A duck says Quack, is carnivorous: false, is a mammal: false

看看我们的例子,你将发现我们定义了一个叫做Animal的新类,它定义了三个方法:isAMammal, isCarnivorous, 和 getHello.你一概还注意到了,我们在每个现存的类申明的前面都添加了extends Animal这个语句.这个语句告诉编译器这些对象是Animal类的子类.

因为方法isAMammal 和 isCarnivorous 都返回 true,所以Dog和Cat类用不着重新实现--即"重载"这两个方法.但是鸭子既不是哺乳动物又不是肉食性的,所以Duck类需要重载这两种方法来返回正确的值.我们所有的对象都以自己独特的方式说"hello",所以它们都需要重载getHello方法.因为每种动物说"hello"的方式都不同,所以我们在基类中将getHello方法申明为抽象的,而且我们没有给这个方法一个函数体.这就迫使Animal的每一个子类重载getHello方法并根据每一个特定动物的需要来定义它.

因为我们将getHello方法申明为虚拟的,我们就不能直接实例化Animal对象.因此,我们需要将Animal类也申明为抽象的.我们通过在Animal类定义的开始行添加abstract关键字来实现这一点.子类重载它们基类的方法的能力就是多态.多态使得子类能够使用基类的方法或是在这些方法不足的时候重载它们.这就实现了代码重用,加快了代码的实现过程,而且它还隔离和程序中的bug,使得程序的维护更容易.

总结
在本文中,我们学习了如何确定潜在的对象.我们还学习了如何使用继承和多态来加快我们的代码实现过程并隔离错误,这使得代码的维护过程更加容易.下一次,我们将展开讨论多态和继承的概念并开始我们对动态绑定的讨论.

<

小小豆叮