导读:
什么是ZK
利用ZK框架设计的web应用程序具备丰富的胖客户端特性和简单的设计模型。ZK包括一个基于AJAX可自动进行交互式操作的事件驱动引擎和一套兼容XUL的组件。利用直观的事件驱动模型,你可以用具有XUL特性的组件来表示你的应用程序并通过由用户触发的监听事件来操作这些组件,就像开发桌面应用程序一样简单。
先来点直观的感受:http://www.zkoss.org/zkdemo/userguide/
什么是Springframework 2.0
大名鼎鼎的Springframework相信没有人不知道吧,就在不久前,Interface21又推出了Spring 2.0版本,Spring2.0的发布恐怕算得上2006年Java社区的一件大事了。Spring2.0中一些新的特性,如:基于XML Schema语法的配置、JPA的支持、支持动态语言、异步JMS等诸多功能都非常不错,总体来说,Spring2.0将向未来的宏大目标又迈进了一大步。
动机
因为工作需要,要写一个基于Web的JMS消息发送程序,当然,这对于技术人员来说,是小菜一碟,现实问题摆在面前,一是时间紧,二是由于客户对技术方面一般,所以GUI的美观程度至关重要,怎么办呢?思前想后,决定使用Ajax技术,但我们也知道,如今Ajax的框架多如牛毛,我该选择哪一个呢,无意中,通过Google找到了ZK。在看完她的在线演示后,被她华丽的外观,简洁实现方式所吸引。于是,就这样,我一边看着ZK技术手册,一边上路了。
第一次重构 — Spring登场
ZK作为表现层技术,一般通过两种手段与业务层交互,一种方式是只使用ZK做表现层,当页面提交后,再由用户指定的servlet来处理具体的业务逻辑,另一种方式是通过象.NET的WebForm一样基于事件响应方式编程。例如:
<window title="event listener demo" border="normal" width="200px"><br> <label id="mylabel" value="Hello, World!"> <br> <button label="Change label"> <br> <attribute name="onClick"><br> mylabel.value = "Hello, Event!" <br> </attribute><br> </button> <br> </label></window>
很明显,使用第二种方式会更加简单,更加容易理解,但问题也随之产生,因为每个事件处理都要使用大量类信息,随着业务逻辑的复杂性增加,每个ZUL(ZK页面)也会变得相当的臃肿,怎么办呢?当然是使用Spring!!原因有二:一是可以将大量业务逻辑代码封装成bean,减少表现层的复杂性,另一个好处是,由于业务场景需要处理JMS要关内容,通过Spring2.0对JMS强大的支持功能,也可以大大减少工作量。说干就干,通过研究尝试,我发现在ZK中可以通过如下方式访问Spring的上下文:
…..

...
{
importorg.springframework.web.context.WebApplicationContext;
importorg.springframework.web.context.support.WebApplicationContextUtils;
WebApplicationContextctx=(WebApplicationContext)Executions.getCurrent().getDesktop().getWebApp().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
…
这样就如鱼得水了,我可以任意使用用Spring管理的Bean了。
第二次重构 — Spring JMS发送
我们知道在Spring中处理JMS的发送一般来讲是通过配置的方式得到JmsTemplate,然后当要发送消息时,我们再创建一个匿名类,如下:
….

this
.jmsTemplate.send(
this
.queue,
new
MessageCreator()
...
{

publicMessagecreateMessage(Sessionsession)throwsJMSException...{
returnsession.createTextMessage("helloqueueworld");
}
}
);
…
通过分析,很显然,使用匿名类的原因就在于,只有在消息发送这一时刻才能决定发送什么类型的消息以及消息内容是什么,知道了这一点,其实我们可以写一个工具Bean类,来封装这个逻辑,来避免这个繁琐的过程,代码如下:
MessageCreatorBean.java
package
bean;
import
java.io.ByteArrayOutputStream;
import
java.io.File;
import
java.io.InputStream;
import
java.util.Date;
import
java.util.HashMap;
import
java.util.Iterator;
import
java.util.Map;
import
java.util.Properties;
import
java.util.Set;
import
java.util.Map.Entry;
import
javax.jms.BytesMessage;
import
javax.jms.JMSException;
import
javax.jms.Message;
import
javax.jms.Session;
import
org.apache.commons.logging.Log;
import
org.apache.commons.logging.LogFactory;
import
org.springframework.jms.core.MessageCreator;
import
org.zkoss.util.media.Media;

public
class
MessageCreatorBean
implements
MessageCreator
...
{
privateMediamedia;
privateMapproperties;
privateStringtext;

publicvoidsetText(Stringstr)...{
text=str;
}

publicStringgetText()...{
returntext;
}

publicvoidsetMedia(Mediam)...{
media=m;
}

publicMediagetMedia()...{
returnmedia;
}

publicvoidsetProperties(Mapmap)...{
properties=map;
}

publicMapgetProperties()...{
returnproperties;
}

privatecreateBinaryMessage(Sessionsession)throwsJMSException...{
BytesMessagemsg=null;
byte[]bytes=null;

try...{
bytes=media.getByteData();
}

catch(IllegalStateExceptionise)...{

try...{
InputStreamis=media.getStreamData();
ByteArrayOutputStreambaos=newByteArrayOutputStream();
byte[]buf=newbyte[1000];
intbyteread=0;

while((byteread=is.read(buf))!=-1)...{
baos.write(buf,0,byteread);
}
bytes=baos.toByteArray();
}

catch(IOExceptionio)...{
}
}
msg=session.createBytesMessage();
msg.writeBytes(bytes);
properties.put("m_name",media.getName());
properties.put("m_format",media.getFormat());
properties.put("m_ctype",media.getContentType());
returnmsg;
}

privateMessagecreateTextMessage(Sessionsession)throwsJMSException...{
Messagemsg=session.createTextMessage(text);
properties.put("m_name",(newDate()).getTime()+".xml");
properties.put("m_format","xml");
properties.put("m_ctype","text/xml");
returnmsg;
}

publicMessagecreateMessage(Sessionsession)throwsJMSException...{
Messagemsg=null;
if(properties==null)properties=newProperties();

if(media==null)...{
msg=createTextMessage(session);
}

else...{
msg=createBinaryMessage(session);
}
applyProperties(msg);
returnmsg;
}

publicvoidmergeProperties(Propertiesprops)...{

if(properties==null)...{
properties=newProperties();
}

if(props!=null)...{
Setkeys=props.keySet();

for(Iteratorit=keys.iterator();it.hasNext();)...{
Stringkey=(String)it.next();
properties.put(key,props.get(key));
}
}
}

privatevoidapplyProperties(Messagemsg)throwsJMSException...{

if(properties!=null)...{

for(Objects:properties.keySet())...{
msg.setStringProperty((String)s,(String)properties.get(s));
}
}
}
}
配置Springframework Context:
<bean id="normalMessageCreator"><br></bean>
class="com.bea.de.bean.
<
class
="com.bea.de.bean.MessageCreatorBean"
/>
使用的时候我们就可以通过Spring来访问了。
…

void
send(Mediamedia,Propertiesprops)
...
{
WebApplicationContextctx=(WebApplicationContext)Executions.getCurrent().getDesktop().getWebApp().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
JmsTemplatejt=(JmsTemplate)ctx.getBean("jmsTemplate");
Queuequeue=(Queue)ctx.getBean("binaryQueue");
MessageCreatorBeanmc=(MessageCreatorBean)ctx.getBean("binaryMessageCreator");
Propertiesp=(Properties)ctx.getBean("messageProperties");
mc.mergeProperties(p);
mc.mergeProperties(props);
mc.setMedia(media);
jt.send(queue,mc);
}
…
第三次重构—BSH(BeanShell)登场
虽然Spring与JMS发送问题解决了,但是还有一个潜在的问题,就是如果发送的消息类型或逻辑攺变了,我们不得不重写MessageCreatorBean这个类,当然,这就引起了重编译部署的问题,怎么能不编译就可以攺变业务逻辑呢?我想到了Spring 2.0的新特性,对脚本语言的支持,Spring 2.0现在支持三种脚本语言:BSH、JRuby、JGroovy。这三种脚本语言使用起来大同小异,我选择了语法更贴近Java的BSH。过程如下:
先编写一个接口
package
com.bea.de.scripting;
import
java.util.Map;
…..

public
interface
MessageCreatorBean
extends
MessageCreator
...
{
publicvoidsetMedia(Mediamsg);
publicMediagetMedia();
publicvoidsetProperties(Mapmap);
publicMapgetProperties();
publicMessagecreateMessage(Sessionsession)throwsJMSException;
publicvoidmergeProperties(Propertiesprops);
}
然后再写一个实现类
文件名MessageCreatorBean.bsh
Mediamedia;
Mapproperties;
Stringtext;

public
void
setText(Stringstr)
...
{
text=str;
}

public
StringgetText()
...
{
returntext;
}

public
void
setMedia(Mediam)
...
{
media=m;
}

public
MediagetMedia()
...
{
returnmedia;
}

public
void
setProperties(Mapmap)
...
{
properties=map;
}

public
MapgetProperties()
...
{
returnproperties;
}

private
createBinaryMessage(Sessionsession)
throws
JMSException
...
{
BytesMessagemsg=null;
byte[]bytes=null;

try...{
bytes=media.getByteData();
}

catch(IllegalStateExceptionise)...{

try...{
InputStreamis=media.getStreamData();
ByteArrayOutputStreambaos=newByteArrayOutputStream();
byte[]buf=newbyte[1000];
intbyteread=0;

while((byteread=is.read(buf))!=-1)...{
baos.write(buf,0,byteread);
}
bytes=baos.toByteArray();
}

catch(IOExceptionio)...{
}
}
msg=session.createBytesMessage();
msg.writeBytes(bytes);
properties.put("m_name",media.getName());
properties.put("m_format",media.getFormat());
properties.put("m_ctype",media.getContentType());
returnmsg;
}

private
MessagecreateTextMessage(Sessionsession)
throws
JMSException
...
{
Messagemsg=session.createTextMessage(text);
properties.put("m_name",(newDate()).getTime()+".xml");
properties.put("m_format","xml");
properties.put("m_ctype","text/xml");
returnmsg;
}

public
MessagecreateMessage(Sessionsession)
throws
JMSException
...
{
Messagemsg=null;
if(properties==null)properties=newProperties();

if(media==null)...{
msg=createTextMessage(session);
}

else...{
msg=createBinaryMessage(session);
}
applyProperties(msg);
returnmsg;
}

public
void
mergeProperties(Propertiesprops)
...
{

if(properties==null)...{
properties=newProperties();
}

if(props!=null)...{
Setkeys=props.keySet();

for(Iteratorit=keys.iterator();it.hasNext();)...{
Stringkey=(String)it.next();
properties.put(key,props.get(key));
}
}
}

private
void
applyProperties(Messagemsg)
throws
JMSException
...
{

if(properties!=null)...{

for(Objects:properties.keySet())...{
msg.setStringProperty((String)s,(String)properties.get(s));
}
}
}
最后编写配置文件
<?
xmlversion="1.0"encoding="UTF-8"
?>
<
beans
xmlns
="http://www.springframework.org/schema/beans"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop
="http://www.springframework.org/schema/aop"
xmlns:tx
="http://www.springframework.org/schema/tx"
xmlns:util
="http://www.springframework.org/schema/util"
xmlns:lang
="http://www.springframework.org/schema/lang"
xsi:schemaLocation
="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/langhttp://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-2.0.xsd"
>
….
<
lang:bsh
id
="normalMessageCreator"
script-source
="classpath:bsh/MessageCreatorBean.bsh"
script-interfaces
="com.bea.de.scripting.MessageCreatorBean"
refresh-check-delay
="5000"
/>
<beans xmlns="http://www.springframework.org/schema/beans"><bsh id="normalMessageCreator"><br> <strong>解释:</strong> <br> <strong>script-source:</strong>具体业务逻辑的的脚本实现文件,当系统上线后,如果我们想修攺业务逻辑,只需修攺这个脚本就可以了,无需重编译类文件。 <br> <strong>script-interface:</strong>业务接口,这个接口文件一定要在前期定好,不然如果要对接口修攺,就要重编译了。如果使用JGroovy就无需这个参数了。 <br> <strong>refresh-check-delay:</strong>引擎每隔多长时间检查脚本状态,如果脚本被攺动就会自动编译。 <br> <strong>第四次重构—Spring JMS接收</strong> <br> 以往我们在实现JMS消息的接收时,往往是通过(MDB-消息EJB)或启用一个后台进程,等待JMS消息进行处理,代码量和复杂度都非常高,因此,我想到了Spring对JMS Container的支持。也就是说,由Spring监控消息以及维护消息处理Bean。实现如下: <br><bsh id="normalMessageListener"><bean id="normalMessageListenerContainer"><property name="concurrentConsumers" value="5"><property name="connectionFactory" ref="connectionFactory"><property name="destination" ref="receiveNormalQueue"><property name="messageListener" ref="normalMessageListener"></property></property></property></property></bean></bsh></bsh></beans>
….
<
lang:bsh
id
="normalMessageListener"
script-source
="classpath:bsh/DiskMessageListenerBean.bsh"
script-interfaces
="com.bea.de.scripting.DiskMessageListenerBean"
refresh-check-delay
="5000"
>
<
lang:property
name
="basePath"
value
="${jms.listener.disk.normal}"
/>
</
lang:bsh
>

<
bean
id
="normalMessageListenerContainer"
class
="org.springframework.jms.listener.DefaultMessageListenerContainer"
>
<
property
name
="concurrentConsumers"
value
="5"
/>
<
property
name
="connectionFactory"
ref
="connectionFactory"
/>
<
property
name
="destination"
ref
="receiveNormalQueue"
/>
<
property
name
="messageListener"
ref
="normalMessageListener"
/>
</
bean
>
normalMessageListener:是一个实现了javax.jms. MessageListener接口的Bean,用来处理消息处理逻辑,我们可以看到,为了维护的方便,此处,我还是使用了BSH。
normalMessageListenerContainer:是一个用来维护消息处理Bean的容器。
第五次重构—Spring JMS消息Pooling机制
经过一系列的大手术,基本上完成了客户所需要的功能,但这时客户有了新的想法:客户的外部系统定期生成数据(以文件方式写入文件目录 ),然后,由信息平台将数据传出。当时第一想法就是使用Quartz,虽然Quartz功能强大,但总觉得其非出生名门,所以最终采用了JDK Timer支持,结合Spring的强大功能,实现了此功能。
代码如下:
<bsh id="poolingMessageExecutor"><br></bsh>
<
lang:bsh
id
="poolingMessageExecutor"
script-source
="classpath:bsh/PoolingMessageTimerTaskBean.bsh"
script-interfaces
="com.bea.de.scripting.MessageTaskExecutor"
refresh-check-delay
="5000"
>
<
lang:property
name
="jmsTemplate"
ref
="jmsTemplate"
/>
<
lang:property
name
="messageProperties"
ref
="messageProperties"
/>
<
lang:property
name
="targetQueue"
ref
="binaryQueue"
/>
<
lang:property
name
="basePath"
value
="${jms.pooling.disk}"
/>
<
lang:property
name
="messageCreator"
ref
="binaryMessageCreator"
/>
</
lang:bsh
>

<
bean
id
="poolingMessageTimerTask"
class
="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean"
>
<
property
name
="targetObject"
ref
="poolingMessageExecutor"
/>
<
property
name
="targetMethod"
value
="execute"
/>
</
bean
>

<
bean
id
="scheduledPoolingMessageTask"
class
="org.springframework.scheduling.timer.ScheduledTimerTask"
>
<
property
name
="delay"
value
="10000"
/>
<
property
name
="period"
value
="50000"
/>
<
property
name
="timerTask"
ref
="poolingMessageTimerTask"
/>
</
bean
>

<
bean
id
="scheduler"
class
="org.springframework.scheduling.timer.TimerFactoryBean"
>
<
property
name
="scheduledTimerTasks"
>
<
list
>
<
ref
bean
="scheduledPoolingMessageTask"
/>
</
list
>
</
property
>
</
bean
>
<property name="jmsTemplate" ref="jmsTemplate"><property name="messageProperties" ref="messageProperties"><property name="targetQueue" ref="binaryQueue"><property name="basePath" value="${jms.pooling.disk}"><bean id="poolingMessageTimerTask"><property name="targetObject" ref="poolingMessageExecutor"><bean id="scheduledPoolingMessageTask"><property name="delay" value="10000"><property name="period" value="50000"><bean id="scheduler"><br> <br> <strong>解释:</strong> <br> <strong>poolingMessageExecutor</strong>:是一个纯的POJO对象(这也是我选此方式的一个很大原因),当然,具体的逻辑还是由BSH完成。 <br> <strong>poolingMessageTimerTask:</strong>此对象用来指明任务执行器的哪个函数进行具体的任务处理。 <br> <strong>scheduledPoolingMessageTask:</strong>配置任务调度信息,如延时、时间间隔 <br> <strong>scheduler:</strong>调度触发器 <br> <strong>第五次重构—ZK代码精减</strong> <br> 至此,全部功能实现完毕,由于Spring与ZK的出色表现,具然提供完成了任务,但回过头来看自己的代码,虽然有了Spring的帮助,但页面中的代码还是显得有些臃肿,因此,决定再次调整。 <br> <strong>第一步:</strong> <br> 调整的第一步就是把共性功能进行包装,然后将这些封装后的代码做成库的型式。例: <br> 生成库文件:common.zs <br> <br></bean></property></property></bean></property></bean></property></property></property></property>
…
import
org.springframework.web.context.WebApplicationContext;
import
com.bea.de.scripting.DiskMessageListenerBean;

void
send(Mediamedia,Propertiesprops)
...
{
……..
jt.send(queue,mc);
}

void
send(Stringstr,Propertiesprops)
...
{
……
jt.send(queue,mc);
}
其它ZUL页面调用时只需如下方式:
….
<
zscriptsrc
=
"
/lib/common.zs
"
/>
….
<zscript src="/lib/common.zs"> <strong>第二步:ZK组件化</strong> <br> 通过分析,我发现有很多功能类型的页面,例如:由于发送消息的类型不同(二进制、文件等),所以我采用了不同的页面实现消息发送,但实际上有很多功能是类似的,为什么我们不同将这些功能模块化呢?说干就干,我为消息发送制做了一个发送组件:Sender.zul,此页面与其它页面没有什么不同,只是它可以接收参数,例如:如果我们想使用调用者传来的desc参数,就使用${arg.desc}。 <br> 代码如下: <br> <strong>文件名:Sender.zul</strong> <br></zscript>
<?
xmlversion
=
"
1.0
"
encoding
=
"
UTF-8
"
?>
<
vbox
>
<
groupboxmold
=
"
3d
"
width
=
"
800px
"
>
<
captionlabel
=
"
控制面板
"
></
caption
>

<
windowwidth
=
"
100%
"
>
<
zscriptsrc
=
"
/lib/common.zs
"
/>
<
zscript
>


org.zkoss.util.media.Mediamedia
=
null
;
boolean
isText
=
true
;


void
doUpload()
...
{

media=Fileupload.get();

if(media==null)return;


if(media.getFormat().equals("txt")||media.getFormat().equals("xml"))...{

Stringcontent=newString(media.getByteData());
msgTextbox.value=content;
isText=true;
}

else...{
isText=false;
msgTextbox.value="上传文件名-->"+media.getName();
}

msgTextbox.disabled=true;
}


void
doSend()
...
{

Stringcontent=msgTextbox.value.trim();
Propertiesprops=newProperties();



if(msgTypeRadiogroup.selectedItem.value.equals("P2P"))...{


if(hospitalListbox.selectedItem==null)...{

Messagebox.show("请选择医院!");

hospitalListbox.focus();
return;
}

Setsel=hospitalListbox.getSelectedItems();

StringBufferbuf=newStringBuffer();


for(Listitemitem:sel)...{

buf.append(item.getValue()).append("|");
}

Stringtmp=buf.toString();
Stringhospitals=tmp.substring(0,tmp.length()-1);

props.put("MessageFor","P2P");
props.put("MessageTarget",hospitals);
}

else...{

if(diseaseListbox.selectedItem==null)...{

Messagebox.show("请选择疾病类型!");
diseaseListbox.focus();
return;
}
props.put("MessageFor","Report");
props.put("MessageTarget",diseaseListbox.selectedItem.value);
}


if(content==null||content.equals(""))...{

Messagebox.show("请输入消息内容!");
}

else...{

if(routingTypeRadiogroup.selectedItem.value.equals("BodyRouting"))...{


if(!isText)...{

Messagebox.show("不能基于流体文件路由,请选择-消息头路由-方式!!");
msgTextbox.focus();
return;
}

else...{

send(content,props);
}
}

elseif(media!=null)...{

send(media,props);
}

else...{

media=neworg.zkoss.util.media.AMedia((newDate()).getTime()+".xml","xml","text/xml",content.getBytes());
send(media,props);
}
Messagebox.show("发送成功");
msgTextbox.focus();
}

msgTextbox.disabled=false;
}


void
doClear()
...
{

msgTextbox.value="";
msgTextbox.disabled=true;
media=null;
msgTextbox.focus();
}

</
zscript
>
<
grid
>
<
rows
>
<
row
>
<
labelvalue
=
"
文件路径
"
/>
<
hbox
>
<
textbox
/>
<
buttonlabel
=
"
上传文件
"
onClick
=
"
doUpload()
"
/>
</
hbox
>
</
row
>
<
row
>
<
labelvalue
=
"
路由类型
"
/>
<
radiogroupid
=
"
routingTypeRadiogroup
"
>

<
radiolabel
=
"
消息头路由
"
value
=
"
HeadRouting
"
checked
=
"
true
"
/>
<
radiolabel
=
"
消息体路由
"
value
=
"
BodyRouting
"
/>
</
radiogroup
>
</
row
>
<
row
>
<
labelvalue
=
"
消息内容
"
/>
<
textboxid
=
"
msgTextbox
"
cols
=
"
80
"
multiline
=
"
true
"
rows
=
"
20
"
value
=
"
${arg.content}
"
/>
</
row
>
<
row
>
<
labelvalue
=
"
消息类型
"
/>
<
radiogroupid
=
"
msgTypeRadiogroup
"
>

<
radiolabel
=
"
点对点
"
value
=
"
P2P
"
checked
=
"
true
"
onCheck
=
"
p2pRow.visible=true;reportRow.visible=false;
"
/>
<
radiolabel
=
"
上报数据
"
value
=
"
Report
"
onCheck
=
"
p2pRow.visible=false;reportRow.visible=true
"
/>
</
radiogroup
>
</
row
>

<
rowid
=
"
p2pRow
"
>
<
labelvalue
=
"
XX
"
/>
<
zscript
>
ListModelhospitalModel
=
getListModel(
"
P2P
"
);
</
zscript
>
<
listboxcheckmark
=
"
true
"
multiple
=
"
true
"
width
=
"
200px
"
id
=
"
hospitalListbox
"
itemRenderer
=
"
com.bea.de.ui.MapListItemRender
"
model
=
"
${hospitalModel}
"
>
<
listhead
>
<
listheaderlabel
=
"
XX名称
"
/>
</
listhead
>
</
listbox
>
</
row
>
<
rowid
=
"
reportRow
"
visible
=
"
false
"
>
<
labelvalue
=
"
XX类型
"
/>
<
bandboxid
=
"
bd1
"
>
<
bandpopup
>
<
zscript
>
ListModeldiseaseModel
=
getListModel(
"
Report
"
);
</
zscript
>
<
listboxwidth
=
"
200px
"
id
=
"
diseaseListbox
"
onSelect
=
"
bd1.value=self.selectedItem.label;bd1.closeDropdown();
"
itemRenderer
=
"
com.bea.de.ui.MapListItemRender
"
model
=
"
${diseaseModel}
"
>
<
listhead
>
<
listheaderlabel
=
"
XX类型名称
"
/>
</
listhead
>
</
listbox
>
</
bandpopup
>
</
bandbox
>
</
row
>
<
row
>

<
labelvalue
=
"
操作
"
/>
<
hbox
>
<
buttonlabel
=
"
发送
"
onClick
=
"
doSend();
"
/>

<
buttonlabel
=
"
清空
"
onClick
=
"
doClear();
"
/>

</
hbox
>
</
row
>
</
rows
>
</
grid
>
</
window
>
</
groupbox
>

<
groupboxmold
=
"
3d
"
open
=
"
false
"
width
=
"
800px
"
>
<
captionlabel
=
"
功能说明
"
></
caption
>
<
windowborder
=
"
normal
"
width
=
"
100%
"
>
<
includesrc
=
"
${arg.desc}
"
/>
<!
—这里就是接收参数的地方
-->
</
window
>
</
groupbox
>
</
vbox
>
<vbox><groupbox mold="3d" width="800px"><window width="100%"><zscript src="/lib/common.zs"><grid><rows><row><label value="文件路径"><row><label value="路由类型"><radiogroup id="routingTypeRadiogroup"><row><label value="消息内容"><br> <br> <strong>组件调用方代码如下:</strong> <br> <br></label></row></radiogroup></label></row></label></row></rows></grid></zscript></window></groupbox></vbox>
<?
xmlversion="1.0"encoding="UTF-8"
?>
<?
componentname="sender"macro-uri="/macros/Sender.zul"
?>
<
window
>
<
sender
>
<
attribute
name
="content"
>
<![CDATA[
xxxxx
]]>
</
attribute
>
<
attribute
name
="desc"
>
<![CDATA[
/descs/Sender.xhtml
]]>
</
attribute
>
</
sender
>
</
window
>
<window><br> </window>
大功告成,看看成果!!
后记:
在刚接触ZK时,一般使用Zero Kode开发ZUL页面,就在2006年10月9日,Zero Kode终于投入了eZing Builder的怀抱,这下子Eclipse的fans有救了。
其实,还有很多地方还可以进一步的优化(自定义页面组件、数据验证等),但由于时间关系,也只能做到这一步,今后有机会一定再和大家分享。
参考资料:
ZK官方网站:http://www.zkoss.org/
Springframework官方网站:http://www.springframework.org
eZing Builder官方网站http://ezingbuilder.sourceforge.net/cms/
Springframework在线文档:
ZK在线文档:
ZK在线演示:
Contact List with ZK and Spring
Painting with Zero Kode: A real-time web page designer based on ZK, Part I
本文转自
http://dev2dev.bea.com.cn/techdoc/20061127901.html
什么是ZK
利用ZK框架设计的web应用程序具备丰富的胖客户端特性和简单的设计模型。ZK包括一个基于AJAX可自动进行交互式操作的事件驱动引擎和一套兼容XUL的组件。利用直观的事件驱动模型,你可以用具有XUL特性的组件来表示你的应用程序并通过由用户触发的监听事件来操作这些组件,就像开发桌面应用程序一样简单。
先来点直观的感受:http://www.zkoss.org/zkdemo/userguide/
什么是Springframework 2.0
大名鼎鼎的Springframework相信没有人不知道吧,就在不久前,Interface21又推出了Spring 2.0版本,Spring2.0的发布恐怕算得上2006年Java社区的一件大事了。Spring2.0中一些新的特性,如:基于XML Schema语法的配置、JPA的支持、支持动态语言、异步JMS等诸多功能都非常不错,总体来说,Spring2.0将向未来的宏大目标又迈进了一大步。
动机
因为工作需要,要写一个基于Web的JMS消息发送程序,当然,这对于技术人员来说,是小菜一碟,现实问题摆在面前,一是时间紧,二是由于客户对技术方面一般,所以GUI的美观程度至关重要,怎么办呢?思前想后,决定使用Ajax技术,但我们也知道,如今Ajax的框架多如牛毛,我该选择哪一个呢,无意中,通过Google找到了ZK。在看完她的在线演示后,被她华丽的外观,简洁实现方式所吸引。于是,就这样,我一边看着ZK技术手册,一边上路了。
第一次重构 — Spring登场
ZK作为表现层技术,一般通过两种手段与业务层交互,一种方式是只使用ZK做表现层,当页面提交后,再由用户指定的servlet来处理具体的业务逻辑,另一种方式是通过象.NET的WebForm一样基于事件响应方式编程。例如:
<window title="event listener demo" border="normal" width="200px"><br> <label id="mylabel" value="Hello, World!"> <br> <button label="Change label"> <br> <attribute name="onClick"><br> mylabel.value = "Hello, Event!" <br> </attribute><br> </button> <br> </label></window>
很明显,使用第二种方式会更加简单,更加容易理解,但问题也随之产生,因为每个事件处理都要使用大量类信息,随着业务逻辑的复杂性增加,每个ZUL(ZK页面)也会变得相当的臃肿,怎么办呢?当然是使用Spring!!原因有二:一是可以将大量业务逻辑代码封装成bean,减少表现层的复杂性,另一个好处是,由于业务场景需要处理JMS要关内容,通过Spring2.0对JMS强大的支持功能,也可以大大减少工作量。说干就干,通过研究尝试,我发现在ZK中可以通过如下方式访问Spring的上下文:
…..
...
{
importorg.springframework.web.context.WebApplicationContext;
importorg.springframework.web.context.support.WebApplicationContextUtils;
WebApplicationContextctx=(WebApplicationContext)Executions.getCurrent().getDesktop().getWebApp().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
…
这样就如鱼得水了,我可以任意使用用Spring管理的Bean了。
第二次重构 — Spring JMS发送
我们知道在Spring中处理JMS的发送一般来讲是通过配置的方式得到JmsTemplate,然后当要发送消息时,我们再创建一个匿名类,如下:
….
this
.jmsTemplate.send(
this
.queue,
new
MessageCreator()
...
{
publicMessagecreateMessage(Sessionsession)throwsJMSException...{
returnsession.createTextMessage("helloqueueworld");
}
}
);
…
通过分析,很显然,使用匿名类的原因就在于,只有在消息发送这一时刻才能决定发送什么类型的消息以及消息内容是什么,知道了这一点,其实我们可以写一个工具Bean类,来封装这个逻辑,来避免这个繁琐的过程,代码如下:
MessageCreatorBean.java
package
bean;
import
java.io.ByteArrayOutputStream;
import
java.io.File;
import
java.io.InputStream;
import
java.util.Date;
import
java.util.HashMap;
import
java.util.Iterator;
import
java.util.Map;
import
java.util.Properties;
import
java.util.Set;
import
java.util.Map.Entry;
import
javax.jms.BytesMessage;
import
javax.jms.JMSException;
import
javax.jms.Message;
import
javax.jms.Session;
import
org.apache.commons.logging.Log;
import
org.apache.commons.logging.LogFactory;
import
org.springframework.jms.core.MessageCreator;
import
org.zkoss.util.media.Media;
public
class
MessageCreatorBean
implements
MessageCreator
...
{
privateMediamedia;
privateMapproperties;
privateStringtext;
publicvoidsetText(Stringstr)...{
text=str;
}
publicStringgetText()...{
returntext;
}
publicvoidsetMedia(Mediam)...{
media=m;
}
publicMediagetMedia()...{
returnmedia;
}
publicvoidsetProperties(Mapmap)...{
properties=map;
}
publicMapgetProperties()...{
returnproperties;
}
privatecreateBinaryMessage(Sessionsession)throwsJMSException...{
BytesMessagemsg=null;
byte[]bytes=null;
try...{
bytes=media.getByteData();
}
catch(IllegalStateExceptionise)...{

try...{
InputStreamis=media.getStreamData();
ByteArrayOutputStreambaos=newByteArrayOutputStream();
byte[]buf=newbyte[1000];
intbyteread=0;
while((byteread=is.read(buf))!=-1)...{
baos.write(buf,0,byteread);
}
bytes=baos.toByteArray();
}
catch(IOExceptionio)...{
}
}
msg=session.createBytesMessage();
msg.writeBytes(bytes);
properties.put("m_name",media.getName());
properties.put("m_format",media.getFormat());
properties.put("m_ctype",media.getContentType());
returnmsg;
}
privateMessagecreateTextMessage(Sessionsession)throwsJMSException...{
Messagemsg=session.createTextMessage(text);
properties.put("m_name",(newDate()).getTime()+".xml");
properties.put("m_format","xml");
properties.put("m_ctype","text/xml");
returnmsg;
}
publicMessagecreateMessage(Sessionsession)throwsJMSException...{
Messagemsg=null;
if(properties==null)properties=newProperties();
if(media==null)...{
msg=createTextMessage(session);
}
else...{
msg=createBinaryMessage(session);
}
applyProperties(msg);
returnmsg;
}
publicvoidmergeProperties(Propertiesprops)...{

if(properties==null)...{
properties=newProperties();
}
if(props!=null)...{
Setkeys=props.keySet();

for(Iteratorit=keys.iterator();it.hasNext();)...{
Stringkey=(String)it.next();
properties.put(key,props.get(key));
}
}
}
privatevoidapplyProperties(Messagemsg)throwsJMSException...{
if(properties!=null)...{
for(Objects:properties.keySet())...{
msg.setStringProperty((String)s,(String)properties.get(s));
}
}
}
}
配置Springframework Context:
<bean id="normalMessageCreator"><br></bean>
class="com.bea.de.bean.
<
class
="com.bea.de.bean.MessageCreatorBean"
/>
使用的时候我们就可以通过Spring来访问了。
…
void
send(Mediamedia,Propertiesprops)
...
{
WebApplicationContextctx=(WebApplicationContext)Executions.getCurrent().getDesktop().getWebApp().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
JmsTemplatejt=(JmsTemplate)ctx.getBean("jmsTemplate");
Queuequeue=(Queue)ctx.getBean("binaryQueue");
MessageCreatorBeanmc=(MessageCreatorBean)ctx.getBean("binaryMessageCreator");
Propertiesp=(Properties)ctx.getBean("messageProperties");
mc.mergeProperties(p);
mc.mergeProperties(props);
mc.setMedia(media);
jt.send(queue,mc);
}
…
第三次重构—BSH(BeanShell)登场
虽然Spring与JMS发送问题解决了,但是还有一个潜在的问题,就是如果发送的消息类型或逻辑攺变了,我们不得不重写MessageCreatorBean这个类,当然,这就引起了重编译部署的问题,怎么能不编译就可以攺变业务逻辑呢?我想到了Spring 2.0的新特性,对脚本语言的支持,Spring 2.0现在支持三种脚本语言:BSH、JRuby、JGroovy。这三种脚本语言使用起来大同小异,我选择了语法更贴近Java的BSH。过程如下:
先编写一个接口
package
com.bea.de.scripting;
import
java.util.Map;
…..
public
interface
MessageCreatorBean
extends
MessageCreator
...
{
publicvoidsetMedia(Mediamsg);
publicMediagetMedia();
publicvoidsetProperties(Mapmap);
publicMapgetProperties();
publicMessagecreateMessage(Sessionsession)throwsJMSException;
publicvoidmergeProperties(Propertiesprops);
}
然后再写一个实现类
文件名MessageCreatorBean.bsh
Mediamedia;
Mapproperties;
Stringtext;
public
void
setText(Stringstr)
...
{
text=str;
}

public
StringgetText()
...
{
returntext;
}

public
void
setMedia(Mediam)
...
{
media=m;
}

public
MediagetMedia()
...
{
returnmedia;
}

public
void
setProperties(Mapmap)
...
{
properties=map;
}

public
MapgetProperties()
...
{
returnproperties;
}

private
createBinaryMessage(Sessionsession)
throws
JMSException
...
{
BytesMessagemsg=null;
byte[]bytes=null;
try...{
bytes=media.getByteData();
}
catch(IllegalStateExceptionise)...{

try...{
InputStreamis=media.getStreamData();
ByteArrayOutputStreambaos=newByteArrayOutputStream();
byte[]buf=newbyte[1000];
intbyteread=0;
while((byteread=is.read(buf))!=-1)...{
baos.write(buf,0,byteread);
}
bytes=baos.toByteArray();
}
catch(IOExceptionio)...{
}
}
msg=session.createBytesMessage();
msg.writeBytes(bytes);
properties.put("m_name",media.getName());
properties.put("m_format",media.getFormat());
properties.put("m_ctype",media.getContentType());
returnmsg;
}

private
MessagecreateTextMessage(Sessionsession)
throws
JMSException
...
{
Messagemsg=session.createTextMessage(text);
properties.put("m_name",(newDate()).getTime()+".xml");
properties.put("m_format","xml");
properties.put("m_ctype","text/xml");
returnmsg;
}

public
MessagecreateMessage(Sessionsession)
throws
JMSException
...
{
Messagemsg=null;
if(properties==null)properties=newProperties();
if(media==null)...{
msg=createTextMessage(session);
}
else...{
msg=createBinaryMessage(session);
}
applyProperties(msg);
returnmsg;
}

public
void
mergeProperties(Propertiesprops)
...
{

if(properties==null)...{
properties=newProperties();
}

if(props!=null)...{
Setkeys=props.keySet();

for(Iteratorit=keys.iterator();it.hasNext();)...{
Stringkey=(String)it.next();
properties.put(key,props.get(key));
}
}
}

private
void
applyProperties(Messagemsg)
throws
JMSException
...
{
if(properties!=null)...{
for(Objects:properties.keySet())...{
msg.setStringProperty((String)s,(String)properties.get(s));
}
}
}
最后编写配置文件
<?
xmlversion="1.0"encoding="UTF-8"
?>
<
beans
xmlns
="http://www.springframework.org/schema/beans"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop
="http://www.springframework.org/schema/aop"
xmlns:tx
="http://www.springframework.org/schema/tx"
xmlns:util
="http://www.springframework.org/schema/util"
xmlns:lang
="http://www.springframework.org/schema/lang"
xsi:schemaLocation
="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/langhttp://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-2.0.xsd"
>
….
<
lang:bsh
id
="normalMessageCreator"
script-source
="classpath:bsh/MessageCreatorBean.bsh"
script-interfaces
="com.bea.de.scripting.MessageCreatorBean"
refresh-check-delay
="5000"
/>
….
<
lang:bsh
id
="normalMessageListener"
script-source
="classpath:bsh/DiskMessageListenerBean.bsh"
script-interfaces
="com.bea.de.scripting.DiskMessageListenerBean"
refresh-check-delay
="5000"
>
<
lang:property
name
="basePath"
value
="${jms.listener.disk.normal}"
/>
</
lang:bsh
>

<
bean
id
="normalMessageListenerContainer"
class
="org.springframework.jms.listener.DefaultMessageListenerContainer"
>
<
property
name
="concurrentConsumers"
value
="5"
/>
<
property
name
="connectionFactory"
ref
="connectionFactory"
/>
<
property
name
="destination"
ref
="receiveNormalQueue"
/>
<
property
name
="messageListener"
ref
="normalMessageListener"
/>
</
bean
>
normalMessageListener:是一个实现了javax.jms. MessageListener接口的Bean,用来处理消息处理逻辑,我们可以看到,为了维护的方便,此处,我还是使用了BSH。
normalMessageListenerContainer:是一个用来维护消息处理Bean的容器。
第五次重构—Spring JMS消息Pooling机制
经过一系列的大手术,基本上完成了客户所需要的功能,但这时客户有了新的想法:客户的外部系统定期生成数据(以文件方式写入文件目录 ),然后,由信息平台将数据传出。当时第一想法就是使用Quartz,虽然Quartz功能强大,但总觉得其非出生名门,所以最终采用了JDK Timer支持,结合Spring的强大功能,实现了此功能。
代码如下:
<bsh id="poolingMessageExecutor"><br></bsh>
<
lang:bsh
id
="poolingMessageExecutor"
script-source
="classpath:bsh/PoolingMessageTimerTaskBean.bsh"
script-interfaces
="com.bea.de.scripting.MessageTaskExecutor"
refresh-check-delay
="5000"
>
<
lang:property
name
="jmsTemplate"
ref
="jmsTemplate"
/>
<
lang:property
name
="messageProperties"
ref
="messageProperties"
/>
<
lang:property
name
="targetQueue"
ref
="binaryQueue"
/>
<
lang:property
name
="basePath"
value
="${jms.pooling.disk}"
/>
<
lang:property
name
="messageCreator"
ref
="binaryMessageCreator"
/>
</
lang:bsh
>

<
bean
id
="poolingMessageTimerTask"
class
="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean"
>
<
property
name
="targetObject"
ref
="poolingMessageExecutor"
/>
<
property
name
="targetMethod"
value
="execute"
/>
</
bean
>

<
bean
id
="scheduledPoolingMessageTask"
class
="org.springframework.scheduling.timer.ScheduledTimerTask"
>
<
property
name
="delay"
value
="10000"
/>
<
property
name
="period"
value
="50000"
/>
<
property
name
="timerTask"
ref
="poolingMessageTimerTask"
/>
</
bean
>

<
bean
id
="scheduler"
class
="org.springframework.scheduling.timer.TimerFactoryBean"
>
<
property
name
="scheduledTimerTasks"
>
<
list
>
<
ref
bean
="scheduledPoolingMessageTask"
/>
</
list
>
</
property
>
</
bean
>
…
import
org.springframework.web.context.WebApplicationContext;
import
com.bea.de.scripting.DiskMessageListenerBean;
void
send(Mediamedia,Propertiesprops)
...
{
……..
jt.send(queue,mc);
}

void
send(Stringstr,Propertiesprops)
...
{
……
jt.send(queue,mc);
}
其它ZUL页面调用时只需如下方式:
….
<
zscriptsrc
=
"
/lib/common.zs
"
/>
….
<zscript src="/lib/common.zs"> <strong>第二步:ZK组件化</strong> <br> 通过分析,我发现有很多功能类型的页面,例如:由于发送消息的类型不同(二进制、文件等),所以我采用了不同的页面实现消息发送,但实际上有很多功能是类似的,为什么我们不同将这些功能模块化呢?说干就干,我为消息发送制做了一个发送组件:Sender.zul,此页面与其它页面没有什么不同,只是它可以接收参数,例如:如果我们想使用调用者传来的desc参数,就使用${arg.desc}。 <br> 代码如下: <br> <strong>文件名:Sender.zul</strong> <br></zscript>
<?
xmlversion
=
"
1.0
"
encoding
=
"
UTF-8
"
?>
<
vbox
>
<
groupboxmold
=
"
3d
"
width
=
"
800px
"
>
<
captionlabel
=
"
控制面板
"
></
caption
>

<
windowwidth
=
"
100%
"
>
<
zscriptsrc
=
"
/lib/common.zs
"
/>
<
zscript
>


org.zkoss.util.media.Mediamedia
=
null
;
boolean
isText
=
true
;

void
doUpload()
...
{
media=Fileupload.get();
if(media==null)return;

if(media.getFormat().equals("txt")||media.getFormat().equals("xml"))...{
Stringcontent=newString(media.getByteData());
msgTextbox.value=content;
isText=true;
}
else...{
isText=false;
msgTextbox.value="上传文件名-->"+media.getName();
}
msgTextbox.disabled=true;
}


void
doSend()
...
{
Stringcontent=msgTextbox.value.trim();
Propertiesprops=newProperties();


if(msgTypeRadiogroup.selectedItem.value.equals("P2P"))...{

if(hospitalListbox.selectedItem==null)...{
Messagebox.show("请选择医院!");
hospitalListbox.focus();
return;
}
Setsel=hospitalListbox.getSelectedItems();
StringBufferbuf=newStringBuffer();

for(Listitemitem:sel)...{
buf.append(item.getValue()).append("|");
}
Stringtmp=buf.toString();
Stringhospitals=tmp.substring(0,tmp.length()-1);
props.put("MessageFor","P2P");
props.put("MessageTarget",hospitals);
}
else...{
if(diseaseListbox.selectedItem==null)...{
Messagebox.show("请选择疾病类型!");
diseaseListbox.focus();
return;
}
props.put("MessageFor","Report");
props.put("MessageTarget",diseaseListbox.selectedItem.value);
}

if(content==null||content.equals(""))...{
Messagebox.show("请输入消息内容!");
}
else...{
if(routingTypeRadiogroup.selectedItem.value.equals("BodyRouting"))...{

if(!isText)...{
Messagebox.show("不能基于流体文件路由,请选择-消息头路由-方式!!");
msgTextbox.focus();
return;
}
else...{
send(content,props);
}
}
elseif(media!=null)...{
send(media,props);
}
else...{
media=neworg.zkoss.util.media.AMedia((newDate()).getTime()+".xml","xml","text/xml",content.getBytes());
send(media,props);
}
Messagebox.show("发送成功");
msgTextbox.focus();
}
msgTextbox.disabled=false;
}


void
doClear()
...
{
msgTextbox.value="";
msgTextbox.disabled=true;
media=null;
msgTextbox.focus();
}

</
zscript
>
<
grid
>
<
rows
>
<
row
>
<
labelvalue
=
"
文件路径
"
/>
<
hbox
>
<
textbox
/>
<
buttonlabel
=
"
上传文件
"
onClick
=
"
doUpload()
"
/>
</
hbox
>
</
row
>
<
row
>
<
labelvalue
=
"
路由类型
"
/>
<
radiogroupid
=
"
routingTypeRadiogroup
"
>

<
radiolabel
=
"
消息头路由
"
value
=
"
HeadRouting
"
checked
=
"
true
"
/>
<
radiolabel
=
"
消息体路由
"
value
=
"
BodyRouting
"
/>
</
radiogroup
>
</
row
>
<
row
>
<
labelvalue
=
"
消息内容
"
/>
<
textboxid
=
"
msgTextbox
"
cols
=
"
80
"
multiline
=
"
true
"
rows
=
"
20
"
value
=
"
${arg.content}
"
/>
</
row
>
<
row
>
<
labelvalue
=
"
消息类型
"
/>
<
radiogroupid
=
"
msgTypeRadiogroup
"
>

<
radiolabel
=
"
点对点
"
value
=
"
P2P
"
checked
=
"
true
"
onCheck
=
"
p2pRow.visible=true;reportRow.visible=false;
"
/>
<
radiolabel
=
"
上报数据
"
value
=
"
Report
"
onCheck
=
"
p2pRow.visible=false;reportRow.visible=true
"
/>
</
radiogroup
>
</
row
>

<
rowid
=
"
p2pRow
"
>
<
labelvalue
=
"
XX
"
/>
<
zscript
>
ListModelhospitalModel
=
getListModel(
"
P2P
"
);
</
zscript
>
<
listboxcheckmark
=
"
true
"
multiple
=
"
true
"
width
=
"
200px
"
id
=
"
hospitalListbox
"
itemRenderer
=
"
com.bea.de.ui.MapListItemRender
"
model
=
"
${hospitalModel}
"
>
<
listhead
>
<
listheaderlabel
=
"
XX名称
"
/>
</
listhead
>
</
listbox
>
</
row
>
<
rowid
=
"
reportRow
"
visible
=
"
false
"
>
<
labelvalue
=
"
XX类型
"
/>
<
bandboxid
=
"
bd1
"
>
<
bandpopup
>
<
zscript
>
ListModeldiseaseModel
=
getListModel(
"
Report
"
);
</
zscript
>
<
listboxwidth
=
"
200px
"
id
=
"
diseaseListbox
"
onSelect
=
"
bd1.value=self.selectedItem.label;bd1.closeDropdown();
"
itemRenderer
=
"
com.bea.de.ui.MapListItemRender
"
model
=
"
${diseaseModel}
"
>
<
listhead
>
<
listheaderlabel
=
"
XX类型名称
"
/>
</
listhead
>
</
listbox
>
</
bandpopup
>
</
bandbox
>
</
row
>
<
row
>

<
labelvalue
=
"
操作
"
/>
<
hbox
>
<
buttonlabel
=
"
发送
"
onClick
=
"
doSend();
"
/>

<
buttonlabel
=
"
清空
"
onClick
=
"
doClear();
"
/>

</
hbox
>
</
row
>
</
rows
>
</
grid
>
</
window
>
</
groupbox
>

<
groupboxmold
=
"
3d
"
open
=
"
false
"
width
=
"
800px
"
>
<
captionlabel
=
"
功能说明
"
></
caption
>
<
windowborder
=
"
normal
"
width
=
"
100%
"
>
<
includesrc
=
"
${arg.desc}
"
/>
<!
—这里就是接收参数的地方
-->
</
window
>
</
groupbox
>
</
vbox
>
<?
xmlversion="1.0"encoding="UTF-8"
?>
<?
componentname="sender"macro-uri="/macros/Sender.zul"
?>
<
window
>
<
sender
>
<
attribute
name
="content"
>
<![CDATA[
xxxxx
]]>
</
attribute
>
<
attribute
name
="desc"
>
<![CDATA[
/descs/Sender.xhtml
]]>
</
attribute
>
</
sender
>
</
window
>
大功告成,看看成果!!
后记:
在刚接触ZK时,一般使用Zero Kode开发ZUL页面,就在2006年10月9日,Zero Kode终于投入了eZing Builder的怀抱,这下子Eclipse的fans有救了。
其实,还有很多地方还可以进一步的优化(自定义页面组件、数据验证等),但由于时间关系,也只能做到这一步,今后有机会一定再和大家分享。
参考资料:
ZK官方网站:http://www.zkoss.org/
Springframework官方网站:http://www.springframework.org
eZing Builder官方网站http://ezingbuilder.sourceforge.net/cms/
Springframework在线文档:
ZK在线文档:
ZK在线演示:
Contact List with ZK and Spring
Painting with Zero Kode: A real-time web page designer based on ZK, Part I
本文转自
http://dev2dev.bea.com.cn/techdoc/20061127901.html
本文介绍如何使用ZK和Spring框架集成开发Web应用程序,并详细记录了从需求分析到功能实现的全过程,包括Ajax技术的应用、Spring对JMS的支持以及ZK组件化等多个方面的实践。
759

被折叠的 条评论
为什么被折叠?



