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,結果還是可以執行,哇咧,想出錯也不行,看來還是有很多我不知道的秘密在!
---
---
---
沒有留言:
張貼留言