从我在JavaWorld 上写
Plug into Java with Java Plug
in
开始
年已经过去了
我的前一篇文章给Java Plug
in下了定义
讲到了怎样在Netscape Communicator
和 Internet Explorer
上怎样安装版本
描述了与Java Plug
in相关的Windows注册表设置
了解了Java Plug
in的控制面版
考查了两个java主控台
讨论了HTML 的<embed> 和 <object>标签
还发布了一个基于Swing的用于Demo的applet
自从我写了那篇文章后java和Java Plugin已经相继发布了许多个版本看来那篇文章已显老态所以我已经写好它的系列篇它专注于针对Firefox Web浏览器的最近的Java Plugin其中之一这篇文章首先为你讲解从Firefox怎样访问Java Plugin然后讨论Java Plugin文件对象模块(DOM) applet状态持久化 和cookie比如在Firefox上运行的各种applet当你在学习这些内容时将会强化你已经掌握的知识这篇文章通过探寻Firefox 和Java Plugin之间的联系再做归纳因为对于Java Plugin而言在其他地方会比我在这里所讲的要详细得多所以我建议可以学习Sun的Java Plugin文档你将会学到更多的技术
这篇文章的实验环境是Mozilla Firefox JSE 和Windows SE即使你没有这些软件我仍然建议你读完这篇文章真诚地希望你能发现一些对你来说还是未知的有兴趣的东西
Java Plugin 和Firefox
Java Plugin象桥一样服务于浏览器和一个外部的 java运行环境(JRE)之间Java Plugin对于在Firefox 中运行的applet而言是相当重要的因为浏览器不能自身为其提供一个JRE——其中包括一个JVM因为这篇文章描述的applet会涉及到不同多个不同的Java Plugin内容还有就是这些applet都是在Firefox的环境中运行的所以我们首先应该看一下如何从Firefox中访问Java Plugin
对于初学者来说你必须确认在你的平台上已经安装了JSE 和 JRE在我的windows平台上我用jdk__windowsiexe安装文件把JSE 和 JRE都安装好了确定你已经安装了JRE因为JRE包含了Java Plugin在安装好JSE 后连接Firefox 到 Java Plugin首先从Firefox的工具菜单中选择Options然后选择Web Features最后点击Enable Java checkbox
一般情况下标签<embed> 和 <object>应该指向在Java Plugin下运行的applet这对Java Plugin 来说不是必要的它能运行由简单的<applet>标签指定的applet在安装时你有机会屏蔽Internet Explorer 和 Mozilla (Firefox)/Netscape Web浏览器对applet的执行如果它们遇到了<applet>标签而转交给Java Plugin处理如果你放弃这个选项迟些时候你可以通过Java Plugin的控制面版来完成这个变化比如打开控制面版选择标签Advanced然后点击<APPLET> Tag Support左边的+再选择Mozilla and Netscape选项关闭这个面版后当Firefox遇到<applet>标签时它就会交给Java Plugin执行图给出了这些必要的改变
选择Mozilla and Netscape所以当Firefox遇到<applet>标签时它就会交给Java Plugin执行
提示
我发现在我的平台上选择Mozilla and Netscape不是必须的我的Firefox Web浏览器不会去管那个复选框的设置当遇见<applet>时它就会让Java Plugin来执行Java Plugin 的文档谈到这是个奇怪的行为如果Mozilla 和 Netscape 都被安装好了负责管理对<APPLET>标签是否支持的Mozilla and Netscape选项没有被选中的情况下<APPLET>标签中的内容照样可以在Sun VM上运行这是与Netscape 的自动扫描功能相关的一个bug因为我已经安装过Netscape 所以我认为我找到了这个bug如果在你的平台上遇到了这个问题记住这点就行了
平台上能驻留多个JRE当Firefox遇到<applet>标签时到底哪一个JRE来实现Java Plugin?可以从Java Plugin控制面版上的Java标签上找到答案在二选一的情况下我用来进行版本测试的applet给出了答案同时也证明了Firefox是连接了 Java Plugin的图显示我当前使用的JRE版本是(版本是JSE 的内部版本号)
图 我已经配置好使用JRE 的Java Plugin
Listing 给出了该applet的VersionDemojava文件的源代码这些代码在系统属性javaversion 中设置了JRE 的版本
Listing VersionDetectjava
// VersionDetectjavaimport javaawt*;public class VersionDetect extends javaappletApplet{ public void paint (Graphics g) {int width = getSize ()width;int height = getSize ()height;gsetColor (Colororange);gfillRect ( width height);String version = JRE + SystemgetProperty (javaversion);FontMetrics fm = ggetFontMetrics ();gsetColor (Colorblack);gdrawString (version (widthfmstringWidth (version))/ height/); }}
为了简洁在这篇文章中我将不会给出运行这个或者其他applet的HTML参见这篇文章中的source code
提示 Firefox只支持Java Plugin _和更高版本
调用DOM
根据WWW 联合会 (WC)所说文件对象模块(DOM)是一个API它针对有效的HTML和合式的XML文档它定义了文档的逻辑结构访问和操作文档的方法 Java Plugin提供了两种方法来调用DOM类netscapejavascriptJSObject和Common DOM API
JSObject为Java applet 和Web 浏览器的JavaScript实现之间提供了接口包含使用了DOM 的JavaScript 对象这些对象的例子Document Link 和 Window我将不会深入JSObject因为在我早期的文章Talk with Me Java中我已经讨论了这个类因此我专注于在一个applet中通过Common DOM API来遍历DOM
在JSE 版本中已经介绍过Common DOM API它是一个类和接口的集合通过这些接口可以让applet遍历DOM实例因为一个浏览器通过帧和窗口能够显示多个文档所以会有许多DOM实例以备遍历一般一个applet就遍历它自己的DOM实例—这个与文档相联系的DOM实例指定了这个applet
访问DOM实例的起点就是静态方法getService(Object obj)该方法位于类comsunjavabrowserdomDOMService中一般情况下一个被唤醒applet的this指针会被作为一个参数传递给getService()这个方法要么返回一个 DOMService对象(为这个applet和它的DOM实例之间提供接口)要么抛一个异常comsunjavabrowserdomDOMUnsupportedException (the DOM service is not available to the object) 或者 comsunjavabrowserdomDOMAccessException (a security violation has resulted)例子DOMService service = DOMServicegetService (this);
因为web浏览器提供了不同的DOM实现所以访问一个DOM实例不是线程安全的除非那个访问是发生在DOM access dispatch线程为了确保线程安全性DOMService提供了两个方法来保证访问只是发生在DOM access dispatch线程—invokeLater() 和 invokeAndWait()
两种方法都用实现了接口comsunjavabrowserdomDOMAction的对象作为参数来提供方法run()invokeLater()在DOM dispatch线程中异步执行run()invokeAndWait()在DOM dispatch线程中同步执行run()
方法run()用实现了接口comsunjavabrowserdomDOMAccessor的对象作为其唯一参数然后返回一个基本的Object引用方法run()经常唤醒DOMAccessor的getDocument()方法传递一个applet的指针做getDocument()的Object参数作为回应getDocument()会返回一个实现orgwcdomDocument接口的对象那个对象中就包含了这个文档的信息对于HTML文档而言它会把Document向下转型为HTMLDocument(位于l中)然后调用HTMLDocument的方法去获取这个文档的标题域applet集和一些其他的在调用属性方法后run()返回一个包含文档信息(比如HTMLDocument的标题)的Object对象那个Object随后又从invokeAndWait()和invokeLater()方法中返回
为了梳理上面讨论的内容我写了一个从一个HTML文档中提取标题的applet然后把标题显示给用户下面就是它的代码
Listing TitleExtractjava
// TitleExtractjavaimport comsunjavabrowserdom*;import javaappletApplet;import javaawt*;import l*;public class TitleExtract extends Applet{ private String title = unknown; public void init () {try{DOMService service = DOMServicegetService (this);title = (String) serviceinvokeAndWait (new DOMAction () {public Object run (DOMAccessor accessor){ HTMLDocument doc = (HTMLDocument)accessorgetDocument (TitleExtractthis); return docgetTitle ();} });}catch (DOMUnsupportedException e){Systemoutprintln (DOM not supported);}catch (DOMAccessException e){Systemoutprintln (DOM cannot be accessed);} } public void paint (Graphics g) {int width = getSize ()width;int height = getSize ()height;gsetColor (Colorcyan);gfillRect ( width height);FontMetrics fm = ggetFontMetrics ();gsetColor (Colorblack);gdrawString (title (widthfmstringWidth (title))/ height/); }}
DOMAction的run()方法调用了HTMLDocument的getTitle()方法去获取HTML文档的标题随后显示该标题例如当在l(参见本文的source code)中遇到元素时getTitle()返回Extract the title图中显示了Firefox中的结果
图 显示一个文档的标题
注意
当从标签<applet> 中调用TitleExtract时Firefox要求mayscript属性需要被给出否则由于Firefox的DOM实现依赖于JSObjectJava Plugin会抛出各种异常
Applet在Firefox session中的持久状态
Java Plugin 的文档提到许多有趣的课题其中之一就是applet的持久性API它可以让一个applet保存其状态以备在后来的相同的web浏览器session中使用State被保存在由Java Plugin控制的一个内部持久性存储数据结构中一个session是指某个web浏览器被打开运行开始到这个浏览器被关闭结束
在JSE 中介绍的applet持久性API 由声明在接口javaappletAppletContext中的三个方法构成
·void setStream(String key InputStream stream) throws IOException 需要当前applet上下文的一个具体key和具体的stream作为参数如果一个新的stream对应了当前的key这个新stream将会取代那个旧的stream如果新stream的size超出了内部size的限制将会抛出一个异常IOException
·InputStream getStream(String key)返回与当前applet key相联系的InputStream如果没有指向该key的stream就返回null
·Iterator<String> getStreamKeys()返回一个迭代器它包括了指向当前applet中stream的所有key
通过调用setStream()来保存状态这个方法把一个key(它可以方便地标识一个输入流)和一个InputStream的引用存储到一个内部数据结构中去比如一个HashMap取出某状态可以通过相应的标识key调用getStream()它会返回一个InputStream(如果这个key存在)以恢复到这个状态
因为Java Plugin文档没有提供例子来指出怎样使用applet持久性API所以我准备了一个例子它给出了一个消息传送applet的两个实例其中的一个使用applet持久性API发送消息给另一个图中用户在左边实例的 Message To Send文本框中输入一个消息然后点击该实例的Send按扭然后用户点击右边实例的Receive按扭这个消息就会在该实例的Message Received文本框中显示
图 applet的持久性API使applet之间相互发送消息给对方点击最大化
消息发送发生在下面代码的action listener中
Listing MessageTransferjava
// MessageTransferjavaimport javaapplet*;import javaawt*;import javaawtevent*;import javaio*;import javaxswing*;public class MessageTransfer extends JApplet implements ActionListener{ private TextField txtRecvMsg txtSendMsg; public void init () {// Build the applets GUIsetLayout (new GridLayout ( ));JPanel pnl = new JPanel ();pnlsetLayout (new FlowLayout (FlowLayoutLEFT));pnladd (new JLabel (Message to send:));txtSendMsg = new TextField ();pnladd (txtSendMsg);getContentPane ()add (pnl);pnl = new JPanel ();pnlsetLayout (new FlowLayout (FlowLayoutLEFT));pnladd (new JLabel (Message received:));txtRecvMsg = new TextField ();pnladd (txtRecvMsg);getContentPane ()add (pnl);pnl = new JPanel ();pnlsetLayout (new FlowLayout (FlowLayoutLEFT));JButton btnSend = new JButton (Send);btnSendaddActionListener (this);pnladd (btnSend);JButton btnReceive = new JButton (Receive);btnReceiveaddActionListener (this);pnladd (btnReceive);getContentPane ()add (pnl); } public void actionPerformed (ActionEvent e) {JButton btn = (JButton) egetSource ();if (btngetText ()equals (Send)){String text = txtSendMsggetText ();try{// Output the String object to a byte array output streamByteArrayOutputStream baos = new ByteArrayOutputStream ();ObjectOutputStream oos = new ObjectOutputStream (baos);ooswriteObject (text);oosclose ();// Extract the String object from the byte array output stream// as an array of bytesbyte [] data = baostoByteArray ();// Convert the array of bytes to a byte array input stream When// the setStream() method is invoked it caches the input stream// reference and key in the applet persistent storeInputStream is = new ByteArrayInputStream (data);getAppletContext ()setStream (text is);}catch (Exception e){Systemoutprintln (etoString ());}}else{InputStream is = getAppletContext ()getStream (text);if (is != null)try{// Input the cached String objectObjectInputStream ois = new ObjectInputStream (is);String text = (String) oisreadObject ();txtRecvMsgsetText (text);}catch (Exception e){ Systemoutprintln (etoString ());}} }}
这个消息发送applet把消息持久化为一个string对象通过把这个对象序列化到ByteArrayOutputStream转化这个stream到一个byte数组基于这个byte数组构建一个ByteArrayInputStream然后通过这个stream和一个名为text的key调用方法setStream()该applet用这个名为text的key调用getStream()取回这个消息然后反序列化这个InputStream
在Firefox中被持久化后的消息对在不同窗口或面版上运行的applet都是可以被访问的这种消息对不同session(也就是说Firefox的不同实例)中运行的applet是不能被访问的因为每个session只与它自己的Java Plugin实例和内部持久性存储数据结构进行通信
提示 出于安全考虑使用不同codebase的applet不能访问对方的stream
了解cookie
Web浏览器和web服务器经常发送小的数据包给对方进行交互这些包也就是大家知道的cookie可以被用来跟蹤用户的喜好帮用户自动登录站点等Java Plugin结合web 浏览器可以让applet访问cookie例如Java Plugin让签名applet去查看从web服务器发送到web浏览器的cookie
Java Plugin文档中谈及对cookie的支持时给出了几个与cookie交互的代码片段因为你可能想在applet中测试这些代码片段还有就是可能你不确定该怎样把它们转变成签名applet所以下面我给出了一个用来查看cookie的签名applet在讨论完这些重要的代码后我将带你学习怎样建立和签名applet最后我们会在Firefox web浏览器中运行这个applet
下面就是代码
Listing CookieDetectjava
// CookieDetectjavaimport javaawtBorderLayout;import javaawtevent*;import *;import javautil*;import javaxswing*;public class CookieDetect extends JApplet implements ActionListener{ private JTextArea txtaStatus; private JTextField txtURL; public void init () {// Build the applets GUIJPanel pnl = new JPanel ();pnladd (new JLabel (Enter URL:));txtURL = new JTextField ();txtURLaddActionListener (this); pnladd (txtURL);getContentPane ()add (pnl BorderLayoutNORTH);txtaStatus = new JTextArea ( );getContentPane ()add (new JScrollPane (txtaStatus)); } public void actionPerformed (ActionEvent e) {try{URL url = new URL (txtURLgetText ());URLConnection conn = urlopenConnection ();nnect ();Map<String List<String>> headers = conngetHeaderFields ();List<String> values = headersget (SetCookie);if (values == null){txtaStatussetText (No cookies detected\n);return;}txtaStatussetText ();for (Iterator iter = erator (); iterhasNext();) txtaStatussetText (txtaStatusgetText () + iternext () + \n);txtaStatussetText (txtaStatusgetText () + \n);}catch (Exception e){Systemoutprintln (e);} }}
CookieDetectjava生成的GUI主要由两个条目构成一个接受URL的文本框和一个显示cookie的文本域无论何时用户按下Enter键和这个文本框获得了输入的光标文本框的action listener的方法actionPerformed()都会被调用
方法actionPerformed()首先用文本框中的URL建立一个URL对象然后调用这个对象的openConnection()方法返回一个表示该applet和该URL相互连接的URLConnection对象继续往下面看URLConnection()的方法connect()建立这个连接它的getHeaderFields()方法可以得到不会改变的一组HTTP头和相对应的值Map的方法get()返回与头SetCookie相对应的值组成的List一个循环列举出了该List中的值每个值表示一个cookie被陆续加入文本区域中
假使CookieDetectjava存放在当前目录c:\applets\CookieDetect中完成下面的操作建立和签名这个查看cookie的applet
·编译 CookieDetectjava: javac CookieDetectjava
·把上步得到的CookieDetectclass替换成一个jar文件jar cvf CookieDetectjar CookieDetectclass
·在一个新keytore中创建一个新keykeytool genkey keystore ks alias me当弹出来后输入testtest作为keystore的密码你的姓和名你所在行业(比如IT)你公司的名字你所在城市名所在州或省的名字你的行业代码 不管你刚才输入的信息是否真实当弹出窗口时选择Yes当弹出窗口让你输入me的密码 时单击Enter这样可以让密码与keystore 中的密码(testtest)相同所有这些信息都被放在文件ks中这个文件需要我们自己签名的测试认证
·创建一个签名的测试认证keytool selfcert alias me keystore ks当被弹出后输入testtest作为keystore的密码这个认证就被放进ks中了Alias me (在前一步这步和下一步中)提醒你这个认证已被签名仅仅用来被测试换句话说不要在公共站点上用这个测试认证来发布签名applet
·用这个测试认证来签名这个jar文件jarsigner keystore ks CookieDetectjar me当弹出后输入testtest作为keystore的密码这个工具更新该jar文件的METAINF目录以包含认证信息和CookieDetectclass的一个数字签名
让我们运行这个applet从本文附的代码中找到l然后放进目录c:\applets\CookieDetect下该HTML文件的<applet>中包含一个archive属性来标识CookieDetectjar文件打开Firefox然后输入该HTML文件的URLc:\applets\CookieDetect\l一段时间后图的安全对话框就会出现在你眼前
Figure 安全对话框让你有权信任这个签名的applet
单击yes键响应该安全对话框然后这个applet被显示输入URL然后按下回车如图所示网站JavaWorld 不会发送cookie
Figure 你不会从 JavaWorld 取得cookie
与JavaWorld 相比网站JavaLobby会发送一个cookie输入图中就会显示有这样一个cookieid 是SESSIONID
Figure JavaLobby 发送了一个简单的cookie
一些网站会发送出大量的cookie比如像kasssamba如图所显示的该站点发送了个cookie到web浏览器
Figure Kasamba 发送了不少的cookie到web浏览器
我极力建议你继续去体会cookie的使用把余下的三段代码(来源于Java Plugin 文档中对cookie支持的标题)也转换成签名applet
小窍门
访问 可以学到更多关于cookie的内容
理解 hood
许多人在让Firefox识别Java Plugin时会遇到困难说来说去就是指applet不能运行通过理解Firefox与Java Plugin怎样进行交互你能避开许多类似的麻烦在后续部分我会为你讲解Firefox如何探测Java PluginJRE的 NP*dll 插件文件和一些被称为OJI的东西
探测Java Plugin
在前面你已经安装了一个简单的JRE(它包含了Java Plugin)现在要安装Firefox你可以运行那个浏览器上网沖浪打开那些使用了applet的网页那些applet就会运行起来了不久后你会思考Firefox是怎样找到Java Plugin让applet运行起来的毕竟说来Firefox只检索它自己的插件列表来查找插件而JRE把它自己的Java Plugin放在它自身的bin目录下在这样的安排下Firefox怎么可能检测到Java Plugin呢?
当Firefox开始运行时它的目录服务提供器就接到一个任务在windows平台上的安装目录中去找adobe Acrobat Apple Quicktime Microsoft Windows Media Player 和 Sun Java pluginJava Plugin的安装目录一经找到目录服务提供器将会传递以下信息给pluginscanSunJRE用户的个人设置名称和该设置的值(要求的最低JRE版本)更进一步securityenable_java 这个用户设置必须存在而且它的布尔值必须为true
假如pluginscanSunJRE 和 securityenable_java存在还假设securityenable_java的值是true目录服务提供器会在windows的注册表中列出所有版本号作为HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Plugin的子键最新版本号识别出Firefox所使用的JRE/Java Plugin这个版本号必须要比pluginscanSunJRE的值要大或相等版本号子键自身的JavaHome子键包含了被识别出的JRE根目录路径目录服务提供器把\bin附加在这个目录后这时Firefox就知道了Java Plugin的安装路径了有点糊涂了?我会用一个例子来拨开这一层雾
我的Firefox浏览器给出我的pluginscanSunJRE值为13它同时也指定securityenable_java值为真在启动时目录服务提供器扫描我的windows注册表相关的设置如下
HKEY_LOCAL_MACHINE
Software
JavaSoft
Java Plugin
JavaHome C:\Program Files\Java\jre″
目录服务提供器列举出所有版本号的子键在Java Plugin子键下面那个子键所示的最高版本号就大于或等于13了我仅有一个子键大于或等于因此目录服务提供器含有子键值为的JavaHome子键—C:\Program Files\Java\jre—然后把\bin附加在这个值的后面这就意味着Firefox会在目录c:\Program Files\Java\jre\bin下去找Java Plugin
因为Firefox的pluginscanSunJRE用户设置用3作为它的默认值所以Java Plugin版本低于的不会被识别这就是为什么Firefox只支持Java Plugin _和更高的版本
NP*dll文件
仔细查看你的JRE的 bin目录你会发现不少文件名以NP开头以dll结尾的文件例如我的平台下就列出了下面这些文件
NPJavadll
NPJavadll
NPJavadll
NPJavadll
NPJavadll
NPJPIdll
NPOJIdll
上面列出的文件是Netscape的插件文件加上你可能在你的JRE的bin目录下发现的其他NP*dll文件和也应该被列出的若干个jpi*dll文件他们一起构成了Java Plugin每个NP*dll文件有相同的大小因为他们基本上都是做的同样的事情它与jpi*dll文件中的一个协同工作下载虚拟机让java环境运行起来
每一个NP*dll文件能识别一种或多种MIME(多用途网际邮件扩展协议)类型当遇到某种MIME类型时这种类型会告诉Firefox去装载一个相应的具体NP*dll文件例如思考下面的<EMBED>标签
<embed code=VersionDetectclass width= height= pluginspage= type=application/xjavaapplet;version=>
注意type属性的application/xjavaapplet;version= MIME类型当Firefox遇到这种MIME类型它就会选择相应的NP*dll文件因为NPJavadll标记了那种MIME类型NPJavadll会去装载——而不是其他的NP*dll装载NPJavadll与jpi*dl文件一起让java环境运行
当Firefox遇见<applet>标签时它到底表示的是哪种MIME类型?我认为当Firefox 遇到<applet> 标签时application/xjavavm就是MIME类型测试显示出只有拥有那个MIME类型的NPOJIdll会去装载
什么是OJI?
你可能已经注意到在NPOJIdll中有OJI想知道这三个字母代表什么OJI表示Open JVM Integration这是个Mozilla project/API它允许外部的JVM能够插入一个Mozilla浏览器(就像Firefox这样)除了没有被绑定在内部虚拟机上这个浏览器也没有被绑定在sun指定的外部虚拟机上——浏览器能够通过其他途径与虚拟机进行交互只要这些虚拟机支持OJI
OJI有许多特性包括允许用户通过web浏览器来显示java主控台OJI也会更改一个applet的生命周期一旦一个applet网页被打开这个applet的init() 和 start()方法就会被调用为了让你能看到这个过程先编译Listing 的 LifeCyclejava源代码在Firefox下运行这个applet打开java主空台然后不停在网页之间进行切换在你每次进入这个applet网页的时候你将注意到对这个applet构造器的调用它表明一个新的LifeCycle对象被生成了
Listing LifeCyclejava
// LifeCyclejavapublic class LifeCycle extends javaappletApplet{ public LifeCycle () {Systemoutprintln (constructor called); } public void init () {Systemoutprintln (init() called); } public void start () {Systemoutprintln (start() called); } public void stop () {Systemoutprintln (stop() called); } public void destroy () {Systemoutprintln (destroy() called); }}
总结
即使讲完上面所有的内容我也仅仅是做了浅层次的讲解其实还有很多东西我想写下来可能只有在以后我再写另一篇后续文章来完成了或者你也可以写那篇文章不管怎样都让我们继续拓展我们对Sun的 Java Plugin技术的理解吧