赞
踩
第一部分 DWR是什么,如何使用
DWR是一个开源的类库,可以帮助开发人员开发包含AJAX技术的网站.它可以允许在浏览器里的代码(javascript)使用运行在WEB服务器上的JAVA函数,就像它就在浏览器里一样.它包含两个主要的部分:允许JavaScript从WEB服务器上一个遵循了AJAX原则的Servlet(小应用程序)中获取数据.另外一方面一个JavaScript库可以帮助网站开发人员轻松地利用获取的数据来动态改变网页的内容.
关于DWR的使用 引用别人的文章。
开始使用 DWR
原文出处: http://www.javatang.com/archives/2006/10/20/254879.html
翻译: Jet Mah
有两种方法来开始 DWR 的学习,最简单的做法是下载官方提供的 WAR 文件然后对此深入研究。不过这种方式不能使你感到将DWR整合到你现有的web程序有多么的简单,所以我们推荐你跟随下面三个步骤:
1. 安装 DWR JAR 包
下载 dwr.jar 文件,然后将它放在 web 程序的 WEB-INF/lib 目录下面,很可能在这个目录下已经有一些 jar 文件了。
2. 编辑 config 文件
将下面的代码添加到 WEB-INF/web.xml 文件中,<servlet>需要放在另外的<servlet>之后,<servlet-mapping>也是如此。
<servlet> <servlet-name>dwr-invoker</servlet-name> <display-name>DWR Servlet</display-name> <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping>
接下来创建 dwr.xml 文件并将此放在 web.xml 所在的 WEB-INF 目录下。文件中类似下面的内容:
<!DOCTYPE dwr PUBLIC ”-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN” ”http://www.getahead.ltd.uk/dwr/dwr10.dtd”> <dwr> <allow> <create creator=”new” javascript=”JDate”> <param name=”class” value=”java.util.Date”/> </create> <create creator=”new” javascript=”Demo”> <param name=”class” value=”your.java.Bean”/> </create> </allow> </dwr>
DWR 配置文件定义了由 DWR 创建和被 Javascript 远程使用的类。在上面的例子中我们在远程创建了2个类,并且给出了在 Javascript 中的类名。
上面使用的 new creator 使用了所有的 JavaBeans 必须含有的公有(public)无参(no-args)的构造函数。值得一提的是, DWR 还有一些限制:
避免使用 JavaScript 保留字;以保留字命名的方法将自动被排除。大多数 JavaScript 的保留字同时也是 Java 的保留字,因此无论如何你也不能使用一个名为 “try()” 的方法。但是最常用的一个词 “delete()”,在 JavaScript 中有特殊的含义而在 Java 中没有。
重载方法(Overloaded methods)将会在调用的时候陷入未知的状况,因此应该避免重载方法。
3. 访问下面的地址
http://localhost:8080/[YOUR-WEBAPP]/dwr/
你应该会看到一个页面,上面显示了刚才你在第二步所创建的类。进入一个链接之后你会看到所有等待调用方法的列表。这些动态产生的例子你也能通过 DWR 来实现。
亲自尝试和体会下吧。
怎样应用到你的 Web 程序中?
在侧边栏有很多例子演示了怎样改变网页中的文本、更新列表、操作表单和动态修改表格。每一个例子都有详细的说明。
另外一个开始方法就是从页面中查看源代码,这些页面你刚刚浏览过:
进入 http://localhost:8080/[YOUR-WEBAPP]/dwr/ 然后点击你创建的类;
查看源代码然后定位到你所感兴趣的方法的代码行;
将这些文本粘贴到你 Web 程序的一个 HTML 或 JSP页面中;
包含下面的 javascrip 文件:
<script src=’/[YOUR-WEBAPP]/dwr/interface/[YOUR-SCRIPT].js’></script>
<script src=’/[YOUR-WEBAPP]/dwr/engine.js’></script>
你可以根据实际情况修改 /[YOUR-WEBAPP]/ 部分。
关于怎样书写 DWR 控制的 Javascript 代码请查看 脚本简介:http://getahead.org/dwr/browser/intro。
第二部分 下载源码,做好准备
DWR在www.java.net开源社区中,目前是3.0版本,而且由于DWR去年刚加入Dojo基金会,而Dojo基金会力促DWR和基金会下面的另外一个comet项目结合,估计将来dwr会有很大变化。dwr是以客户端获取会主的动态技术,comet是以服务器端推为主的动态技术,这两个结合有好看的,我们拟目以待。首先要去到www.java.net网站注册一个用户名,注册之后,就可以下载源码啦。【四大开源社区: 1)www.sourceforge.net;2)www.java.net;3)www.eclipse.org;4)www.opensource.org】
我是这么下载源码的,使用的eclipse的cvs工具,按下面方式填写
Connection type: pserver
User: 你的注册用户名称
Password: 你的注册用户密码
Host: cvs.dev.java.net
Repository path: /cvs
Use default port
打开cvs后,你会发现好多好多开源工程,找到DWR工程,目前DWR版本是3.0,check out,在把运行环境配备好,我用的是Tomcat5.5,java虚拟机1.5版本,这三个正好配套。
将eclipse和Tomcat5.5调试好后,我们就可以将工程发布到Tomcat中去了,这里注意,由于DWR工程组织方式是按ant习惯方式组织的,所以你不能按eclipse默认方式去发布你的DWR工程,你需要执行build.xml构建dwr.war包,该包会生成放到工程的\target\ant\目录下,在我机器上是D:\dwr_workspace\dwr\target\ant\dwr.war,然后你需要将dwr.war拷贝到D:\Tomcat 5.5\webapps目录,运行Tomcat,系统会自动将dwr.war解包,形成dwr目录,比如在我机器上会形成文件夹D:\Tomcat 5.5\webapps\dwr。
在我机器上访问http://localhost:5050/dwr,就可见到首面了,但是下半部分报错,看看tomcat后台也报错,查了查是找不到类的缘故,再查发现缺少js.jar包,上网找找,下载了一个js.jar包,再将js.jar包添加到目录D:\dwr_workspace\dwr\web\WEB-INF\lib\js.jar,然后重新发布war包就没问题啦。
第三部分 DWR的技术基石-无刷新访问服务器
从2005年开始,ajax在全球渐渐掀起了一股潮流,它使浏览器可以为用户提供更为自然的浏览体验,全球几百个ajax项目中,dwr就是其中一项。
ajax采用的技术基石其实早就有了,就是“无刷新访问服务器”技术,所以有人说ajax其实就是新瓶装老酒,一点没错,就是新瓶装老酒,在工程师们将“无刷新访问服务器”技术封装成各种套件工具时,确实给我们带来了惊喜,也带来了新的响亮的名字ajax(Asynchronous JavaScript and XML)。
第一步,我们要回顾“无刷新访问服务器”技术。
第二步,在源码中找到dwr中“无刷新访问服务器”技术的原始代码。
第三步,我们再简单的总结总结。
1)“无刷新访问服务器”技术
var xmlhttp=null; function PostOrder(xmldoc) { var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.5.0"); xmlhttp.Open("POST", "http://myserver/orders/processorder.asp";, false); //设置XMLHTTP对象,这个对象可以执行向预定目标地址以Post方式发送数据包。 //http://myserver/orders/processorder.asp服务器端专门用来接收数据的地址 xmlhttp.onreadystatechange= HandleStateChange;//设置客户端响应状态,发送数据后,服务器会响应 xmlhttp.Send(xmldoc);//发送数据,xmldoc是xml结构化的数据 myButton.disabled = true; } function HandleStateChange() { if (xmlhttp.readyState == 4)//当收到正确响应时状态变成4,否则一直等待 { myButton.disabled = false; alert("Result = " + xmlhttp.responseXML.xml);//返回的数据 } } var xmlDoc=new ActiveXObject("MSXML2.DOMDocument"); flag=xmlDoc.loadXML(""); newNode =xmlDoc.createElement("编码") MarkNode=xmlDoc.documentElement.appendChild(newNode); newNode =xmlDoc.createElement("StartMark") newNode.text=StartMark; MarkNode.appendChild(newNode) newNode =xmlDoc.createElement("EndMark") newNode.text=EndMark; MarkNode.appendChild(newNode) newNode =xmlDoc.createElement("日期") DateNode=xmlDoc.documentElement.appendChild(newNode); newNode =xmlDoc.createElement("StartDate"); newNode.text=StartDate; DateNode.appendChild(newNode) newNode =xmlDoc.createElement("EndDate") newNode.text=EndDate; DateNode.appendChild(newNode);
上面的代码很好理解吧。好了,这里要给出补充了,实际上ActiveXObject是IE支持的类型,假如浏览器不支持这个呢
摘录了一段比较细的解释:
1:先建立XMLHttpRequest,建立成功以后要在它的后面紧跟着建立一个xhr.overrideMimeType("text/xml")对于该句的解释在天极网找到了这么一段话“如果服务器的响应没有XML mime-type header,某些Mozilla浏览器可能无法正常工作。为了解决这个问题,如果服务器响应的header不是text/xml,可以调用其它方法修改该header”。
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
if(xhr.overrideMimeType)
{ xhr.overrideMimeType("text/xml") ;}
}
2:如果XMLHttpRequest建立不成功就要建立针对IE的ActiveXObject.不同的IE版本有不同ActiveXObject,但是我们现在只要建立两个就可以了,163是这样做的于是我也就理解为这样就可以兼容所有的IE了,具体没测试过.
try{
xhr = new ActiveXObject("Msxml.XMLHTTP");
}catch(e){
try{
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(e){}
}两个try嵌套把两种情况都写出来了.
到现在为止,算是回顾了无状态刷新技术。
2)dwr中“无刷新访问服务器”技术的原始代码
在dwr.jar包里,可以找到文件\org\directwebremoting\engine.js,有如下的代码片段,在这段代码中,我们能够找到“无刷新访问服务器”技术的原始代码,关注这段代码中的“看这里”,代码相对于上面,膨胀了很多呢,主要是处理意外、特殊情况的代码,没必要仔细研究。
/**//** * Remoting through XHR */ xhr:{ /**//** * The default HTTP method to use */ httpMethod:"POST", /**//** * The ActiveX objects to use when we want to do an XMLHttpRequest call. * TODO: We arrived at this by trial and error. Other toolkits use * different strings, maybe there is an officially correct version? */ XMLHTTP:["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"], /**//** * Setup a batch for transfer through XHR * @param {Object} batch The batch to alter for XHR transmit */ send:function(batch) { if (batch.isPoll) { batch.map.partialResponse = dwr.engine._partialResponseYes; } // Do proxies or IE force us to use early closing mode? if (batch.isPoll && dwr.engine._pollWithXhr == "true") { batch.map.partialResponse = dwr.engine._partialResponseNo; } if (batch.isPoll && dwr.engine.isIE) { batch.map.partialResponse = dwr.engine._partialResponseNo; } //看这里 if (window.XMLHttpRequest) { batch.req = new XMLHttpRequest(); } else if (window.ActiveXObject) { batch.req = dwr.engine.util.newActiveXObject(dwr.engine.transport.xhr.XMLHTTP); } //看这里 // Proceed using XMLHttpRequest if (batch.async) { batch.req.onreadystatechange = function() { if (typeof dwr != 'undefined') { dwr.engine.transport.xhr.stateChange(batch); } }; } // If we're polling, record this for monitoring if (batch.isPoll) { dwr.engine._pollReq = batch.req; // In IE XHR is an ActiveX control so you can't augment it like this if (!document.all) batch.req.batch = batch; } httpMethod = dwr.engine.transport.xhr.httpMethod; // Workaround for Safari 1.x POST bug var indexSafari = navigator.userAgent.indexOf("Safari/"); if (indexSafari >= 0) { var version = navigator.userAgent.substring(indexSafari + 7); if (parseInt(version, 10) < 400) { if (dwr.engine._allowGetForSafariButMakeForgeryEasier == "true") { httpMethod = "GET"; } else { dwr.engine._handleWarning(batch, { name: "dwr.engine.oldSafari", message: "Safari GET support disabled. See getahead.org/dwr/server/servlet and allowGetForSafariButMakeForgeryEasier." }); } } } batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall; var request = dwr.engine.batch.constructRequest(batch, httpMethod); try { batch.req.open(httpMethod, request.url, batch.async);//看这里,发送设置 try { for (var prop in batch.headers) { var value = batch.headers[prop]; if (typeof value == "string") { batch.req.setRequestHeader(prop, value); } } if (!batch.headers["Content-Type"]) { batch.req.setRequestHeader("Content-Type", "text/plain"); } } catch (ex) { dwr.engine._handleWarning(batch, ex); } batch.req.send(request.body);//看这里,执行发送 if (!batch.async) { dwr.engine.transport.xhr.stateChange(batch); } } catch (ex) { dwr.engine._handleError(batch, ex); } if (batch.isPoll && batch.map.partialResponse == dwr.engine._partialResponseYes) { dwr.engine.transport.xhr.checkCometPoll(); } // This is only of any use in sync mode to return the reply data return batch.reply; }
3)在batch.req.send(request.body)代码行上添加alert("see me:"+request.body);可以看到执行发送时,到底发送了什么。
1,在我进入dwr工程的首页时。alert弹出的内容如下:
see me:callCount=1
windowName=DWR-4B6B.06
c0-scriptName=_System
c0-methodName=pageLoaded
c0-id=0
batchId=0
page=/dwr/
httpSessionId-
scriptSessionId=
2,在我们进入样例Dynamically Text中,点击发送时,alert弹出内容
see me:callCount=1
windowName=DWR-4B6B.2D06
c0-scriptName=Demo
c0-methodName=sayHello
c0-id=0
c0-param0=string:Joe
batchId=1
page=/dwr/simpletext/index.html
httpSessionId=
scriptSessionId=A822701A
3,当我进入样例Edit Table时,删除表中一行时,alert弹出内容
see me:callCount=2
windowName=DWR-4B6B.D06
c0-scriptName=People
c0-methodName=deletePerson
c0-id=0
c0-e1=number:18
c0-param0=Object_Object:{id:reference:c0-e1}
c1-scriptName=People
c1-methodName=getAllpeople
c1-id=1
batchId=2
page=/dwr/people/index.html
httpSessionId=
scriptSessionId=2557C..42A1
看看上面这些,比较比较不同处,现在可能还不好理解,当我们在后面讲解dwr原理时,回过头来看会更能理解。
我们先提前解释一下:
callCount=1 表示执行了多少个方法
c0-scriptName =Demo 调用了那个java类 c后面的序号是方法的序号,如果有两个方法,那么就有c0,c1,见第三个例子
c0-methodName=sayHello 调用了类中的哪个方法
c0-id =0 方法序号
c0-param1=String: Joe 传递的值,这个值对应于java方法的参数
page=/dwr/.. 路径
总结:
dwr通过无状态刷新技术,向服务器端发送数据包,服务器端响应,客户端收到返回数据后,作出变化。
下面这一段话是更详细的技术细节
以样例Dynamically Text为例
在浏览器加载http://localhost:5050/dwr/simpletext/index.html时,
浏览器遇到下面这句话
<script type='text/javascript' src='../dwr/interface/Demo.js'> </script>
就执行dwrServlet.java(web.xml有dwr相关配置),向服务器调用发送类似于样例1的内容,不执行任何操作。
当我们真正点击发送按钮时,向服务器发送样例2的数据,告诉服务器,我会调用哪个类的哪个方法,并且传什么样的参数,参数内容是什么。
不用说,服务器端肯定是接收相应的数据,激发相应的类相应的方法
对于参数,涉及到字符串数据比如String:Joe,转换为java对象,有专门的转换类处理,后缀为convert的类
第四部分 DWRServlet类分析
org.directwebremoting.servlet.DwrServlet主要分为三部分,也就是三个主要方法:init;doGet;doPost;
1,init,初始化。
DWR对于Servelet容器内部设一个配置容器,该容器装载dwr映射相关信息。
- public void init(ServletConfig servletConfig) throws ServletException
- {
- super.init(servletConfig);
- ServletContext servletContext = servletConfig.getServletContext();
-
- try
- {
- // setupLogging() only needed for servlet logging if commons-logging is unavailable
- // logStartup() just outputs some version numbers
- 日志初始化开始
- StartupUtil.logStartup(servletConfig);
-
- // create and setup a DefaultContainer
- DWR对于Servelet容器内部设一个配置容器,该容器装载dwr隐射相关信息。
- container = ContainerUtil.createAndSetupDefaultContainer(servletConfig);
-
- StartupUtil.initContainerBeans(servletConfig, servletContext, container);
- webContextBuilder = container.getBean(WebContextBuilder.class);
-
- ContainerUtil.prepareForWebContextFilter(servletContext, servletConfig, container, webContextBuilder, this);
- ContainerUtil.publishContainer(container, servletConfig);
- ContainerUtil.configureContainerFully(container, servletConfig);
- }
- catch (ExceptionInInitializerError ex)
- {
- log.fatal("ExceptionInInitializerError. Nested exception:", ex.getException());
- throw new ServletException(ex);
- }
- catch (Exception ex)
- {
- log.fatal("DwrServlet.init() failed", ex);
- throw new ServletException(ex);
- }
- finally
- {
- if (webContextBuilder != null)
- {
- webContextBuilder.unset();
- }
- }
- }
-
比较细致的环节:
init()调用
createAndSetupDefaultContainer()
调用
setupDefaultContainer(defaultContainer, servletConfig)
调用如下步骤
1)setupDefaults(container);
包括:
容器相关管理工具配置集,也就是要实现的一些接口。这种方式蛮好,当容器需要具备很多接口功能时,把这些接口全部做成隐射,并设置成可配置的实现
container.addImplementation(AccessControl.class, DefaultAccessControl.class);
// Mapping handlers to URLs,路径配置
createUrlMapping(container, "/index.html", IndexHandler.class, "indexHandlerUrl");
//读取servlet配置属性
2)setupFromServletConfig(container, servletConfig);
额外处理1)中多选类型
3)resolveMultipleImplementations(container, servletConfig);
里面使用了通用yui型压缩器
【使用 YUI Compressor 压缩 Javascript 和 CSS
工具下载: http://www.julienlecomte.net/yuicompressor/
英文介绍: http://com3.devnet.re3.yahoo.com/yui/compressor/
YUI Compressor是使用Java编写的工具,需要Java版本大于等于1.4。
工作原理简介:
主要是去掉冗余的空白,主要包括空格,换行符、制表符。
对于 Javascript,还采用缩短变量名的方法压缩文件,就是在保证代码正确性的情况下将变量名用只有1个字符的字符串代替,或者2个、3个字符,总之尽量短。
对于 CSS,还有采用优化0值属性值的表示,优化颜色值的方法压缩文件。
CSS的优化比 CSSTidy 功能弱。CSSTidy还能支持一些相关属性的合并。】
//工具配置集 加载,显现,检查
4)container.setupFinished();
容器内容bean初始化 ,包括系统默认???
initContainerBeans
2,doGet
实际调用doPost,看到这里最好温习下servlet的doGet方法和doPost的区别
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException
{
doPost(req, resp);
}
3,doPost
在工程中,有个样例Dynamically Text,地址是http://localhost:5050/dwr/simpletext/index.html,当点击按钮时,实际上就是触发了org.directwebremoting.servlet.DwrServlet的下面的方法。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
try
{
webContextBuilder.set(request, response, getServletConfig(), getServletContext(), container);
UrlProcessor processor = container.getBean(UrlProcessor.class);
processor.handle(request, response);
}
finally
{
webContextBuilder.unset();
}
}
第五部分 包拆解 1)org.directwebremoting包
AjaxFilter
2种类型接口
1,认证,日志,安全,
2,延迟
org.directwebremoting.filter.ExtraLatencyAjaxFilter
一般和AjaxFilterChain合作
Container
容器
hub
集线器,提供多个ajax互相操作接口,使得一个界面中多个ajax可以互相调用,参考openajax联盟制定的规则,dwr已经是联盟的会员之一
org.directwebremoting.export包会用到,具体细节不清楚
ScriptBuffer
采用list储存script,功能类似于StringBuffer
ServerContext
服务器端上下文
WebContext
浏览器上下文,好像是每个客户一个(红色表示没仔细看)
auth.js
AuthHandler负责读取这个js文件
细节由CachingFileHandler读取
readResource()将字节符转换为字符串
继承JavaScriptHandler接口,专门处理读取js文件
JavaScriptHandler extends TemplateHandler implements CachingFileHandler implements Handler
CachingFileHandler
读取数据文件,根据req的报头属性判断,如果修改过,那么重新下载,否则不用
下载读取资源文件,由不同的handle处理,这些handle都继承于JavaScriptHandler
如:AuthHandler extends JavaScriptHandler
实际上客户端会将js文件下载到缓存区,这种机制可以使得浏览的时候检查是否js文件内容发生变化。从而重新下载。
dwr20.dtd
org.directwebremoting.impl.DTDEntityResolve处理
dwr20.xsd
实际上没用到
两种xml框架类型文件,可以被parse识别。
engine.js
引擎js,在浏览器缓存中会发现这个文件已经下载到本地。EngineHandler处理,
messages.properties
语言资源文件
由org.directwebremoting.util.Messages读取
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle("org.directwebremoting.messages");
第五部分 包拆解 2)org.directwebremoting.bayeux包
这个包是用来支持bayeux协议的,兼容comet技术,什么是bayeux,请看如下:
下面这些可以实现基于web的实时事件通知的方法:
1.HTTP流:这种方法由存在于不间断的HTTP连接响应中或某个XMLHttpRequest连接中的服务器数据流所组成。
2.HTTP拉取方式:在这种传统的方法中,客户端以用户可定义的时间间隔去检查服务器上的最新数据。这种拉取方式的频率要足够高才能保证很高的数据精确度,
但高频率可能会导致多余的检查,从而导致较高的网络流量。而另一方面,低频率则会导致错过更新的数据。理想地,拉取的时间间隔应该等于服务器状态改变的速度。
3.反转AJAX:服务流应用到AJAX,就是所谓的反转AJAX 或者COMET 。它使得服务器在某事件发生时可以发送消息给客户端,而不需要客户端显式的请求。
目标在于达到状态变化的实时更新。COMET使用了HTTP/1.1中的持续连接的特性。通过HTTP/1.1,除非另作说明,服务器和浏览器之间的TCP连接会一直保持连接状态,
直到其中一方发送了一条明显的“关闭连接”的消息,或者有超时以及网络错误发生。
4.长时间轮询:也就是所谓的异步轮询,这种方式是纯服务器端推送方式和客户端拉取方式的混合。它是基于BAYEUX协议的。这个协议遵循基于主题的发布——订阅机制。
在订阅了某个频道后,客户端和服务器间的连接会保持打开状态,并保持一段事先定义好的时间。如果服务器端没有事件发生,而发生了超时,
服务器端就会请求客户端进行异步重新连接。如果有事件发生,服务器端会发送数据到客户端,然后客户端重新连接
注:Comet:基于 HTTP 长连接的“服务器推”技术
Bayeux是Dojo基金会定义的一个协议。dwr在去年的时候就开始支持该协议,随着dwr项目2008年初加入到Dojo基金会,ajax技术(dwr)和comet(cometd)技术将会融合的更加紧密(说明:dwr,cometd现在都属于dojo基金会下面的项目),本包将来估计会作很大的变化。ajax技术和comet技术的融合,参看使用 Jetty 和 Direct Web Remoting 编写可扩展的 Comet 应用程序,
这个包是dwr外部支持包,并未被其他包引用。
org.directwebremoting.bayeux.BayeuxClient Bayeux协议客户端
org.directwebremoting.bayeux.BayeuxScriptConduit Bayeux协议Script管道
org.directwebremoting.bayeux.BayeuxServletContextAttributeListener 该监听器由web.xml注册,如果要使用推的技术,那么你就必须注册,BayeuxServletContextAttributeListener,会调用BayeuxClient,按comet原理将script推送的客户端。具体细节未作研究。
第五部分 包拆解 3)org.directwebremoting.convert包
转换器包,包下面的类是各种类型的的转换器。
在源码工程中,有两个dwr.xml,一个是系统的,一个是客户自定义的,这两个dwr文件在DwrServlet初始化时加载。在系统dwr.xml(org\directwebremoting\dwr.xml)中,我们可以看到convert元素,convert元素的作用是告诉DWR在服务器端Java 对象表示和序列化的JavaScript之间如何转换数据类型。
java对象被转换成字符串,或者字符串被转换为java对象,这些字符串是按json格式写的,客户端javascript可识别,在客户端接收到后转换成javascript对象。注意:字符串内容没有采用xml格式,而是采用json格式,是因为json格式技术更成熟和快捷。
每个convert都包含两个方法convertInbound,convertOutbound,这两个方法相反
convertInbound 进站转换 将字符串转换为java可识别的对象 针对调用方法的输入参数
convertOutbound 出站转换 将java对象转换为javascript可识别的json格式的字符串 针对调用方法的返回值
第五部分 包拆解 4)org.directwebremoting.create包
在dwr.xml中有,<creator>标签负责公开用于Web远程的类和类的方法,实际上最后调用的就是本包下面的script创建类。
creator类型在1.1版本的时候有如下几种,现在是3.0版本了,我查了下源码,种类好像比下面要多(查XXXCreator有多少个)。
new: 用Java的new关键字创造对象。
none: 它不创建对象,看下面的原因。 (v1.1+)
scripted: 通过BSF使用脚本语言创建对象,例如BeanShell或Groovy。
spring: 通过Spring框架访问Bean。
jsf: 使用JSF的Bean。 (v1.1+)
struts: 使用Struts的FormBean。 (v1.1+)
pageflow: 访问Beehive或Weblogic的PageFlow。 (v1.1+)
creator是在什么时候调用的呢?
实际上,在Serlvet加载的时候有个doPost方法,doPost方法调用handle,handle再调用remotor,remotor最终调用相应的creator,比如NewCreator,creator实际上是执行创建script字符串的工作。
举例来说,我们想在html中某个地方直接显示当前时间的long值,那么我们就可以调用java.util.Date类的getTime()方法。dwr.xml中写法如下:
<dwr>
<allow>
<create creator="new" javascript="JDate">
<param name="class" value="java.util.Date"/>
</create>
<create creator="new" javascript="Demo">
<param name="class" value="your.java.Bean"/>
</create>
</allow>
</dwr>
我们知道这样所有的/dwr/*所有请求都由这个servlet来处理,那么实际上,浏览器加载
<script type='text/javascript' src='dwr/interface/JDate.js'></script>时,实际上是在触发servlet,这次触发属于系统触发,不做事的,只有执行javascript调用方法时如:
function getServerDateTime(){
JDate.getTime(handleGetTime);
}
function handleGetTime(dateTime){
DWRUtil.setValue("date", dateTime);
}
才会触发下面
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException
{
doPost(req, resp);
}
doGet会调用到doPost
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
try
{
webContextBuilder.set(request, response, getServletConfig(), getServletContext(), container);
UrlProcessor processor = container.getBean(UrlProcessor.class);
processor.handle(request, response);
}
finally
{
webContextBuilder.unset();
}
}
在proccessor中我们看到如下代码
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException
{
try
{
String pathInfo = request.getPathInfo();
contextPath = request.getContextPath();
if (pathInfo == null || pathInfo.length() == 0 || "/".equals(pathInfo))
{
response.sendRedirect(contextPath + request.getServletPath() + indexHandlerUrl);
}
else
{
// Loop through all the known URLs
for (Entry<String, Object> entry : urlMapping.entrySet())
{
String url = entry.getKey();
// If this URL matches, call the handler
if (pathInfo.startsWith(url))
{
Handler handler = (Handler) entry.getValue();
handler.handle(request, response);
return;
}
}
notFoundHandler.handle(request, response);
}
}
catch (Exception ex)
{
exceptionHandler.setException(ex);
exceptionHandler.handle(request, response);
}
}
这些handle有多种
1,
/**
* A Handler that supports requests for auth.js
*/
public class AuthHandler extends JavaScriptHandler
2,
/**
* A Handler that supports requests for engine.js
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class EngineHandler extends JavaScriptHandler
3,
/**
* A Handler that supports requests for util.js
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class GiHandler extends JavaScriptHandler
4,
/**
* A handler for interface generation requests
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class InterfaceHandler extends JavaScriptHandler
上面三种都是系统范围的handle,对于我们自己编写的类,应该是触发InterfaceHandler 。
我们再看如下关系
public abstract class CachingFileHandler implements Handler
public abstract class TemplateHandler extends CachingFileHandler
public abstract class JavaScriptHandler extends TemplateHandler
public class InterfaceHandler extends JavaScriptHandler
逐级往上继承
InterfaceHandler,我们看到如下代码,执行顺序-4,里面调用了远程remoter
protected String generateTemplate(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String scriptName = request.getPathInfo();
scriptName = scriptName.replace(interfaceHandlerUrl, "");
String contextServletPath = request.getContextPath() + request.getServletPath();
if (scriptName.endsWith(PathConstants.EXTENSION_JS))
{
scriptName = scriptName.replace(PathConstants.EXTENSION_JS, "");
if (!LocalUtil.isJavaIdentifier(scriptName))
{
log.debug("Throwing at request for script with name: '" + scriptName + "'");
throw new SecurityException("Script names may only contain Java Identifiers");
}
return remoter.generateInterfaceScript(scriptName, contextServletPath);
}
else if (scriptName.endsWith(PathConstants.EXTENSION_SDOC))
{
scriptName = scriptName.replace(PathConstants.EXTENSION_SDOC, "");
if (!LocalUtil.isJavaIdentifier(scriptName))
{
log.debug("Throwing at request for script with name: '" + scriptName + "'");
throw new SecurityException("Script names may only contain Java Identifiers");
}
return remoter.generateInterfaceSDoc(scriptName, contextServletPath);
}
else
{
log.debug("Throwing at request for script with unknown extension: '" + scriptName + "'");
throw new SecurityException("Unknown extension");
}
}
类JavaScriptHandler代码如下,调用了父类TemplateHandler的generateCachableContent方法执行顺序-2
protected String generateCachableContent(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String output = super.generateCachableContent(request, response);
if (debug || compressor == null)
{
return output;
}
try
{
return compressor.compressJavaScript(output);
}
catch (Exception ex)
{
log.warn("Compression system (" + compressor.getClass().getSimpleName() +") failed to compress script", ex);
return output;
}
}
TemplateHandler类代码如下执行顺序-3,里面调用了generateTemplate方法
protected String generateCachableContent(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String template = generateTemplate(request, response);
Map<String, String> replace = getSearchReplacePairs();
if (replace != null)
{
for (Map.Entry<String, String> entry : replace.entrySet())
{
String search = entry.getKey();
if (template.contains(search))
{
template = template.replace(search, entry.getValue());
}
}
}
return template;
}
/** *//**
* Generate a template to undergo search and replace processing according to
* the search and replace pairs from {@link #getSearchReplacePairs()}.
* @param request The HTTP request data
* @param response Where we write the HTTP response data
* @return A template string containing ${} sections to be replaced
*/
protected abstract String generateTemplate(HttpServletRequest request, HttpServletResponse response) throws IOException;
CachingFileHandler类代码如下,handle方法里面调用了generateCachableContent()方法
执行顺序-1
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException
{
if (isUpToDate(request))
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
String output;
synchronized (scriptCache)
{
String url = request.getPathInfo();
output = scriptCache.get(url);
if (output == null)
{
output = generateCachableContent(request, response);
}
scriptCache.put(url, output);
}
response.setContentType(mimeType);
response.setDateHeader(HttpConstants.HEADER_LAST_MODIFIED, CONTAINER_START_TIME);
response.setHeader(HttpConstants.HEADER_ETAG, ETAG);
PrintWriter out = response.getWriter();
out.println(output);
}
代码有点绕
我们看到在InterfaceHandler类中
有一行代码如下:
remoter.generateInterfaceScript(scriptName, contextServletPath);
如果按例子,对于声明在dwr.xml中的JDate类,scriptName应该是JDate,contextServletPath应该是'dwr/interface/'
执行generateInterfaceScript方法生成相应的字符串,对应于JDate.js
在CachingFileHandler类中的方法handle最后一行如下,
out.println(output);
在这个地方,将返回内容输出到客户端。
第六部分 DWR原理
通过前面五章学习,对DWR整体有所了解,但是我仍然对核心细节不是很清楚。我最后的办法就是单步调试,调试之后将所有东西串一串,DWR的原理就清楚了,搞清楚核心生产线,其他全是辅助的,没必要再分析了。老外聪明啊,服。
我们以DWR的第一个样例为例Dynamically Text
1,在index.html里面我们嵌入
<script type='text/javascript' src='../dwr/engine.js'> </script>
<script type='text/javascript' src='../dwr/util.js'> </script>
<script type='text/javascript' src='../dwr/interface/Demo.js'> </script>
前面两个都是DWR系统默认需要加载的,Demo.js是Demo.java所对应的。按理论,只要在web.xml和dwr.xml配置好,那么我们就可以在客户端操作Demo.js,类似于操作服务器端的Demo.java。
HTML source:
<p>
Name:
<input type="text" id="demoName"/>
<input value="Send" type="button" οnclick="update()"/>
<br/>
Reply: <span id="demoReply"></span>
</p>
Javascript source:
function update() {
var name = dwr.util.getValue("demoName");
Demo.sayHello(name, function(data) {
dwr.util.setValue("demoReply", data);
});
}
Java source:
package org.getahead.dwrdemo.simpletext;
public class Demo {
public String sayHello(String name) {
return "Hello, " + name;
}
}
dwr.xml
<?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>
<create creator="new" javascript="Demo">
<param name="class" value="org.getahead.dwrdemo.simpletext.Demo"/>
</create>
</allow>
</dwr>
在浏览器第一次加载本页面时,浏览器第一步发现<script type='text/javascript' src='../dwr/engine.js'> </script>,根据web.xml关于dwr的配置,系统会激发Servlet的POST方法,向客户端输出engine.js文件流,这个文件你可以在IE的缓存里面发现,当然,程序会在最后输出一刻比较客户端是否已经存在该文件,如果要输出的文件流大小和和该文件大小一致,就不输出了,同样util.js、Demo.js也是这么输出的。那么看看Demo.js文件里面的内容如下:
if (typeof this['Person'] != 'function') {
function Person() {
this.address = null;
this.phoneNumber = null;
this.name = null;
this.id = 0;
this.salary = 0;
}
}
// Provide a default path to dwr.engine
if (typeof this['dwr'] == 'undefined') this.dwr = {};
if (typeof dwr['engine'] == 'undefined') dwr.engine = {};
if (typeof this['Demo'] == 'undefined') this.Demo = {};
Demo._path = '/dwr/dwr';
Demo.sayHello = function(p0, callback) {
return dwr.engine._execute(Demo._path, 'Demo', 'sayHello', p0, callback);
};
Demo.getInclude = function(callback) {
return dwr.engine._execute(Demo._path, 'Demo', 'getInclude', callback);
};
这样我们就知道了,实际上Demo.java类的sayHello方法已经被解释到Demo.js中了,只不过后面的调用还不是很清楚而已。
继续!
public String sayHello(String name) {
return "Hello, " + name;
}
翻译成
Demo._path = '/dwr/dwr';
Demo.sayHello = function(p0, callback) {
return dwr.engine._execute(Demo._path, 'Demo', 'sayHello', p0, callback);
};
在调用engine.execute()方法时,最终采用无刷新访问服务器技术。
采用该技术的关键问题是两个参数,一个是url,往什么地方发送;一个是doc,发送什么内容
通过跟踪engine.js我们知道
url=/dwr/dwr/call/plaincall/Demo.sayHello.dwr
发送内容如下:
callCount=1
windowName=DWR-442B435899
c0-scriptName=Demo
c0-methodName=sayHello
c0-id=0
c0-param0=string:Joe
batchId=1
page=/dwr/simpletext/index.html
httpSessionId=
scriptSessionId=1CC3A.0A3
实际上url=/dwr/dwr/call/plaincall/Demo.sayHello.dwr,转向的是DwrServlet,DwrServlet实际上获取了两部分信息,一部分是url,根据这个能够解析到我们的目标java类、方法,另外一部分是发送信息包,实际上这就是SayHello所需要的参数输入信息,最后,返回数据,这些数据格式通过response返回,在engine.js中对应于return batch.reply;通过解析返回对象,采用javascirpt将数据动态刷新到页面。
至此,dwr的核心原理,基本清晰
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。