Javascript 不能讀寫本機的檔案,這是可以確定的,另外能不能執行本機的程式(例如 ipconfig),大概也不行吧,而這些 Java 都可以!
合作的原理很簡單,在 Applet 所在的 HTML 裡,Javascript 可以呼叫 Applet 帶來的 Java class,至於為什麼可以呼叫,這個我還不知道,就先擱下吧。
先來寫 Java Applet。
package idv.neil.jsa; import java.applet.Applet; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.util.Date; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; /** * @author Neil Chan * @date 2012/10/24 */ @SuppressWarnings("serial") public class JSAApplet extends Applet { /** * Javascript 可以呼叫 所有public 變數或 method */ public String message = null; public String getMessage() { return "我說: " + this.message; } /** * Javascript 可以呼叫其他 class */ public Stepper getStepper() { return new Stepper(); } /** * 讀寫本機檔案 */ public String readText(String filePath) { String text; try { // 因為讀 user home 得有其他權限,這邊先跳過 // String userHome = System.getProperty("user.home"); // String filePath = userHome + "/HelloJSA.txt"; File file = new File(filePath); // 不存在就新增 if (!file.exists()) { FileUtils.touch(file); } // 讀入檔案 text = FileUtils.readFileToString(file); // 更新內容 if (StringUtils.isBlank(text)) { text = new Date().toString(); } else { text = text + "\n" + new Date().toString(); } // 回寫檔案 FileUtils.writeStringToFile(file, text); } catch (Exception e) { text = "Error >>> " + e.getMessage() + "\n"; } return text; } /** * 執行外部程式,以 ipconfig 為例 */ public String exec() { StringBuilder sb = new StringBuilder(); try { Process pr = Runtime.getRuntime().exec("ipconfig"); BufferedReader input = new BufferedReader(new InputStreamReader(pr.getInputStream(), "MS950")); String line = null; while ((line = input.readLine()) != null) { sb.append(line); } int exitVal = pr.waitFor(); sb.append("Exit code - " + exitVal); } catch (Exception e) { e.printStackTrace(); sb.append("Error: " + e.getMessage()); } return sb.toString(); } }如果不需要的話,不用做任何 UI,除此之外,就是一般 Java class。
為了展示 Javascript 與 Applet 的合作本事,刻意從 Applet 抽出功能到其他的 class。
public class Stepper { private int from = 1; private int to = 10; private int step = 1; /** * 設定 */ public void setup(int from, int to, int step) { this.from = from; this.to = to; this.step = step; } /** * 加總 */ public int sum() { Integer[] nums = this.getNums(); int sum = 0; for (int num : nums) { sum += num; } return sum; } /** * 取得陣列,Javascript 不支援取得 object array */ public Integer[] getNums() { List<Integer> list = new ArrayList<Integer>(); for (int i = this.from; i <= this.to; i += this.step) { list.add(i); } return list.toArray(new Integer[] {}); } /** * 取得陣列,Javascript 只支援取得 primitive array */ public int[] getPrimitiveNums() { List<Integer> list = new ArrayList<Integer>(); for (int i = this.from; i <= this.to; i += this.step) { list.add(i); } int[] is = new int[list.size()]; for (int i = 0; i < list.size(); i++) { is[i] = list.get(i); } return is; } }事後測試發現,只有 Chrome 不支援 Object array,Firefox 與 IE9 都支援。
再加入一個完全沒關聯的 class。
public class DateHelper { /** * Javascript 可以存取靜態變數 */ public static String LABEL = "日期:"; public String getDate() { return LABEL + " " + new SimpleDateFormat("yyyy/MM/dd").format(new Date()); } }Java 部份結束,再用 Maven 打包成 jar 檔就可以了。
網頁部份有三個主角,第一個就是剛產生的 jar 檔,另外除了 html 外,還要有一個 jnlp。
HelloJSA.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang="en-US"> <head> <title>HelloJSA</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script> <script src="http://www.java.com/js/deployJava.js"></script> <script type="text/javascript"> $(function() { /* 放這不能用 var attributes = { id: 'jsaApplet', // 供 javascript 使用 code: 'idv.neil.jsa.JSAApplet', width: 1, height: 1 }; var parameters = {jnlp_href: 'HelloJSA.jnlp'}; deployJava.runApplet(attributes, parameters, '1.6'); */ }); function callApplet() { // 要值 var from = prompt('輸入起值: ', '1'); var to = prompt('輸入迄值 : ', '10'); var step = prompt('輸入間隔 : ', '1'); // 回傳物件,呼叫另一個 class 做事情 var stepper = jsaApplet.getStepper(); stepper.setup(from, to, step); // 回傳陣列 var nums = stepper.getNums(); alert("Integer陣列 - " + nums.length + ',' + arrayToString(nums)); var nums = stepper.getPrimitiveNums(); alert("int陣列 - " + nums.length + ',' + arrayToString(nums)); // 回傳 primitive var sum = stepper.sum(); alert("加總 - " + sum); // 直接存取 applet 的 public 變數 jsaApplet.message = prompt('輸入訊息: ', '神奇的 Javascript 與 Applet!!'); // 呼叫 applest 的 public method var message = jsaApplet.getMessage(); alert(message); // 使用 Packages 呼叫靜態 class 與 method jsaApplet.Packages.java.lang.System.out.println("這樣去哪看?"); // 使用 Packages 呼叫靜態變數 jsaApplet.Packages.idv.neil.jsa.DateHelper.label = "今天是 "; // 使用 Packages new 一個 class var dateHelper = new jsaApplet.Packages.idv.neil.jsa.DateHelper(); var dateStr = dateHelper.getDate(); alert(dateStr); } function arrayToString(nums) { var s = '['; try { for ( var i = 0; i < nums.length; i++) { s += (nums[i] + ','); } for ( var a in nums) { s += (nums[a] + ','); } } catch (e) { alert(e); } s += ']'; return s; } function ioFile() { var filePath = prompt('Enter File Path:', 'd:/HelloJSA.txt'); alert(jsaApplet.readText(filePath)); } function exec() { alert(jsaApplet.exec()); } </script> </head> <body> <script type="text/javascript"> // 只能放在這!!!不能放在 $(function(){...}) var attributes = { id : 'jsaApplet', // 供 javascript 使用 code : 'idv.neil.jsa.JSAApplet', width : 1, height : 1 }; // 加上 ?v=xxx 可以防止瀏覽器 cache applet jar // 除了這裡,jnlp 裡 jar href 也要加上 ?v=xxx var parameters = { jnlp_href : 'HelloJSA.jnlp?v=11' }; deployJava.runApplet(attributes, parameters, '1.6'); </script> <h1>HelloJSA Applet</h1> <p> <a href="javascript:; " onclick="callApplet();">Call applet...</a> </p> <p> <a href="javascript:; " onclick="ioFile();">Read and write file...</a> </p> <p> <a href="javascript:; " onclick="exec();">Ipconfig...</a> </p> </body> </html>HelloJSA.jnlp
<?xml version="1.0" encoding="UTF-8"?> <jnlp spec="1.0+" codebase="" href=""> <information> <title>HelloJSA</title> <vendor>Sun</vendor> </information> <resources> <j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se"/> <jar href="HelloJSA.jar?v=11" main="true" /> </resources> <applet-desc name="Hello JSA Applet" main-class="idv.neil.jsa.JSAApplet" width="1" height="1"> </applet-desc> <update check="background"/> </jnlp>完工!
開發過程最困擾的就是瀏覽器將 jar 檔 cache,找不到方法清掉,只能用改網址的消極方式,或者使用官方的作法,但這不是使用者可以接受的方法,後來發現,可以在上述兩個地方加上 ?v=xxx 的方式強迫下載新版本的 jar。
最後發生無法解釋的事了,理論上 Java Applet 預設是不能讀取本機的檔案的,除非有使用者的授權,也就是要將 Applet Sign 過才行,Sign Applet 的參考文章在這。
精簡來說就是兩個指令:
keytool -genkey -alias HelloJSA -validity 365 jarsigner HelloJSA.jar HelloJSA兩個指令都可以在 JAVA_HOME/bin 下找到,第一個用來產生 .keystore,放在 USER_HOME 目錄裡,第二個就是 Sign Applet,會用到第一個指令輸入的密碼。
剛開始開發時,因為 Applet 沒 sign 過,所以在讀寫檔案時出現「access denied java.io.filepermission read」的錯誤訊息,sign 過之後就沒事了,但是在做這邊筆記時,想要再看一次錯誤訊息,所以使用未 sign 的 Applet,結果還是可以執行,哇咧,想出錯也不行,看來還是有很多我不知道的秘密在!
---
---
---
沒有留言:
張貼留言