DWR 3 反转Ajax

1.Reverse Ajax
简介
小试身手
细粒度控制
Early Closing Mode(默认模式)
Full Streaming Mode(IE不支持)
Polling Mode
涉及到的一些知识
ScriptSession生命周期
non-dwr thread,非DWR线程
在non-dwr thread中获取ScriptSessionManager
Browser API
附上简单的代码实现

1.Reverse Ajax

简介

反转Ajax是DWR 2.0里最大的一个新特性,让你能够异步的从服务端推送数据到客户端(浏览器); 
当初web在设计的时候就不允许web服务器主动跟web浏览器建立连接(只能是浏览器主动去服务端请求数据),所以对于浏览器来说从服务端得到及时的数据可能有点棘手。DWR支持三种方式从服务端推送数据到客户端(服务器推,高大上,也就是反转Ajax):Piggyback, Polling和Comet. 
Polling:这种方式类似于我们经常使用的轮询的方式,每隔几秒中去动态请求一次数据,只不过dwr帮我们去完成这个每隔。。请求一次数据的任务,默认dwr好像是5秒去请求一次,这种方式简单,但是对于实时的数据来说不太合适,实现方式类似我们在web页面上写一个js或者用ajax每隔一段时间去请求一下服务器,然后从后端服务中带回数据。 
Commet:它是一种基于 HTTP 长连接的一种服务器推送技术,它不需要浏览器每次去连接请求,所以它的延迟非常低,但是它对服务器的负载带来很大压力,一些实时系统都是使用这种方式推送,commet方式也不可能一直保持连接状态,由于各种各样的原因可能会断开连接,所以它也会根据已有的规则来重连接。 
Piggyback:这种方式就是服务器会记录你的更新能容,当你的web去服务器请求的是否它会把从上次请求到这次请求之间的所有更新一起返回给web页面,上面两种方式相对这种方式可能对网络依赖比较强,Piggyback因为不是实时从服务端发送数据到客户端,而是等待下次客户端请求才一次发送给客户端,所以数据延时相对较高,这种方式一般不需要其它配置就可以使用。 
DWR允许你使用上面的任意一种方式来推送,只需要稍微修改一下配置文件即可。commet与polling是两个极端,一个是长时间连接一个是长时间处于断开等到客户端去connect,默认这两种方式是没有打开的,因为对服务器的负载太大,可以手动配置打开。

小试身手

要使用反转ajax只需要简单的两步。

  • 第一步就是开启服务器端的主动反向ajax,这是为了安全期间的服务器端配置,只需要在你的web.xml中加入下面代码:
  
  
  
  
  1. <servlet>
  2. <servlet-name>dwr-invoker</servlet-name>
  3. <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
  4. <init-param>
  5. <param-name>activeReverseAjaxEnabled</param-name>
  6. <param-value>true</param-value>
  7. </init-param>
  8. ...
  9. </servlet>
  • 第二步,在你的web页面中请求反向ajax,开始一个polling或者commet的循环周期,只需要在你的页面加载完毕后加入一下代码:
  
  
  
  
  1. dwr.engine.setActiveReverseAjax(true);
  2. 给个例子:
  3. <body onload="dwr.engine.setActiveReverseAjax(true);dwr.engine.setNotifyServerOnPageUnload(true);">

这样理论上你的项目就已经具备了反向ajax的功能,下面介绍一些细粒度的控制。

细粒度控制

在上面提到,piggyback一般不需要过多的配置即可使用,它属于被动模式,而polling与commet则属于主动模式,DWR主要就是这两种工作模式,被动模式没什么配置我们不在说,如果没有下面的配置,默认就是被动模式,下面主要了解一下主动模式也就是Active Reverse Ajax,而主动模式又可以细分为三种:

  • Early Closing Mode(默认模式)
  • Full Streaming Mode(IE不支持,日了狗了)
  • Polling Mode

Early Closing Mode(默认模式)

在DWR2.04版本以前默认模式还是Full Streaming Mode(我们姑且给它叫FSM简写之),但之后默认就改为了Early Closing Mode(ECM),在FSM模式下这种长连接的模式会增加服务器的压力,而现在ECM这种模式下如果没有数据要输出到浏览器那么它保持60秒后会关闭,一旦有数据输出,dwr会在关闭链接前再保持链接一个配置时间maxWaitAfterWrite让数据发送出去,然后断开连接,再重新启一个连接等待数据。

  
  
  
  
  1. <init-param>
  2. <param-name>maxWaitAfterWrite</param-name>
  3. <param-value>1000</param-value>
  4. </init-param>

Full Streaming Mode(IE不支持)

这种模式是响应最快的一种模式,因为它连接一次,断开一次,它会每隔1分钟去检测一下浏览器是否关闭,ie下无法使用,默认切换到了Early Closing Mode,但是在Early Closing Mode情况下,如果你有大量的浏览器–服务器连接存在,它们可能在同一时间尝试重连接,这种情况下就会很糟糕,那么可以尝试使用Full Streaming Mode这种模式,它只会关闭一次。。。,它的配置很简单:

  
  
  
  
  1. <init-param>
  2. <param-name>maxWaitAfterWrite</param-name>
  3. <param-value>-1</param-value>
  4. </init-param>

Polling Mode

如果你认为长时间持有连接是不明智的,那么你可以使用Polling Mode这种模式,配置如下:

  
  
  
  
  1. <init-param>
  2. <param-name>org.directwebremoting.extend.ServerLoadMonitor</param-name>
  3. <param-value>org.directwebremoting.impl.PollingServerLoadMonitor</param-value>
  4. </init-param>

默认轮询时间是5秒,你可以通过下面参数配置:

  
  
  
  
  1. <init-param>
  2. <param-name>disconnectedTime</param-name>
  3. <param-value>60000</param-value>
  4. </init-param>

涉及到的一些知识

ScriptSession生命周期

当你把/dwr/engine.js放到你的项目中的时候,其实这个ScriptSession生命周期已经开始了,默认使用org.directwebremoting.impl.DefaultScriptSessionManager来管理整个生命周期,你可以在页面中加入这句代码来通知DefaultScriptSessionManager,当页面关闭时让它的生命周期失效。

  
  
  
  
  1. dwr.engine.setNotifyServerOnPageUnload(true);
  2. 例如:
  3. <body onload="dwr.engine.setActiveReverseAjax(true);dwr.engine.setNotifyServerOnPageUnload(true);">

但是当浏览器直接关闭时可能导致短暂的延迟,如果浙对你的项目来说是不理想的,你可以设置第二个参数:

  
  
  
  
  1. dwr.engine.setNotifyServerOnPageUnload(true, true);

这样可能导致浏览器关闭与页面卸载同时发生时卸载不一致的情况。 
如果没有设置setNotifyServerOnPageUnload为true或者失效,那么默认使用服务器端的session失效时间,默认是5分钟,这个可以配置。

non-dwr thread,非DWR线程

什么是dwr thread呢?什么又是non-der thread呢?api上是这么说的:Non-dwr threads have no reference to the dwr-thread that created them.在non-dwr thread中使用WebContextFactory().get().getScriptSession()得到scriptSession时会返回null,它只能在dwr thread中使用。
个人理解:dwr thread就是说是dwr主动发起的线程,比如说在web页面主动通过dwr调用java后端代码,这些肯定是一个dwr thread;其它的比如说在后台想访问前端的web页面的js代码,这就是non-dwr thread,因为这个线程是你自己业务线程,它想要访问前端js。例如(这是dwr thread):前端

  
  
  
  
  1. function getTime(){
  2. Info.getTime(function(data){
  3. alert(data)
  4. });
  5. }

后端:

  
  
  
  
  1. public String getTime(){
  2. ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
  3. scriptSession.setAttribute("date",new Date());
  4. String format = DateUtil.format(new Date(), DateUtil.DEFAULT);
  5. return format;
  6. }

在non-dwr thread中获取ScriptSessionManager

  
  
  
  
  1. Container container = ServerContextFactory.get().getContainer();
  2. ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);

Browser API

Browser API包含很多有用的方法来更新浏览器页面,其中有一些方法需要带一个ScriptSessionFilter,它允许你根据scriptSession来处理特定的scriptSession。 
看一下api里面的例子:如何针对不同的用户使用带ScriptSessionFilter的Browser API

  • Implement a ScriptSessionFilter
  
  
  
  
  1. public class TestScriptSessionFilter implements ScriptSessionFilter
  2. {
  3. public TestScriptSessionFilter(String attributeName)
  4. {
  5. this.attributeName = attributeName;
  6. }
  7. /* (non-Javadoc)
  8. * @see org.directwebremoting.ScriptSessionFilter#match(org.directwebremoting.ScriptSession)
  9. */
  10. public boolean match(ScriptSession session)
  11. {
  12. //这里写你的判断逻辑,返回true或者false
  13. Object check = session.getAttribute(attributeName);
  14. return (check != null && check.equals(Boolean.TRUE));
  15. }
  16. private String attributeName;
  17. }
  • Browser API方法,3种方案
  
  
  
  
  1. ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName);
  2. Browser.withPageFiltered(page, filter, new Runnable()
  3. {
  4. public void run()
  5. {
  6. // Call a method on DWR's Util class which sets the value on an element on your HTML page with a id of "divID".
  7. Util.setValue("divID", "value of div");
  8. }
  9. });
  10. Or call a named function:ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName);
  11. Browser.withPageFiltered(page, filter, new Runnable()
  12. {
  13. public void run()
  14. {
  15. // Call a named function from your html page. Note - The ScriptsSessions.addFunctionCall will only
  16. // send the function call to ScriptSessions matching TestScriptSessionFilter.
  17. ScriptSessions.addFunctionCall("yourJavaScriptFunctionName", arg1, arg2, etc.);
  18. }
  19. });
  20. Or add some arbitrary script:ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName);
  21. Browser.withPageFiltered(page, filter, new Runnable()
  22. {
  23. public void run()
  24. {
  25. // Add script which will modify the document.title. on your html page.
  26. ScriptSessions.addScript(("document.title = 'My new title, from DWR reverse AJAX!';"));
  27. }
  28. });
  • 设置SciptSession的Attribute 
    在使用filter的match方法会轮询目前已经存在的scriptSession来循环调用filter.match(session),所以应该在调用filter之前先设置scriptSession对象,例如设置attribute。 
    第一种方法:dwr thread方式
  
  
  
  
  1. // Add the attribute into the ScriptSession sometime before using the Filter.
  2. ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
  3. String attributeName = "attr";
  4. scriptSession.setAttribute(attributeName, true);
  5. 完整的是这样:
  6. /**
  7. * This method should be remoted via DWR and generally called before reverse ajax is initialized.
  8. * You may choose to call this method when your page which uses reverse AJAX is initialized, then
  9. * in your callback function you may initialize reverse AJAX (dwr.engine.setActiveReverseAjax(true);)
  10. * and be certain that the
  11. */
  12. public void remoteMethod() {
  13. String value = "someValue"; // this may come from the HttpSession for example
  14. ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
  15. scriptSession.setAttribute("key", value);
  16. }
  17. //这种方法必须dwr thread来完成,上面有讲到这个,可以在页面load完成的是否调用后端代码来设置这个session,还有使用non-dwr threa的方式,我们慢慢谈。

第二种方法:non dwr thread方式,使用 ScriptSessionListener。 
ScriptSessionListener能够监听到ScriptSession的创建与销毁的动作

  
  
  
  
  1. Container container = ServerContextFactory.get().getContainer();
  2. ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
  3. ScriptSessionListener listener = new ScriptSessionListener() {
  4. public void sessionCreated(ScriptSessionEvent ev) {
  5. HttpSession session = WebContextFactory.get().getSession();
  6. String userId = (String) session.getAttribute("userId");
  7. ev.getSession().setAttribute("userId", userId);
  8. }
  9. public void sessionDestroyed(ScriptSessionEvent ev) { }
  10. };
  11. manager.addScriptSessionListener(listener);

必须注意的是:ScriptSessionListener必须在DWR初始化后才能开始工作,有两种方式实现 
一种是new 一个class extend DwrServlet,然后web.xml中如下:

  
  
  
  
  1. <servlet>
  2. <display-name>DWR Servlet</display-name>
  3. <servlet-name>dwr-invoker</servlet-name>
  4. <servlet-class>com.supconit.jtjc.dwr.DefaultDwrServlet</servlet-class>
  5. 。。。
  6. java代码如下:
  7. public class DefaultDwrServlet extends DwrServlet{
  8. @Override
  9. public void init(ServletConfig servletConfig) throws ServletException {
  10. super.init(servletConfig);
  11. Container container = ServerContextFactory.get().getContainer();
  12. ScriptSessionManager sessionManager = container.getBean(ScriptSessionManager.class);
  13. ScriptSessionListener scriptSessionListener = new ScriptSessionListener(){
  14. @Override
  15. public void sessionCreated(ScriptSessionEvent scriptSessionEvent) {
  16. HttpSession session = WebContextFactory.get().getSession();
  17. AuthenticationInfo AuthenticationInfo =(AuthenticationInfo) session.getAttribute("hc.safety.authc.AuthenticationInfo.SESSION_KEY");
  18. Set<String> roles = AuthenticationInfo.getRoles();
  19. scriptSessionEvent.getSession().setAttribute("roles", roles);
  20. }
  21. @Override
  22. public void sessionDestroyed(ScriptSessionEvent scriptSessionEvent) {
  23. }
  24. };
  25. sessionManager.addScriptSessionListener(scriptSessionListener);
  26. }
  27. }

第二种就是新建一个serverlet实现那一段代码,然后在web.xml中给这个serverlet配置一个load-on-startup,它的值要比DWR servlet的值要大。

附上简单的代码实现

  
  
  
  
  1. /**
  2. * Created with IDEA .
  3. * @date:2016-04-07 13:32:30.
  4. * @description:不指定页面,所有
  5. */
  6. public void sendAlermInfo(final String content,final String bjxl){
  7. Runnable run = new Runnable(){
  8. private ScriptBuffer script = new ScriptBuffer();
  9. public void run() {
  10. //设置要调用的 js及参数
  11. script.appendCall("show" , content);
  12. //得到所有ScriptSession
  13. Collection<ScriptSession> sessions = Browser.getTargetSessions();
  14. //遍历每一个ScriptSession
  15. for (ScriptSession scriptSession : sessions){
  16. scriptSession.addScript( script);
  17. }
  18. }
  19. };
  20. //执行推送
  21. Browser. withAllSessionsFiltered(new ScriptSessionFilter() {
  22. @Override
  23. public boolean match(ScriptSession scriptSession) {
  24. return filterMatch(scriptSession, bjxl);
  25. }
  26. }, run);
  27. }
  28. /**
  29. * Created with IDEA .
  30. * @date:2016-04-07 13:32:30.
  31. * @description:指定单个页面
  32. */
  33. public void sendRealTimeAlermInfo(final String content,final String bjxl){
  34. //这个url是会被spring mvc拦截然后转到你所要更新的界面
  35. Browser.withPageFiltered("/jtjc_web/realtimeAlerm/alermList", new ScriptSessionFilter() {
  36. @Override
  37. public boolean match(ScriptSession scriptSession) {
  38. return filterMatch(scriptSession, bjxl);
  39. }
  40. }, new Runnable() {
  41. private ScriptBuffer script = new ScriptBuffer();
  42. @Override
  43. public void run() {
  44. //设置要调用的 js及参数
  45. script.appendCall("loadRealTimeData", content);
  46. //得到所有ScriptSession
  47. Collection<ScriptSession> sessions = Browser.getTargetSessions();
  48. //遍历每一个ScriptSession
  49. for (ScriptSession scriptSession : sessions) {
  50. scriptSession.addScript(script);
  51. }
  52. }
  53. });
  54. }
  55. /**
  56. * Created with IDEA .
  57. * @date:2016-04-20 16:48:09.
  58. * @description:判断是否符合filter
  59. */
  60. private boolean filterMatch(ScriptSession scriptSession, String bjxl) {
  61. Set<String> roles = (Set<String>)scriptSession.getAttribute("roles");
  62. List<String> bjxlList=new ArrayList<String>();
  63. for (String role : roles) {
  64. /* List<IsdcAlermType> isdcAlermTypes=isdcAlermType.getByRoleCode(role);
  65. for (IsdcAlermType alermType : isdcAlermTypes) {
  66. bjxlList.add(alermType.getDictionaryDataId());
  67. }*/
  68. }
  69. if (bjxlList.contains(bjxl)) {
  70. return true;
  71. } else {
  72. return false;
  73. }
  74. }

你可能感兴趣的:(DWR,服务器推,3,反转Ajax)