使用GWT已经半年了,查了很多资料,但发现国内关注它的人很少,而且骂声也不少(当然GWT也有让我恶心的地方),所以就把平时实验的结果和感想,在这里和大家分享一下。
GWT困扰我的一个最恶心的缺点,就是凡事要编译。系统一大,模块之间依赖很强,修改一个客户端(界面)的小功能,就要重编译整个项目,费时费劲,我们项目现在重编一次已经需要800多秒了——好在有Development mode(感叹这个东东的强大)。
之前看到过GWT提供JSNI的功能,能够使gwt 的java code与纯JavaScript互通信,因此打算尝试使用JSNI作为中介,看看能不能减轻模块间的依赖——或者实现多模块之间实现分模块编译。
想法是这样的,大部分模块基本是不变的,希望不要经常编译,假设其为Dll1;有些为客户开发的模块Dll2,它依赖于Dll1,而且经常发生变化(需求总是变化的)。希望修改了Dll2后,不重新编译Dll1。
Dll1和Dll2只是一个命名,并不是真正的dll啊!
如果按照GWT的依赖实现,Dll2中的gwt.xml中,声明inherit name="demo1.Dll1"后,重编Dll2其实就包含重编Dll1
使用JSNI,见http://code.google.com/webtoolkit/doc/latest/DevGuideCodingBasicsJSNI.html
将Dll1的接口,使用$entry方法,发布成为标准的javascript;Dll2不直接依赖Dll1,使用JSNI,调用Dll1发布成为javascript的接口。
Dll1中,用GWT的java实现了3个方法
import java.util.Date;
import com.extjs.gxt.ui.client.data.BaseModel;
import com.extjs.gxt.ui.client.js.JsonConverter;
import com.extjs.gxt.ui.client.widget.TabItem;
import com.extjs.gxt.ui.client.widget.TabPanel;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.google.gwt.dom.client.Element;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;
public class DllImpl {
public static void method1(String value) {
Window.alert(value);
}
public static String methodJson() {
BaseModel result = new BaseModel();
result.set( " int " , 1 );
result.set( " double " , new Double( 1.2 ));
result.set( " string " , " str " );
result.set( " date " , new Date());
result.set( " boolean " , true );
JSONObject obj = JsonConverter.encode(result.getProperties());
String str = obj.toString();
return str;
}
public static Element methodJS() {
TabPanel p = new TabPanel();
TabItem item = new TabItem();
item.setClosable( true );
item.setText( " dll 1 " );
item.setLayout( new FitLayout());
p.add(item);
RootPanel.get( " cross " ).add(p);
return item.getElement();
}
public static native void exportStaticMethod() /* -{
$wnd.method1 =
$entry(@demo1.client.DllImpl::method1(Ljava/lang/String;));
$wnd.methodJson =
$entry(@demo1.client.DllImpl::methodJson());
$wnd.methodJS =
$entry(@demo1.client.DllImpl::methodJS());
}- */ ;
}
exportStaticMothod是将类中的3个方法,发布为javascript,其路径就是$wnd.method1、$wnd.methodJson和$wnd.methodJS,参数列表参考google文档中的JSNI。
在Dll1的EntryPoint中,调用这个exportStaticMethod方法。
import com.google.gwt.core.client.EntryPoint;
/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class Dll1 implements EntryPoint {
public void onModuleLoad() {
DllImpl.exportStaticMethod();
}
}
在Dll2中,就使用JSNI调用javascript,路径就是之前的$wnd.method1、$wnd.methodJson和$wnd.methodJS
import java.util.Date;
import java.util.Map;
import com.extjs.gxt.ui.client.data.BaseModel;
import com.extjs.gxt.ui.client.js.JsonConverter;
import com.extjs.gxt.ui.client.widget.form.FormPanel;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Timer;
/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class Dll2 implements EntryPoint {
public void onModuleLoad() {
Timer t = new Timer() {
@Override
public void run() {
// 由于Dll1和Dll2没有声明依赖,所以使用Timer强制延时
callMethod1( " Hello world form dll2. " );
String json = callMethodJSON();
Map < String, Object > map = JsonConverter.decode(json);
BaseModel m = new BaseModel(map);
System.out.println(m.get( " int " ) instanceof Integer);
System.out.println(m.get( " double " ) instanceof Double);
System.out.println(m.get( " string " ) instanceof String);
System.out.println(m.get( " date " ) instanceof Date);
System.out.println(m.get( " boolean " ) instanceof Boolean);
Element x = callMethodJS();
FormPanel f2 = new FormPanel();
f2.setHeading( " dll 2 " );
f2.render((com.google.gwt.user.client.Element) x);
}
};
t.schedule( 2000 );
}
protected native void callMethod1(String value) /* -{
$wnd.method1(value);
}- */ ;
protected native String callMethodJSON() /* -{
return $wnd.methodJson();
}- */ ;
protected native Element callMethodJS() /* -{
var x = $wnd.methodJS();
//alert(x);
return x;
}- */ ;
}
之所以要用timer,是因为Dll2没有直接依赖Dll1,所以HTML声明加载Dll1和Dll2时,不能确定Dll2就是在Dll1加载后才被加载。如果Dll2在Dll1前加载,则调用的$wnd.method1()就还没被Dll1所“导出”,调用就会失败。
HTML是这样加载2个模块的——"mce:"是CSDN的blog自动添加上去的,主要参考那两个script标记,分别使html加载dll1模块和dll2模块。
< html >
< head >
< meta http-equiv ="content-type" content ="text/html; charset=UTF-8" >
< link type ="text/css" rel ="stylesheet" href ="DoubleMain.css" mce_href ="DoubleMain.css" >
< title > Web Application Starter Project </ title >
< mce:script type ="text/javascript" language ="javascript"
src ="doublemain/doublemain.nocache.js" ></ mce:script >
< mce:script type ="text/javascript" language ="javascript"
src ="doublemain2/doublemain2.nocache.js" ></ mce:script >
</ head >
< body >
<!-- OPTIONAL: include this if you want history support -->
< iframe src ="javascript:''" mce_src ="javascript:''" id ="__gwt_historyFrame" tabIndex ='-1'
style ="position: absolute; width: 0; height: 0; border: 0" ></ iframe >
</ body >
</ html >
至此,实现了一个简单的多模块间解耦合的调用,但是这里面的问题很多,不是一劳永逸,留待下篇博文来分解。