WEB开发之旅—站在山脚下
作者:T.WOLF
STURTS+SPRING+[HIBERNET|JPA]+DWR+EXTJS
.$$$有需求就有想法,有想法就有动作,有动作最好就有结果$$$.
最近,总觉得该写些什么,但始终不知道要从何写起,索性今天就毫无章法的随便侃侃,侃侃在IT开发中近期的一些感触,所以……
基本上,大家一致认为IT界的软件开发是吃青春饭的,从侧面用实际行动验证着身体是革命本钱的亘古不变谚论,IT光环下的苦累与现实,非亲身经历者无法体会。
刚步入社会的我一工作就被派去出差了,带头的大哥【简称老大】年龄已过40,带着我们兄弟3人日夜奋战于重庆电信近4个月,每人负责一块,基本上是单打独斗作业方式,我是负责报表的开发及分析系统的升级改造,所以,累是自然的,但我的累和我们老大的累与压力相比,倒也算不了什么,年龄使他可能面临IT的自然淘汰,房贷仍是他的一块心病,再加之家庭、社会的其它方方面面的压力,老大的身影给人以莫名的沉重感,使我不由得自问:IT开发者,你的出路在哪里?
关于此问题,大家可能会有以下几点考虑:1、转管理;2、转销售;3、转创业;4、转培训、教育;5、做技术牛人;6、做资深咨询顾问;7… 何去何从,将漫长的摸索与抉择!
而今,出差在海南电信的我,不由得又想到了这个问题,但我今天要说的主题倒不是:“IT开发者,你的出路在哪里?”,而是目前对IT开发的一些自我认识,很早就有动笔的冲动,但总是不知从何处说起,今天刚刚为开发局方报表新需求找到一条新的路线,觉得应该写些什么,随便侃侃…
作为IT开发者,我觉得应该将其成长分成三个阶段:1、站在山脚下:完成开发任务;2、爬向山顶:掌握技术中不变部分;3、站在山顶:做系统架构或分析。
为何这样说呢?以我为例,简单的阐述下: 2007年7月份毕业于汕头大学计算机网络应用与安全专业,硕士期间开发过门户网站、做过协议测试、入侵检测等项目,毕业了,为了不荒废我那张系统分析师证书,进入广东普信做软件开发,从2007年8月份进入公司到书写本文章止,基本上都是在出差中度过的,出差主要是和unix、oracle、Microstratege及java打交道,这些都是没有选择的余地。在java开发中,更加认识到了技术的庞杂,没有一条主线或既定目标,终究注定要成为技术的奴隶,当耗尽革命本钱之际也即离开IT开发之时,而不幸的是,这种离开将把我们带入暗狱,没有光明,亦没有希望,为了改变这种预定将到来的结局,我们不得不提前准备,做技术的主人,随心所欲的让技术为我们服务,所以,我将我的IT发展定位成上面的3个阶段,但更严格的说是4个阶段,而第4个既定的阶段是转做培训或教育,因为我是带着我所谓的梦想走出校园的,而我的梦想就是积累工程经验、返归教育事业,尽绵薄之力弥补工科教育与社会需求的脱节,希望能给以微弱的灯光指引期待进入该行的莘莘学子。
……
WEB开发之旅—站在山脚下:完成开发任务~~
Web开发,大家可能最熟悉的开发框架组合是SSH,这种架构的确不错,低耦合,高内聚,架构职责清晰,开发及维护工作变得条例化、简单化,我也是采用的这种框架组合模式,但少有不同,SSH中的H被J替代了【Struts+Spring+JPA】。这倒不是我今天所说的重点,大家知道,ajax目前是个炙手可热的家伙,暂不论述它的优劣,但我深信,大家一定很期待能在自己的项目中使用ajax,还有一点,那就是UI,大家也很清楚,对于web开发,其UI功能远低于desktop模式,所以,我也深信,大家一样期待在自己的项目中能够有优美的易用的UI界面。其实,ajax技术使得web应用更像desktop模式了,它已经不再遵循传统的requset-response直等的方式。那么,我们如何才能找到适合或者比较适合自己的开发框架呢?我一直思索着,今天还没有完美的答案,但也有一定的收获,下面做一个简述:
一、框架【Struts|JSF+Spring+HIBERNET|JPA】
这是较为常见及流行的开发框架,它的最大好处便是:解藕、结构清晰、职责明确,编码尤其是变动维护易,减少代码量、提升开发速度等。所以,我们可以选择这样的开发框架。
关于该框架的具体阐述待续,因为它不是我们今天所关注的重点是,而我们今天关注的重点是:完成开发任务,为自己选取一劳永逸的开发框架集合;SSH这样的框架能满足我们的需求吗?不能,至少我是这样认为的。那么我们还缺少什么呢?漂亮易用的界面UI及异步数据存取能力Ajax。
UI及Ajax均可以借助javasscript来完成,但问题是我们具备这样的能力吗?即便是我们具备了这样的能力,重复制造轮子又有意义吗?我们的目标无外乎是用最短的时间、最有效途径完成较高质量软件,那么,我们唯一的选择就是借助于第三方资源,杜绝重复制造轮子…
二、Ajax与界面UI【extjs+dwr】
我们所熟悉的Ajax及UI第三方包应该比较不少,但我们要到底如何抉择呢?我们先来看看下面的罗列陈述:
☆ YUI (http://developer.yahoo.com/yui/)
Yahoo!用户界面库(Yahoo! User Interface Library, YUI)提供一些在开发Web胖客户端时常用到的一些工具和UI控件。工具:拖放(Drag and Drop)操作,连接管理器(XMLHttpRequest),页面特效,浏览器事件(例如鼠标点击和键盘按键)管理。UI控件:自动补全(AutoComple)、日历(Calendar),容器(Container)类控件包括提示(Tooltip)、面板(Panel)、对话框(Dialog)等、菜单(Menu)、TabView、TreeView,Logger。YUI 还包括了在创建简洁,灵活的布局并能够兼容多种浏览器时所需要的CSS资源。
☆ EXTJS (ExtJS http://extjs.com/)
ExtJS(yui-ext)是一组扩展自Yahoo!UI,具有CS风格的Web用户界面组件。主要UI包括:dialog,grid,layout,tabs等。
☆ GWT-Ext (http://code.google.com/p/gwt-ext/)
Java语言:GWT-Ext是一个基于GWT和ExtJs开发的Web界面组件库。组件包括:具有排序、分页和过滤功能的Grid,支持拖放操作的Tree,能够灵活配置的ComboBoxe、Tab Panels、Menus&Toolbars、Dialogs、Forms等等。
☆ GWT (http://code.google.com/webtoolkit/)
Google Web Toolkit (GWT) 是一个Java软件开发框架用于开发类似于Google Maps和Gmail的AJAX应用程序。GWT的设计参考Java AWT包设计,类命名规则、接口设计、事件监听等。你可以用Java编程语言开发你的界面,然后用GWT编译器将Java类转换成适合浏览器执行的JavaScript与HTML。Eclipse开发插件Googlipse。
☆ Dojo(http://dojotoolkit.org/)
Dojo是一个非常强大面向对象,开源的JavaScript工具箱。它为开发Web胖客户端程序提供了一套完整的Widget和一些特效操作。
☆ Prototype(http://www.prototypejs.org/)
Prototype 是一个JavaScript框架,它的目标是使开发动态的web应用变得容易。
☆ Prototype UI(http://www.prototype-ui.com/)
Prototype UI是个基于Prototype (1.6) and Script.aculo.us (1.8)的javascript库。是基于通用基类的UI组件,这些组件可以很容易的各种web应用。( It's a library of User Interface components, based on a common fundation classes, which could be easily used by various web applications.)
☆ jQuery (http://jquery.com/)
jQuery是一个快速,简练的的JavaScript工具箱它能够让你以简单的方式来操作HTML元素,处理事件,实现特效并为Web页面添加Ajax交互。jQuery设计用于改变你编写JavaScript的方式。
☆ jQuery UI (http://ui.jquery.com/)
jQuery UI是一套基于jquery构建具有皮肤更换功能的UI控件和鼠标交互组件。用于帮助开发人员构建具有良好用户体验的Web应用程序。交互组件包括drag/dropping、sorting、selecting和resizing等。基于这些核心交互组件构建的UI控件有:accordion、date picker、dialog、slider、table sorter和tab等。
☆ JSON-RPC(http://oss.metaparadigm.com/jsonrpc/)
JSON-RPC-Java是一个用Java来实现动态JSON-RPC的框架. 利用它内置的一个轻级量JSON-RPC JavaScripIt客户端,可以让你透明地在JavaScript中调用Java代码。JSON-RPC-Java可运行在Servlet容器中如Tomcat也可以运行在JBoss与其它J2EE应用服务器中因此可以在一个基于JavaScript与DHTML的Web应用程序中利用它来直接调用普通Java方法与EJB方法。JSON:JavaScript Object Notation。
☆ SmartClient (http://www.smartclient.com/technology/basics.jsp)
SmartClient Ajax platform原本是一个商业产品,现在基于LGPL许可发布成为一个开源项目。SmartClient Ajax平台包括:一个不需要安装的DHTML/AJAX客户端引擎;一套胖客户端UI组件和服务;客户端与服务器数据绑定系统。SmartClient具有的特性:
1.客户端Ajax:在SmartClient中所有的表示层职责和所有HTML生成都在浏览器中完成。HTML的生成或表示层职责都不需要通过服务器来处理。一旦SmartClient加载完成,在浏览器与服务器之间只有数据在传输。
2.多平台支持:SmartClient能够与任务服务器平台相集成通过一些跨平台标准比如:REST和WSDL web services。SmartClient还包含一个Java集成服务器用于加速与Java服务器集成,和对一些流行Java框架的支持如Java Beans/EJB,Spring,Struts与Hibernate。
3.增加升级: SmartClient组件能够很容易的嵌到现有应用程序中。添加Grids、forms、trees、dialogs、wizards和其它SmartClient组件都不会使现有架构发生变化。
4.面向对象:SmartClient提供面向对象的JavaScript APIs,其所有组件都是通过XML或JavaScript创建。采用标准的面向对象模式使得你可以很容易扩展、定制和创建新的SmartClient组件而且不需要学习底层的Ajax技术或作跨浏览器测试与调试。
5.元数据驱动。
6.AJAX MVC(类似于Struts for AJAX)。
此外通过Adobe AIR平台,Firefox extensions,Google Gears和其它技术,SmartClient还支持包括离线应用,提供SmartClient应用程序桌面安装版本(不需要改动任务代码),兼容mobile端浏览器Safari,Opera和IE等。
☆ DWR (http://getahead.org/dwr/)
DWR(Direct Web Remoting)是一个WEB远程调用框架.利用这个框架可以让AJAX开发变得很简单.利用DWR可以在客户端利用JavaScript直接调用服务端的Java方法并返回值给JavaScript就好像直接本地客户端调用一样(DWR根据Java类来动态生成JavaScrip代码).它的最新版本DWR3.0添加许多特性如:支持Dom Trees的自动配置,支持Spring(JavaScript远程调用spring bean),更好浏览器支持,还支持一个可选的commons-logging日记操作.
☆ AJAX Tag (http://ajaxtags.sourceforge.net/)
AJAX Tag是一组Jsp标签,用来简化AJAX(Asynchronous JavaScript and XML )技术在JSP页面中的使用.它提供了一些常见功能的标签如下拉级联选择,用户在文本框中输入字符自动从指定的数据中匹配用户输入的字符等。它构建在prototype Javascript框架之上。
☆ Struts Ajax Tags (http://ajaxtags.sourceforge.net/)
这个AjaxTags是在现有的Struts HTML标签库上添加对AJAX (Asynchronous Javascript+XML)技术的支持。这样就可以为现有的基于Struts HTML标签库的应用程序添加AJAX功能而不用破坏现存的代码并且开发者不需要了解AJAX是怎样工作的。与此类似的还有JSF Ajax Tags。
☆ AjaxAnywhere (http://ajaxanywhere.sourceforge.net/)
AjaxAnywhere被设计成能够把任何一套现存的JSP组件转换成AJAX感知组件而不需要复杂的JavaScript编码.它利用标签把Web页面简单地划分成几个区域,然后使用AjaxAnywhere来刷新那些需要被更新地区域。
对于第三方开源选择的原则,个人认为要考虑以下几点:1、开源组织;2、稳定性;3、可扩充性;4、方便性;5、文档。
在做出选择之前,我们先来用图示的方式大致的整理一下上面提到的各种第三方开源组件,以便帮助我们分析问题:
图0
从上面的图示,我们不难看出,GWT-EXT应该是较为合理的选择,因为它架构在EXTJS和GWT上,兼顾了Ajax和UI两个方面,由google提供开源,适合企业级应用,而且可纯java编程,但其使用模式不同于我们常规开发,用了它,其伸缩性可能有所影响,所以,在短时间内选择适合我们完成开发任务的架构,我认为最佳组合为EXTJS+DWR。DWR可以单纯的使用Ajax技术,不必考虑UI界面,而且使用起来相当简单方便,有较多的用户群,多少不必质疑它对企业级开发的胜任能力;EXTJS可以帮助我们做出优美的UI界面,它对jQuery、Prototype及YUI做了适配器Adapter,随时可以将这3者加进来,同时EXTJS本身也应用了Ajax技术,可以很方便的完成client与server的异步数据交换及数据解析,EXTJS可以借助于MVC框架比如Struts的Action或通过web.xml配置文件和继承HttpServlet类的方式完成client与server的数据异步传输。这种组合方式,相对于选择GWT/GWT-EXT或SmartClient有更大的进退空间,当我们没有太多决策权及时间仓促的时,毕竟我们的目标重点是保证任务完成而不是最佳架构的选择及论证。
当我们时间充足时,最好多了解GWT、GWT-EXT、SmartClient及Ajax Tag,在实践中体会它们。
综合考虑以上几点,我倾向选择extjs和dwr。因此,站在山脚下:完成开发任务,对我目前而言,最终的框架为Struts+Spring+JPA+Extjs+DWR+Json-lib。
下面是我选择Extjs和DWR时做过几个小例子,分享于大家,希望对初学者有所帮助。
三、DWR应用举例
学习开源技术的最好方式是下载官方网站提供的源码包,参考网站上有关应用该技术的资料,实践它的demo程序。
1、 下载dwr.jar
其下载地址是http://getahead.org/dwr/,目前的最新版本是2.0.3,我们可以只下载dwr-2.0.3-src.zip,因为它里面已经包含了dwr.jar和demo两个部分。
2、 搭建dwr环境
用我们的IDE(MyEclipse6.0)新建一个Web Project,并将刚才下载的dwr.jar包添加到该工程的WEB-INF/lib目录下,并在WEB-INF目录下新建一个dwr的配置文件dwr.xml。
在web.xml中添加加载dwr的配置信息,其代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>initApplicationScopeCreatorsAtStartup</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>maxWaitAfterWrite</param-name>
<param-value>100</param-value>
</init-param>
<!--
<init-param>
<param-name>org.directwebremoting.extend.ServerLoadMonitor</param-name>
<param-value>org.directwebremoting.impl.PollingServerLoadMonitor</param-value>
</init-param>
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
<!-- 加载log4j -->
<servlet>
<servlet-name>log4jServlet</servlet-name>
<servlet-class>org.twolf.common.util.Log4jServlet</servlet-class>
<init-param>
<param-name>log4jFileLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
至此,我们web工程已经可以使用dwr了,下面是实践的工程结构图,以便帮助你我更加清晰的理解我的开发过程:
图1
3、 开发示例一(Ajax)
这是Ajax一个简单的类似helloworld的程序,client端请求server端数据,程序的运行界面为:
图2
在name栏中输入信息,当点击send时可以无页面刷新的获取server反馈的信息并显示在页面,如上图的Hello,towlf。下面,我们来完成该示例程序的开发。
A.开发server端程序,该程序主要提供一个sayHello方法,将在后面被client调用,其代码为:
package org.twolf.dwrdemo.simpletext;
import java.io.IOException;
import javax.servlet.ServletException;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
/**
* Some simple text demos
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class Demo
{
/**
* Return a server side string to display on the client in real time
* @param name The name of person to say hello to
* @return A demo string
*/
public String sayHello(String name)
{
return "Hello, " + name;
}
/**
* Fetch a resource using forwardToString()
* @return a demo HTML page
* @throws ServletException If the servlet engine breaks
* @throws IOException If the servlet engine breaks
*/
public String getInclude() throws ServletException, IOException
{
WebContext wctx = WebContextFactory.get();
return wctx.forwardToString("/simpletext/forward.html");
}
}
B. 配置dwr.xml,使得client端可以访问server端Demo中的方法:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr/dwr20.dtd">
<dwr>
<allow>
<!-- simpletext -->
<create creator="new" javascript="Demo">
<param name="class" value="org.twolf.dwrdemo.simpletext.Demo"/>
</create>
</allow>
</dwr>
这样,在client端可以通过Demo.sayHello(param,function)的模式调用server端的sayHello方法。顺便补充一句,在上面的配置文件中,我们也可以指定server端某个类中的哪些方法可以被client端访问,而哪些方法则不可,同时,也可以为我们的类指定转换器,当我们的类含有需要转换的对象时,具体的请参考官方demo中的people这个示例,或者请登录http://getahead.org/dwr/documentation获取更多的dwr信息。
C.书写client端
如上图1所示,我们需要完成WebRoot/simpletext目录下三个文件的代码书写:
index.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Simple Text Generation Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- 下面engine.js,util.js以及Demo.js都是要按这种既定形式的加入-->
<!—或绝对路径/yourWebApp/dwr/engine.js或相对路径../dwr/engine.js -->
<script type='text/javascript' src='/dwr/dwr/engine.js'> </script>
<script type='text/javascript' src='/dwr/dwr/util.js'> </script>
<script type='text/javascript' src='/dwr/dwr/interface/Demo.js'> </script>
<script type="text/javascript" src='index.js'> </script>
<link rel="stylesheet" type="text/css" href="../generic.css" />
</head>
<!-- dwr.util.useLoadingMessage()可以在浏览页面的右上角显示Loading…的字样当加载数据时 -->
<body onload="dwr.util.useLoadingMessage();">
<h1>Dynamically Updating Text</h1>
<p>This is a simple demonstration of how to dynamically update a web-page with
text fetched from a web server.</p>
<div id="demoDiv">
<p>
Name:
<input type="text" id="demoName" value="Joe"/>
<!—onclick是调用index.js中的update()函数,完成client与server的数据交换 -->
<input value="Send" type="button" onclick="update()"/>
<br/>
Reply: <span id="demoReply" style="background:#eeffdd; padding-left:4px; padding-right:4px;"></span>
</p>
</div>
</body>
</html>
index.js:
function update() {
//获取index.html页面<input type="text" id="demoName" value="Joe"/>的值
var name = dwr.util.getValue("demoName");
//将name值传给server端Demo类的sayHello方法,同时获取该方法返回值data
//并将其显示在index.html页面的<span id="demoReply"></span >处
Demo.sayHello(name, function(data) {
dwr.util.setValue("demoReply", data);
});
}
forward.html:
<html>
<body>
<big>
<span style="color: rgb(153, 153, 153);">An <code>import</code>ed</span>
<span style="font-weight: bold; font-family: Helvetica,Arial,sans-serif;">
<span style="color: rgb(204, 102, 204);">JSP</span> Page
</span>
<span style="text-decoration: underline;">with </span>
<span style="font-style: italic; text-decoration: underline;">lots</span>
<span style="text-decoration: underline;"> of formatting</span>
</big>
</body>
</html>
至此,将该工程部署到tomcat中,便可运行本示例了,而那个forward.html只有当server端Demo类抛出异常时才会被调用,暂可不予考虑。
4、开发示例二(Reverse Ajax)
无论是传统的web开发还是上面提到的Ajax技术,在client与server数据交换中,都是client首先发起pull动作,之后,server给以响应,而若我们期待server端的变化比如server是监控某台主机的运行情况能够实时的主动反馈的client,那我们就要考虑Reverse Ajax了,它能够使得server主动的push数据到client,下面我们以clock时钟程序为例,演示以下Reverse Ajax技术:
A.开发server端程序Clock
/*
* Copyright 2005 Joe Walker
http://www.apache.org/licenses/LICENSE-2.0
*
*/
package org.twolf.dwrdemo.clock;
import java.util.Collection;
import java.util.Date;
import javax.servlet.ServletContext;
import org.directwebremoting.ServerContext;
import org.directwebremoting.ServerContextFactory;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.proxy.dwr.Util;
import org.directwebremoting.util.Logger;
public class Clock implements Runnable
{
public Clock()
{
ServletContext servletContext = WebContextFactory.get().getServletContext();
sctx = ServerContextFactory.get(servletContext);
}
public synchronized void toggle()
{
active = !active;
if (active)
{
new Thread(this).start();
}
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run()
{
try
{
log.debug("CLOCK: Starting server-side thread");
while (active)
{
Collection sessions = sctx.getScriptSessionsByPage("/dwr/clock/index.html");
Util pages = new Util(sessions);
pages.setValue("clockDisplay", new Date().toString());
log.debug("Sent message");
Thread.sleep(1000);
}
Collection sessions = sctx.getScriptSessionsByPage("/dwr/clock/index.html");
Util pages = new Util(sessions);
pages.setValue("clockDisplay", "");
log.debug("CLOCK: Stopping server-side thread");
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
/**
* Our key to get hold of ServerContexts
*/
private ServerContext sctx;
/**
* Are we updating the clocks on all the pages?
*/
private transient boolean active = false;
private static final Logger log = Logger.getLogger(Clock.class);
}
B.配置dwr.xml
<!-- clock -->
<create creator="new" javascript="Clock" scope="application">
<param name="class" value="org.twolf.dwrdemo.clock.Clock"/>
</create>
C.书写client端
如上图1所示,我们需要完成WebRoot/clock目录下三个文件的代码书写:
index.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Server Side Reverse Ajax Clock</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type='text/javascript' src='/dwr/dwr/interface/Clock.js'> </script>
<script type='text/javascript' src='/dwr/dwr/engine.js'> </script>
<script type='text/javascript' src='/dwr/dwr/util.js'> </script>
</head>
<body onload="dwr.engine.setActiveReverseAjax(true);">
<h1>Server Side Reverse Ajax Clock</h1>
<p>Creating a clock in a web page is easy, but what about a clock controlled by
the server? This demo shows how use use a separate server side thread to control
a number of browsers.</p>
<input type="button" value="Start / Stop" onclick="Clock.toggle();"/>
<p></p>
<div style="font-size:200%;" id="clockDisplay"></div>
</body>
</html>
至此,重新部署该工程,启动tomcat,便可运行本程序,如下图3所示:
图3
5、小结
A.大致开发流程
1.编写业务代码,该代码是和dwr无关的。
2.确认业务代码中哪些类、哪些方法要由javascript直接访问的。
3.编写dwr组件,对步骤2的方法进行封装。
4.配置dwr组件到dwr.xml文件中,如果有必要,配置convert,
进行java和javascript类型互转。
5.通过反射机制,dwr将步骤4的类转换成javascript代码,提供给前台页面调用。
6.编写网页,调用步骤5的javascript中的相关方法(间接调用服务器端的相关类的方法),执行业务逻辑,将执行结果利用回调函数返回。
7.在回调函数中,得到执行结果后,可以继续编写业务逻辑的相关javascript代码。
B.为何要将用到的js单独写成一个文件,而不是混用在html页面中?
原因有二:一是维护方便,二是与浏览器解析效率有关
C.可能遇到的问题与解决方法【Qusetion&Answer】
#Q
log4j:WARN No appenders could be found for logger (org.directwebremoting.util.Logger).
log4j:WARN Please initialize the log4j system properly.
#A
没有加载log4j
#Q
log4j:WARN No appenders could be found for logger (org.apache.commons.digester.Digester.sax).
log4j:WARN Please initialize the log4j system properly.
#A
没有加载log4j
#Q
[org.directwebremoting.dwrp.DefaultConverterManager]-[INFO] Probably not an issue: org.jdom.Document is not available so the jdom converter will not load. This is only an problem if you wanted to use it.
#A
缺少jdom.jar包
四、EXTJS应用举例
序:在开发电信报表时,局方提出这样的需求,形式化描述为:在web前台展示电信的数据A,由工作人员输入相应其它运营上的数据B,然后,根据既定的计算公式,生成所需数据C,最后,生成xls格式报表。因为之前我们已经完成了xls报表的生成与展示,所以,我们所面临的问题就是如何完成web前台电信数据的展示、相应运营商数据的输入及传输到后台产生最终的报表,为此,我在此的示例程序是Extjs的EditGrid。
1、下载Extjs
其下载地址http://extjs.com/download,目前最新版本为2.0.2.
2、搭建使用extjs环境
搭建方式有多种,可以根据你的需求,登陆http://extjs.com/download/build ,在线定制自己的需求,为方便记,你也不妨采用我下面的方式,搭建一个简易的测试环境:
图4
如上图4,创建一个web工程extjsApp,将我们下载的ext-2.0.1.zip解压缩后,把其整个内容拷贝到WebRoot/extjs目录下,部署该工程,启动tomcat服务,我们便可以访问该工程下的docs文档及examples示例,如下图5、6:
图5
图6
当我们输入http://localhost:10000/extjsApp/extjs/examples/grid/totals.html,便可以得到下图7所示的示例程序:
图 7
该程序将是我们的研究对象,它涉及到三个文件:totals.js、totals.html及GroupSummary.js,其中,GroupSummary.js主要是完成数据汇总,显示在图7每个分组的最后一行,它定义了一个继承于Ext.util.Observable类的子类Ext.grid.GroupSummary,在该类中,我们目前仅需关注它的Ext.grid.GroupSummary.Calculations方法的应用就够了,totals.js完成图7页面数据的静态加载,当我们改变Estimate、Rate时,相应的统计值会发生变化。
但是,上面的示例程序尚不能完全满足我们的需求,我们还要为其加入如下功能:1、加入button,提交参数到server之后展示数据;2、数据应从server端动态获取;3、能够获取展示的grid数据并传送到server端。为此,我们需要做如下几个工作:
A. 改写totals.js
Ext.onReady(function(){
//定义函数变量datesubmitfunc,为button点击相应做准备
var datesubmitfunc = function() {
//改写默认的背景图片,否则Ext回到http://www.extjs.com获取该图片
Ext.BLANK_IMAGE_URL = '/extjsApp/extjs/resources/images/default/s.gif';
Ext.QuickTips.init();
// Ext.get('btnsubmitdate').on('click',function(){alert('click me~~')});
var xg = Ext.grid;
/*
var reader = new Ext.data.JsonReader({
idProperty:'taskId', //idProperty:'taskId' 可以更换为 id:'taskId'
fields: [
{name: 'projectId', type: 'int'},
{name: 'project', type: 'string'},
{name: 'taskId', type: 'int'},
{name: 'description', type: 'string'},
{name: 'estimate', type: 'float'},
{name: 'rate', type: 'float'},
{name: 'cost', type: 'float'},
{name: 'due', type: 'date', dateFormat:'m/d/Y'}
]
});
*/
/*
以下是替换上面的代码的 var reader = new Ext.data.JsonReader -twolf
*/
var contentNode = new Ext.data.Record.create([
{name: 'projectId', type: 'int'},
{name: 'project', type: 'string'},
{name: 'taskId', type: 'int'},
{name: 'description', type: 'string'},
{name: 'estimate', type: 'float'},
{name: 'rate', type: 'float'},
{name: 'cost', type: 'float'},
{name: 'due', type: 'date', dateFormat:'m/d/Y'}
]);
var reader = new Ext.data.JsonReader({root:'data',id:'taskId'},contentNode);
/*
替换结束-twolf
*/
// define a custom summary function,so u can do sth. for yourself’s requirements like this
Ext.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){
return v + (record.data.estimate * record.data.rate);
}
var summary = new Ext.grid.GroupSummary();
//定义变量获取totals.html输入框id标记为date的值,测试client数据传输到server
var params = Ext.get('date').getValue(false);
/*
定义按钮及其点击事件处理
*/
var btntest = new Ext.Button({
id: 'btn-one',
text: 'bnt4test',
handler: function(){alert('test')}
});
var btn = new Ext.Button({
id:'_btn',
text:'btn'
});
btn.on('click',function(){
//alert(myGrid.getStore().getCount());
//获取grid中数据,编写成json格式,然后上传到server
var upInfo='[';
var dataStore = myGrid.getStore();
var record;
for(var i=0;i<dataStore.getCount();i++) {
record = dataStore.getAt(i);
upInfo = upInfo + '{estimate:' + record.get('estimate') + ',rate:' + record.get('rate') + '},';
}
upInfo = upInfo.substring(0,upInfo.length-1) + ']';
alert(upInfo);
//利用Ajax技术,将我们的upInfo信息传递到server,以备后台程序处理
Ext.Ajax.request({
url:'/extjsApp/updata',
params:{upInfo:upInfo},
success:function(response,options){
alert('success!');
}
});
});
var myGrid = new xg.EditorGridPanel({
//ds 即为 store
ds: new Ext.data.GroupingStore({
autoLoad: true,
loadMask:{msg:'Loading ...'},
reader: reader,
//修改data来源,要求来自server端
//data: xg.dummyData,
proxy: new Ext.data.HttpProxy(
new Ext.data.Connection(
{
extraParams:{test:params,suc:'suc'},
url:'/extjsApp/getdata'
}
)
),
//url:'/extjsApp/getdata?test=test',这种形式可以带头上面的proxy,因为它可以自动帮我们创建一个HttpProxy
sortInfo:{field: 'due', direction: "ASC"},
groupField:'project'
}),
/*
以下的summaryType、summaryRenderer工作都是由GroupSummary.js完成
*/
columns: [
{
id: 'description',
header: "Task",
width: 80,
sortable: true,
dataIndex: 'description',
summaryType: 'count',
hideable: false,
summaryRenderer: function(v, params, data){
return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');
},
editor: new Ext.form.TextField({
allowBlank: false
})
},{
header: "Project",
width: 20,
sortable: true,
dataIndex: 'project'
},{
header: "Due Date",
width: 25,
sortable: true,
dataIndex: 'due',
summaryType:'max',
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
editor: new Ext.form.DateField({
format: 'm/d/Y'
})
},{
header: "Estimate",
width: 20,
sortable: true,
dataIndex: 'estimate',
summaryType:'sum',
renderer : function(v){
return v +' hours';
},
editor: new Ext.form.NumberField({
allowBlank: false,
allowNegative: false,
style: 'text-align:left'
})
},{
header: "Rate",
width: 20,
sortable: true,
renderer: Ext.util.Format.usMoney,
dataIndex: 'rate',
summaryType:'average',
editor: new Ext.form.NumberField({
allowBlank: false,
allowNegative: false,
style: 'text-align:left'
})
},{
id: 'cost',
header: "Cost",
width: 20,
sortable: false,
groupable: false,
renderer: function(v, params, record){
return Ext.util.Format.usMoney(record.data.estimate * record.data.rate);
},
dataIndex: 'cost',
summaryType:'totalCost',
summaryRenderer: Ext.util.Format.usMoney
}
],
buttons: [btntest,btn],
view: new Ext.grid.GroupingView({
forceFit:true,
showGroupName: false,
enableNoGroups:false, // REQUIRED!
hideGroupedColumn: true
}),
plugins: summary,
frame:true,
width: 800,
height: 450,
clicksToEdit: 1,
collapsible: true,
animCollapse: false,
trackMouseOver: false,
//enableColumnMove: false,
title: 'Sponsored Projects',
iconCls: 'icon-grid',
renderTo: document.body
});
};
Ext.get('btnsubmitdate').on('click',datesubmitfunc);
//获取totals.html中id为btnsubmitdate的button对象并添加click点击事件
});
B、在上面A中,我们用到了两个请求:url:'/extjsApp/getdata'从server获取数据和url:'/extjsApp/updata'传送数据到server端,为此,我们添加以下内容到web.xml配置文件中:
<servlet>
<servlet-name>getdata</servlet-name>
<servlet-class>twolf.dataserv.GetData</servlet-class>
</servlet>
<servlet>
<servlet-name>updata</servlet-name>
<servlet-class>twolf.dataserv.Updata</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getdata</servlet-name>
<url-pattern>/getdata</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>updata</servlet-name>
<url-pattern>/updata</url-pattern>
</servlet-mapping>
C、 定义上面B用到的两个测试类
package twolf.dataserv;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class GetData extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
//super.doGet(req, resp);
resp.setContentType("application/x-json;charset=utf-8");
PrintWriter out = resp.getWriter();
System.out.println("param:"+req.getParameter("test"));
String info = "{data:["
+ "{projectId: 100, project: 'Ext Forms: Field Anchoring', taskId: 112, description: 'Integrate 2.0 Forms with 2.0 Layouts', estimate: 6, rate: 150,cost:101, due:'06/24/2007'}"
+","
+"{projectId: 100, project: 'Ext Forms: Field Anchoring', taskId: 113, description: 'Implement AnchorLayout', estimate: 4, rate: 150,cost:102, due:'06/25/2007'}"
+","
+"{projectId: 102, project: 'Ext Grid: Summary Rows X', taskId: 111, description: 'Testing and debugging', estimate: 8, rate: 125,cost:103, due:'07/15/2007'}"
+"]}";
out.println(info);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
//super.doPost(req, resp);
doGet(req,resp);
}
}
package twolf.dataserv;
import java.io.IOException;
import java.util.Iterator;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
public class Updata extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
//super.doGet(req, resp);
String upInfo = req.getParameter("upInfo");
System.out.println(upInfo);
JSONArray jsonArr = JSONArray.fromObject(upInfo);
JSONObject jsonObj;
Iterator<Object> itr = jsonArr.iterator();
while(itr.hasNext()) {
jsonObj = JSONObject.fromObject(itr.next());
System.out.println(jsonObj);
}
System.out.println("...........................");
for(Object obj : jsonArr){
jsonObj = JSONObject.fromObject(obj);
System.out.println(jsonObj);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
//super.doPost(req, resp);
doGet(req,resp);
}
}
至此,我们重新部署一下,就可以运行它了,效果如下:
启动
Submit
Btn
操作后client、server数据往来信息
到此为止,改造后的EditGrid理论上已经可以满足我们的需求了。
你是否已经注意到这样两个问题:1、类Updata使用了json对象;2、用到了firefox的javascript调试工具firebug。对于firebug,它是的确是个不错的javascript调试工具,若你尚未使用,那请你尽快使用吧;而对于json,尽管我们已经把数据传递到了server端,但那样 的一串字符要我们个人去解析绝对不是一个好的选择,所以,我引入了json,通过jsonarray将字符串解析成jsonobject,然后在将其转化为javabean对象,这样一来我们就可以想往常一样解决我们的业务逻辑编码了。
关于json,我下面做一个简要的阐述,仅仅给你一感性上的认识而已……
备注:在上面示例演示调试过程中,遇到了一个令人头痛的问题是js代码可以顺利的通过firefox浏览器,而可能不会通过IE类的浏览器(如microsoft的ie、傲游、世界之窗等),造成该问题的症结是js中的某些注释影响的浏览器对其解析的识别度,对于这个问题,我暂无好的解决方法,若诸位有好的解决方法,望不吝告知,谢谢先~~
但大家也知道,js是解释性的语言,为了提高解释效率,尽量减少注释和空行,可这却不太利于代码阅读等,这也就是为何extjs中同时存在ext-all.js、ext-all-debug.js、ext-core.js及ext-core-debuge.js的原因,xx-debug.js便于阅读用于开发阶段,xx.js便于浏览器解析用于发布阶段。
五、json-lib简介
JSON-lib这个Java类包用于把bean,map和XML转换成JSON并能够把JSON转回成bean或DynaBean。转换时,javascript与java数据类型的对应关系为:
JSON |
| Java |
string | <=> | java.lang.String, java.lang.Character, char |
number | <=> | java.lang.Number, byte, short, int, long, float, double |
true|false | <=> | java.lang.Boolean, boolean |
null | <=> | null |
function | <=> | net.sf.json.JSONFunction |
array | <=> | net.sf.json.JSONArray (object, string, number, boolean, function) |
object | <=> | net.sf.json.JSONObject |
Json-lib有jdk1.3和jdk1.5两个版本,而且都有依赖的第三方jar,我用的是jdk1.5版,依赖于jakarta commons-lang 2.3、jakarta commons-beanutils 1.7.0、jakarta commons-collections 3.2、 jakarta commons-logging 1.1及ezmorph 1.0.4,它们的下载地址分别为:
Json-lib:http://json-lib.sourceforge.net/
EZMorph:http://ezmorph.sourceforge.net
以下几个都可以在apache的官方网站apache.org的找到,或直接登陆http://commons.apache.org/,或分别登陆:
Collections:http://jakarta.apache.org/commons/collections/
Commons-Lang:http://jakarta.apache.org/commons/lang/
Commons-BeanUtils:http://commons.apache.org/beanutils/
commons-logging:http://commons.apache.org/logging/
在web开发中,json大有取代xml的趋势,关于json的简单应用及其与xml的对比,我们可以直接参考官方网站http://json-lib.sourceforge.net/usage.html,为方便记,转帖该内容如下:
。。。。。。
举例:我把当时做测试的例子贴出来供大家参考,贴出来的目的是给大家一个可能遇到的异常的解决方法:
类MyBean:
package twolf.test;
import java.math.BigDecimal;
class MyBean {
private String name="zhangfeng";
private String sex="male";
private BigDecimal age= new BigDecimal(27);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public BigDecimal getAge() {
return age;
}
public void setAge(BigDecimal age) {
this.age = age;
}
}
类TestMain:
package twolf.test;
import java.util.HashMap;
import java.util.Map;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
public class TestMain {
public static void main(String[] args) {
JSONObject jsonObject;
JSONArray jsonArr;
System.out.println(" .....json from map.... ");
Map<String,Object> map = new HashMap<String, Object>();
map.put( "name", "json" );
map.put( "bool", Boolean.TRUE );
map.put( "int", new Integer(1) );
map.put( "arr", new String[]{"a","b"} );
map.put( "func", "function(i){ return this.arr[i]; }" );
jsonObject = JSONObject.fromObject( map );
System.out.println( jsonObject );
System.out.println(" .....json from bean.... ");
MyBean bn = new MyBean();
bn.setName("yun dao");
jsonObject = JSONObject.fromObject(bn);
System.out.println( jsonObject );
System.out.println(" .....json to bean.... ");
String json = "[{name:'zhangfeng',sex:'male',age:27},{name:'zhangfeng',sex:'male',age:27}]";
jsonArr = JSONArray.fromObject( json );
System.out.println(jsonArr);
for(int i = 0; i < jsonArr.size(); i++) {
Object tmpStr = jsonArr.get(i);
System.out.println(tmpStr);
jsonObject = JSONObject.fromObject(tmpStr);
bn = (MyBean) JSONObject.toBean(jsonObject,MyBean.class);
System.out.println(bn.getName() + " " +bn.getSex() + " " + bn.getAge());
}
String str="zhangfeng";
System.out.println(str.substring(1, 3)); //从指定位置到指定位置
}
}
当你运行时就会遇到如下的异常:
* Exception in thread "main" net.sf.json.JSONException: java.lang.NoSuchMethodException: Property 'name' has no getter method
at net.sf.json.JSONObject._fromBean(JSONObject.java:949)
at net.sf.json.JSONObject.fromObject(JSONObject.java:189)
at net.sf.json.JSONObject.fromObject(JSONObject.java:151)
at twolf.test.TestMain.main(TestMain.java:49)
Caused by: java.lang.NoSuchMethodException: Property 'name' has no getter method
at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1127)
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:686)
at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:715)
at org.apache.commons.beanutils.PropertyUtils.getProperty(PropertyUtils.java:290)
at net.sf.json.JSONObject._fromBean(JSONObject.java:924)
... 3 more
上面异常的解决方法是将MyBean类声明成public类型。