GWT 入门介绍(续)
功能介绍(集成JUnit)
@TODO
功能介绍(国际化)
项目开发过程中经常需要一些可配置的常量,例如查询最大条数,目录位置等。在传统的Java应用程序中这些内容通常会放在
属性文件中(Properties文件),但是使用属性文件有些弊端,第一,不支持类型,所有的内容都是String,第二是,只有在具体使用
的时候才能发现有些属性没有定义,而不能在编译的时候发现。
那么GWT如何处理这个问题呢?GWT中有一个特殊的接口com.google.gwt.i18n.client.Constants可以使用这个接口达到
定义常量的效果,并且这些常量在编译的时候被绑定,而且可以支持类型。
使用GWT主要有以下几步:
第一步,建立一个集成于Constants的接口,例如:
public interface NumberFormatConstants extends Constants {
/**
* @return the localized decimal separator
*/
String decimalSeparator();
/**
* @return the localized thousands separator
*/
String thousandsSeparator();
}
第二步,根据接口中定义的方法定义一个跟接口同名的属性文件,例如:
#NumberFormatConstants.properties
decimalSeparator = ,
thousandsSeparator = .
第三步,获取文件中定义的内容,例如:
public void useNumberFormatConstants() {
NumberFormatConstants constants = (NumberFormatConstants) GWT.create(NumberFormatConstants.class);
String decimalSep = constants.decimalSeparator();
String thousandsSep = constants.thousandsSeparator();
String msg = "Decimals are separated using '" + decimalSep + "'";
msg += ", and thousands are separated using '" + thousandsSep + "'";
showMessage(msg);
}
上述三步中在第二步和第三步中间隐含了伊特特殊的步骤,就是GWT编译器结合接口文件和属性文件编译出了一个
类,这个类实现了这个接口,每一个方法返回属性文件中的值。
其中GWT.create()方法可以获得生成的中间类的引用。
通常情况下,接口方法明和属性文件中的名字相同,例如:
String decimalSeparator(); 和 thousandsSeparator = .
但是也可以自定义接口方法和属性文件中内容的映射,例如:
public interface NumberFormatConstantsWithAltKey extends Constants {
/**
* @gwt.key fmt.sep.decimal
* @return the localized decimal separator
*/
String decimalSeparator();
/**
* @gwt.key fmt.sep.decimal
* @return the localized thousands separator
*/
String thousandsSeparator();
}
@gwt.key fmt.sep.decimal 定义了属性文件中key的内容,所以属性文件应该为:
#NumberFormatConstants.properties
fmt.sep.decimal = .
fmt.sep.thousands = ,
Constants子接口中定义的方法必须满足如下形式:
T methodName()
这里T是一个返回值,T可以使用如下表中的所有类型:
T类型 属性文件定义
String 简单的字符串
String[] 使用逗号分割的字符串,如果某个字符串中包含逗号需要使用\\作为转移字符,例如:'\\,'
int int值,在编译的时候做类型检查
float float值,在编译的时候做类型检查
double double值,在编译的时候做类型检查
boolean boolean值"true" 或者 "false"), 在编译的时候做类型检查
Map 使用逗号分隔的字符产,每一个字符产在属性文件中有一条定义,定义了一个Key-Value值
Map示例:
a = X
b = Y
c = Z
someMap = a, b, c
Map someMap();方法得到的内容为:{a:X, b:Y, c:Z}
ConstantsWithLookup
ConstantsWithLookup是Constants的子接口,用法一样,只不过ConstantsWithLookup有一组通过属性名字获取属性值的方法:
getBoolean(String) 通过名字找到boolean型内容
getDouble(String) 通过名字找到double型内容
getFloat(String) 通过名字找到float型内容
getInt(String) 通过名字找到int型内容
getMap(String) 通过名字找到Map型内容
getString(String) 通过名字找到String型内容
getStringArray(String) 通过名字找到String[]型内容
效率问题:Constants效率比ConstantsWithLookup高,为什么呢?Constants在编译的时候会生成对应的JavaScript代码,
GWT Compiler会根据程序中是否使用了某些属性来决定这些内容是否会被编译为JavaScript,所以及时在Constants中声明
了某些方法,如果在代码中不使用的话,不会被编译为JavaScript代码的。
但是ConstantsWithLookup有根据属性名字查找属性内容的方法,所以,GWT Compiler不能根据上述方法确定属性是否被使用,
所以所有的属性内容都回被编译为JavaScript代码。
这是ConstantsWithLookup的优点,也是缺点!
Message类
在使用Constants(或者ConstantsWithLookup)的时候,我们只能使用预定义的消息,有些时候我们需要可变的消息。
例如:
我们需要一个通用的消息再加上一个功能名字的参数怎么实现呢?
Message类相当于Java中的Properties,ResourceBundle和MessageFormat的联合体,例如:
消息文件类:
public interface GameStatusMessages extends Messages {
/**
* @param username the name of a player
* @param numTurns the number of turns remaining
* @return a message specifying the remaining turns for a player
*/
String turnsLeft(String username, int numTurns);
/**
* @param numPoints the number of points
* @return a message describing the current score for the current player
*/
String currentScore(int numPoints);
}
属性文件定义:
turnsLeft = Turns left for player ''{0}'': {1}
currentScore = Current score: {0}
使用:
public void beginNewGameRound(String username) {
GameStatusMessages messages = (GameStatusMessages) GWT.create(GameStatusMessages.class);
// Tell the new player how many turns he or she has left.
int turnsLeft = computeTurnsLeftForPlayer(username);
showMessage(messages.turnsLeft(username, turnsLeft));
// Tell the current player his or her score.
int currentScore = computeScore(username);
setCurrentPlayer(username);
showMessage(messages.currentScore(currentScore));
}
我们可以看到在使用的时候基本一致,但是,可以使用参数配置原有的消息。
另外Message的方法的格式为:
String methodName(optional-params)
从中我们也可以看出区别,Message只能使用String类型的参数。
Constants(或者ConstantsWithLookup)和Message的区别是:
Constants用来定义系统的常量,支持多种类型。
Message用来定义系统的消息,可以支持参数化消息,但是只支持String类型的内容。
在使用Constants和Message的时候,可以将属性文件的编码设置为UTF-8这样,就不用
使用Native2ascii将正常的文件转移为utf-8的替换文件了。
当然如果你觉得不麻烦也可以使用传统的Java属性文件(使用native2ascii处理过得文件)。
功能介绍(JavaScript Native Interface)
JavaScript Native Interface = JSNI
JSNI定义了在GWT环境下,Java与JavaScript交互的一种方法。
虽然GWT的一些核心的方法是用JavaScript编写的,但是这里还是不推荐使用JNI,应为这样做与GWT的初衷相悖,
并且,有一定的难度,开发调试也相对困难。
Java调用JavaScript方法:
JSNI方法定义需要使用native关键字,并且需要在参数列表之后,结尾的分号之前定义。JSNI方法的开始使用/*-{
结尾使用}-*/,例如:
public static native void alert(String msg) /*-{
$wnd.alert(msg);
}-*/;
当上述方法在Java中调用的时候,实际上将会调用Window的alert方法,将传入的内容打印出来。
在Hosted Mode下,断点可以设置在上述方法中,可以方便的查看传入的参数。
JavaScript调用Java方法:
方法调用方式:
[instance-expr.]@class-name::method-name(param-signature)(arguments)
属性访问方式:
[instance-expr.]@class-name::field-name
[instance-expr.]
用来区分实例方法调用还是静态方法调用。在调用实例方法的时候必须出现,在调用静态方法的时候不能出现。
class-name
类的名字。
method-name
方法的名字
param-signature
方法的参数列表,这里使用的是内部形式(参考Java虚拟机Class格式),但是不需要写返回值类型。
arguments
调用方法的实际参数。
例如:
public class JSNIExample {
String myInstanceField;
static int myStaticField;
void instanceFoo(String s) {
// use s
}
static void staticFoo(String s) {
// use s
}
// 该方法被调用的时候将在JavaScript中执行,并且
// 可以使用JavaScript中的内容。
public native void bar(JSNIExample x, String s) /*-{
// 调用这个实例本身的instanceFoo方法
this.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);
// 调用x实例(输入参数)上的instanceFoo实例方法
x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);
// 调用静态方法 staticFoo()
@com.google.gwt.examples.JSNIExample::staticFoo(Ljava/lang/String;)(s);
// 读取这个实例的变量
var val = this.@com.google.gwt.examples.JSNIExample::myInstanceField;
// 设置x上的实例变量
x.@com.google.gwt.examples.JSNIExample::myInstanceField = val + " and stuff";
// Read static field (no qualifier)
@com.google.gwt.examples.JSNIExample::myStaticField = val + " and stuff";
}-*/;
}
Java和JavaScript之间参数的传递:
Java -> JavaScript
Java type JavaScript Type
numeric primitive a JavaScript numeric value, as in var x = 42;
String a JavaScript string, as in var s = "my string";
boolean a JavaScript boolean value, as in var b = true;
JavaScriptObject (see notes) a JavaScriptObject that must have originated from JavaScript code, typically as the return value of some other JSNI method
Java array an opaque value that can only be passed back into Java code
any other Java Object an opaque value accessible through special syntax
异常
调用JSNI方法的时候会抛出一个JavaScriptException的异常,但是由于JavaScript不是一个强类型的语言,所以
无法想Java一样处理JavaScript异常。一个好的方式是在Java中处理Java异常,在JavaScript中处理JavaScript异常。
另外在JSNI方法,Java普通方法混掉的过程中,异常可以从最底层移植抛到最想的调用层,例如:
1. Java method foo() calls JSNI method bar()
2. JavaScript method bar() calls Java method baz()
3. Java method baz() throws an exception
baz()中抛出的异常可以蔓延到bar方法,可以在foo方法中捕获。
从Host Model到 Web Model
在Host Model方式下,GWT并不将Java代码编译为JavaScript,而是在GWT环境中直接运行Java bytecode,
但是项目正式部署之后使用的是Web Model,那么如何从Host Model迁移到Web Model呢?
首先需要将Java代码编译为JavaScript代码。
使用如下命令可以将Java代码编译为JavaScript代码:
java -cp "%~dp0\src;%~dp0\bin;%~dp0\../../gwt-user.jar;%~dp0\../../gwt-dev-windows.jar" com.google.gwt.dev.GWTCompiler -out "%~dp0\www" %* com.google.gwt.sample.hello.Hello
-cp 指定源代码目录,Class目录,和GWT的jar文件的路径
-out 指定JavaScript代码的输出路径
com.google.gwt.sample.hello.Hello 指定编译的Module,一般是gwt.xml文件中entry-point类去掉client之后的内容。
当代码量比较大的时候,需要指定Java使用内存的大小,否则会内存溢出。
java -Xmx512m -Xms128m -cp "%~dp0\src;%~dp0\bin;%~dp0\../../gwt-user.jar;%~dp0\../../gwt-dev-windows.jar" com.google.gwt.dev.GWTCompiler -out "%~dp0\www" %* com.google.gwt.sample.hello.Hello
之后将编译成的JavaScript代码拷贝到Web项目的根目录中,与WEB-INF相同层次的目录。
最后需要将gwt.xml文件中定义的service编程对应的Servlet。
=>
Calendar
com.google.gwt.sample.dynatable.server.SchoolCalendarServiceImpl
Calendar
/calendar
使用数据源
Hosted Mode 虽然开发起来很方便,但是也有缺点,例如,数据源的配置就有问题。
在GWT Hosted Mode下无法配置数据源,一种可选的方式是使用一个假的数据库链接
管理类,这个类的接口返回Connection,内部以DriverManager的方式实现,等待
后续部署之后再切换到数据源模式。
日志处理(Log4J)
回想GWT应用程序,client包内部的代码将会被编译为客户端JavaScript代码,所以这里
不需要记录日志,也不可能使用Log4j。
但是Server包内的内容在服务器上运行,需要合理的使用日志。
一个简单的Login示例
代码结构如下:
└─src
└─com
└─jpleasure
└─gwt
└─logon
│ LogonDemo.gwt.xml GWT配置模块文件
│
├─client 客户端代码包
│ │ LogonDemo.java GWT代码的入口点
│ │ LogonDemoController.java 画面迁移控制类
│ │
│ ├─exception 异常定义包
│ │ ApplicationException.java 应用程序异常
│ │
│ ├─panel 页面Panel包