运用加密技术保护Java源代码
12:43
俞良松 (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装入类文件。
|
如前所述,定制ClassLoader只需先获取类文件的数据,然后把字节码传递给运行时系统,由后者完成余下的任务。
ClassLoader有几个重要的方法。创建定制的ClassLoader时,我们只需覆盖其中的一个,即loadClass,提供获取原始类文件数据的代码。这个方法有两个参数:类的名字,以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记是true,我们只需在返回JVM之前调用resolveClass。
|
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:创建应用。我们的例子包含一个App主类,两个辅助类(分别称为Foo和Bar)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其他应用也就不在话下。
- 步骤2:生成一个安全密匙。在命令行,利用GenerateKey工具(参见GenerateKey.java)把密匙写入一个文件:
% java GenerateKey key.data - 步骤3:加密应用。在命令行,利用EncryptClasses工具(参见EncryptClasses.java)加密应用的类:
该命令把每一个.class文件替换成它们各自的加密版本。% java EncryptClasses key.data App.class Foo.class Bar.class - 步骤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分布式应用
12:43
小小豆叮
JDBC接口技术
12:43
小小豆叮
编写一个JAVA的队列类
12:43
小小豆叮
用Java绘制K线
12:43
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的动态类载入机制
12:43
作 为 充 分 利 用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访问数据库
12:43
小小豆叮
Java 的i18n问题
12:42
- 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的可移植性
12:42
小小豆叮
共享内存在Java中的实现和应用
12:42
郭洪锋 (ghf_emai@china.com)
2001 年 9 月
1 共享内存对应应用开发的意义
对熟知UNIX系统应用开发的程序员来说,IPC(InterProcess Communication)机制是 非常熟悉的,IPC基本包括共享内存、信号灯操作、消息队列、信号处理等部分,是开发应
用中非常重要的必不可少的工具。其中共享内存IPC机制的关键,对于数据共享、系统快 速查询、动态配置、减少资源耗费等均有独到的优点。
对应UNIX系统来说,共享内存分为一般共享内存和映像文件共享内存两种,而对应 Windows,实际上只有映像文件共享内存一种。所以java应用中也是只能创建映像文件共享 内存。
在java语言中,基本上没有提及共享内存这个概念,但是,在某一些应用中,共享内 存确实非常有用,例如采用java语言的分布式应用系统中,存在着大量的分布式共享对象, 很多时候需要查询这些对象的状态,以查看系统是否运行正常或者了解这些对象的目前的一 些统计数据和状态。如果采用网络通信的方式,显然会增加应用的额外负担,也增加了一些 不必要的应用编程。而如果采用共享内存的方式,则可以直接通过共享内存查看对象的状态 数据和统计数据,从而减少了一些不必要的麻烦。
共享内存的使用有如下几个特点:
- 可以被多个进程打开访问;
- 读写操作的进程在执行读写操作时其他进程不能进行写操作;
- 多个进程可以交替对某一共享内存执行写操作;
- 一个进程执行了内存的写操作后,不影响其他进程对该内存的访问。同时其他进程对更新后的内存具有可见性。
- 在进程执行写操作时如果异常退出,对其他进程写操作禁止应自动解除。
- 相对共享文件,数据访问的方便性和效率有
另外,共享内存的使用上有如下情况:
- 独占的写操作,相应有独占的写操作等待队列。独占的写操作本身不会发生数据的一致性问题。
- 共享的写操作,相应有共享的写操作等待队列。共享的写操作则要注意防止发生数据的一致性问题。
- 独占的读操作,相应有共享的读操作等待队列;
- 共享的读操作,相应有共享的读操作等待队列。
一般情况下,我们只是关心第一二种情况。
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();
如果多个应用映像同一文件名的共享内存,则意味着这多个应用共享了同一内存数据。 这些应用对于文件可以具有同等存取权限,一个应用对数据的刷新会更新到多个应用中。
为了防止多个应用同时对共享内存进行写操作,可以在该共享内存的头部信息加入写操 作标志。该共享内存的头部基本信息至少有:
|
共享内存的头部信息是类的私有信息,在多个应用可以对同一共享内存执行写操作时, 开始执行写操作和结束写操作时,需调用如下方法:
|
如果执行写操作的应用异常中止,那么映像文件的共享内存将不再能执行写操作。为了 在应用异常中止后,写操作禁止标志自动消除,必须让运行的应用获知退出的应用。在多线 程应用中,可以用同步方法获得这样的效果,但是在多进程中,同步是不起作用的。方法可 以采用的多种技巧,这里只是描述一可能的实现:采用文件锁的方式。写共享内存应用在获 得对一个共享内存写权限的时候,除了判断头部信息的写权限标志外,还要判断一个临时的 锁文件是否可以得到,如果可以得到,则即使头部信息的写权限标志为1(上述),也可以 启动写权限,其实这已经表明写权限获得的应用已经异常退出,这段代码如下:
|
3 共享内存在java中的应用
共享内存在java应用中,经常有如下两种种应用:
- 永久对象配置。
在java服务器应用中,用户可能会在运行过程中配置一些参数,而这些参数需要永久 有效,当服务器应用重新启动后,这些配置参数仍然可以对应用起作用。这就可以用到该文 中的共享内存。该共享内存中保存了服务器的运行参数和一些对象运行特性。可以在应用启 动时读入以启用以前配置的参数。
- 查询共享数据。
一个应用(例 sys.java)是系统的服务进程,其系统的运行状态记录在共享内存中,其 中运行状态可能是不断变化的。为了随时了解系统的运行状态,启动另一个应用(例 mon.java),该应用查询该共享内存,汇报系统的运行状态。
可见,共享内存在java应用中还是很有用的,只要组织好共享内存的数据结构,共享内存就可以在应用开发中发挥很不错的作用。
<小小豆叮
DOM文档操作和XML文件互相转换的java实现
12:42
简介:该文简要描述了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文档可以有如下代码实现:
|
4.2 遍历DOM文档
获得接口类document实例后,可以对DOM的文档树进行访问。要遍历DOM文档,首先要获得Root元素。然后获得Root元素的子节点列表。这里通过递归的方法实现遍历的目的。
|
4.3 修改DOM文档
修改DOM文档的API在DOM level 2 Core规范中做了说明,jkd1.4中的org.xml.dom中实现了这些API。修改DOM文档操作主要集中在Document、Element、Node、Text等类中,这里给出的例子中是在解析出的DOM文档中增加一系列对象,对应与在XML文件中增加一条记录。
|
4.4 将DOM文档转化成XML文件
|
这里提供了该例程的完整程序,该例程在windows 2000中jdk1.4环境中运行通过。
以上给出了一个例子,读者可以从中了解到对DOM操作的思路。因为对DOM的操作均遵循了DOM规范,所以也适用于其它语言对DOM的处理。
参考资料:
- http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-2000-1113
- Java 2 Platform, Standard Edition, V1.4.0 API Specificaion
小小豆叮
JSP白皮书 之三
12:42
采用企业级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)创建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技术保持平台和服务器间固有的可移植性。
小小豆叮
JSP白皮书 之一
12:42
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白皮书 之二
12:42
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实例程序
12:42
小小豆叮
如何让 JAVA 程序在 NT 的服务中运行
12:41
小小豆叮
在JSP编译的时候,服务器内部做了什么?
12:41
在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开始面向对象的编程
12:41
你正在从传统的过程化的编程转向面向对象的开发模式吗?还是想要进入膨胀的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: 理解类和对象
12:41
上一次在"使用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,使得程序的维护更容易.
总结
在本文中,我们学习了如何确定潜在的对象.我们还学习了如何使用继承和多态来加快我们的代码实现过程并隔离错误,这使得代码的维护过程更加容易.下一次,我们将展开讨论多态和继承的概念并开始我们对动态绑定的讨论.
小小豆叮