反转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只需要简单的两步。
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
...
</servlet>
dwr.engine.setActiveReverseAjax(true);
给个例子:
<body onload="dwr.engine.setActiveReverseAjax(true);dwr.engine.setNotifyServerOnPageUnload(true);">
这样理论上你的项目就已经具备了反向ajax的功能,下面介绍一些细粒度的控制。
在上面提到,piggyback一般不需要过多的配置即可使用,它属于被动模式,而polling与commet则属于主动模式,DWR主要就是这两种工作模式,被动模式没什么配置我们不在说,如果没有下面的配置,默认就是被动模式,下面主要了解一下主动模式也就是Active Reverse Ajax,而主动模式又可以细分为三种:
在DWR2.04版本以前默认模式还是Full Streaming Mode(我们姑且给它叫FSM简写之),但之后默认就改为了Early Closing Mode(ECM),在FSM模式下这种长连接的模式会增加服务器的压力,而现在ECM这种模式下如果没有数据要输出到浏览器那么它保持60秒后会关闭,一旦有数据输出,dwr会在关闭链接前再保持链接一个配置时间maxWaitAfterWrite让数据发送出去,然后断开连接,再重新启一个连接等待数据。
<init-param>
<param-name>maxWaitAfterWrite</param-name>
<param-value>1000</param-value>
</init-param>
这种模式是响应最快的一种模式,因为它连接一次,断开一次,它会每隔1分钟去检测一下浏览器是否关闭,ie下无法使用,默认切换到了Early Closing Mode,但是在Early Closing Mode情况下,如果你有大量的浏览器–服务器连接存在,它们可能在同一时间尝试重连接,这种情况下就会很糟糕,那么可以尝试使用Full Streaming Mode这种模式,它只会关闭一次。。。,它的配置很简单:
<init-param>
<param-name>maxWaitAfterWrite</param-name>
<param-value>-1</param-value>
</init-param>
如果你认为长时间持有连接是不明智的,那么你可以使用Polling Mode这种模式,配置如下:
<init-param>
<param-name>org.directwebremoting.extend.ServerLoadMonitor</param-name>
<param-value>org.directwebremoting.impl.PollingServerLoadMonitor</param-value>
</init-param>
默认轮询时间是5秒,你可以通过下面参数配置:
<init-param>
<param-name>disconnectedTime</param-name>
<param-value>60000</param-value>
</init-param>
当你把/dwr/engine.js放到你的项目中的时候,其实这个ScriptSession生命周期已经开始了,默认使用org.directwebremoting.impl.DefaultScriptSessionManager来管理整个生命周期,你可以在页面中加入这句代码来通知DefaultScriptSessionManager,当页面关闭时让它的生命周期失效。
dwr.engine.setNotifyServerOnPageUnload(true);
例如:
<body onload="dwr.engine.setActiveReverseAjax(true);dwr.engine.setNotifyServerOnPageUnload(true);">
但是当浏览器直接关闭时可能导致短暂的延迟,如果浙对你的项目来说是不理想的,你可以设置第二个参数:
dwr.engine.setNotifyServerOnPageUnload(true, true);
这样可能导致浏览器关闭与页面卸载同时发生时卸载不一致的情况。
如果没有设置setNotifyServerOnPageUnload为true或者失效,那么默认使用服务器端的session失效时间,默认是5分钟,这个可以配置。
什么是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):前端
function getTime(){
Info.getTime(function(data){
alert(data)
});
}
后端:
public String getTime(){
ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
scriptSession.setAttribute("date",new Date());
String format = DateUtil.format(new Date(), DateUtil.DEFAULT);
return format;
}
Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
Browser API包含很多有用的方法来更新浏览器页面,其中有一些方法需要带一个ScriptSessionFilter,它允许你根据scriptSession来处理特定的scriptSession。
看一下api里面的例子:如何针对不同的用户使用带ScriptSessionFilter的Browser API
public class TestScriptSessionFilter implements ScriptSessionFilter
{
public TestScriptSessionFilter(String attributeName)
{
this.attributeName = attributeName;
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSessionFilter#match(org.directwebremoting.ScriptSession)
*/
public boolean match(ScriptSession session)
{
//这里写你的判断逻辑,返回true或者false
Object check = session.getAttribute(attributeName);
return (check != null && check.equals(Boolean.TRUE));
}
private String attributeName;
}
ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName);
Browser.withPageFiltered(page, filter, new Runnable()
{
public void run()
{
// Call a method on DWR's Util class which sets the value on an element on your HTML page with a id of "divID".
Util.setValue("divID", "value of div");
}
});
Or call a named function:ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName);
Browser.withPageFiltered(page, filter, new Runnable()
{
public void run()
{
// Call a named function from your html page. Note - The ScriptsSessions.addFunctionCall will only
// send the function call to ScriptSessions matching TestScriptSessionFilter.
ScriptSessions.addFunctionCall("yourJavaScriptFunctionName", arg1, arg2, etc.);
}
});
Or add some arbitrary script:ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName);
Browser.withPageFiltered(page, filter, new Runnable()
{
public void run()
{
// Add script which will modify the document.title. on your html page.
ScriptSessions.addScript(("document.title = 'My new title, from DWR reverse AJAX!';"));
}
});
// Add the attribute into the ScriptSession sometime before using the Filter.
ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
String attributeName = "attr";
scriptSession.setAttribute(attributeName, true);
完整的是这样:
/**
* This method should be remoted via DWR and generally called before reverse ajax is initialized.
* You may choose to call this method when your page which uses reverse AJAX is initialized, then
* in your callback function you may initialize reverse AJAX (dwr.engine.setActiveReverseAjax(true);)
* and be certain that the
*/
public void remoteMethod() {
String value = "someValue"; // this may come from the HttpSession for example
ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
scriptSession.setAttribute("key", value);
}
//这种方法必须dwr thread来完成,上面有讲到这个,可以在页面load完成的是否调用后端代码来设置这个session,还有使用non-dwr threa的方式,我们慢慢谈。
第二种方法:non dwr thread方式,使用 ScriptSessionListener。
ScriptSessionListener能够监听到ScriptSession的创建与销毁的动作
Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
ScriptSessionListener listener = new ScriptSessionListener() {
public void sessionCreated(ScriptSessionEvent ev) {
HttpSession session = WebContextFactory.get().getSession();
String userId = (String) session.getAttribute("userId");
ev.getSession().setAttribute("userId", userId);
}
public void sessionDestroyed(ScriptSessionEvent ev) { }
};
manager.addScriptSessionListener(listener);
必须注意的是:ScriptSessionListener必须在DWR初始化后才能开始工作,有两种方式实现
一种是new 一个class extend DwrServlet,然后web.xml中如下:
<servlet>
<display-name>DWR Servlet</display-name>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>com.supconit.jtjc.dwr.DefaultDwrServlet</servlet-class>
。。。
java代码如下:
public class DefaultDwrServlet extends DwrServlet{
@Override
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager sessionManager = container.getBean(ScriptSessionManager.class);
ScriptSessionListener scriptSessionListener = new ScriptSessionListener(){
@Override
public void sessionCreated(ScriptSessionEvent scriptSessionEvent) {
HttpSession session = WebContextFactory.get().getSession();
AuthenticationInfo AuthenticationInfo =(AuthenticationInfo) session.getAttribute("hc.safety.authc.AuthenticationInfo.SESSION_KEY");
Set<String> roles = AuthenticationInfo.getRoles();
scriptSessionEvent.getSession().setAttribute("roles", roles);
}
@Override
public void sessionDestroyed(ScriptSessionEvent scriptSessionEvent) {
}
};
sessionManager.addScriptSessionListener(scriptSessionListener);
}
}
第二种就是新建一个serverlet实现那一段代码,然后在web.xml中给这个serverlet配置一个load-on-startup,它的值要比DWR servlet的值要大。
/**
* Created with IDEA .
* @date:2016-04-07 13:32:30.
* @description:不指定页面,所有
*/
public void sendAlermInfo(final String content,final String bjxl){
Runnable run = new Runnable(){
private ScriptBuffer script = new ScriptBuffer();
public void run() {
//设置要调用的 js及参数
script.appendCall("show" , content);
//得到所有ScriptSession
Collection<ScriptSession> sessions = Browser.getTargetSessions();
//遍历每一个ScriptSession
for (ScriptSession scriptSession : sessions){
scriptSession.addScript( script);
}
}
};
//执行推送
Browser. withAllSessionsFiltered(new ScriptSessionFilter() {
@Override
public boolean match(ScriptSession scriptSession) {
return filterMatch(scriptSession, bjxl);
}
}, run);
}
/**
* Created with IDEA .
* @date:2016-04-07 13:32:30.
* @description:指定单个页面
*/
public void sendRealTimeAlermInfo(final String content,final String bjxl){
//这个url是会被spring mvc拦截然后转到你所要更新的界面
Browser.withPageFiltered("/jtjc_web/realtimeAlerm/alermList", new ScriptSessionFilter() {
@Override
public boolean match(ScriptSession scriptSession) {
return filterMatch(scriptSession, bjxl);
}
}, new Runnable() {
private ScriptBuffer script = new ScriptBuffer();
@Override
public void run() {
//设置要调用的 js及参数
script.appendCall("loadRealTimeData", content);
//得到所有ScriptSession
Collection<ScriptSession> sessions = Browser.getTargetSessions();
//遍历每一个ScriptSession
for (ScriptSession scriptSession : sessions) {
scriptSession.addScript(script);
}
}
});
}
/**
* Created with IDEA .
* @date:2016-04-20 16:48:09.
* @description:判断是否符合filter
*/
private boolean filterMatch(ScriptSession scriptSession, String bjxl) {
Set<String> roles = (Set<String>)scriptSession.getAttribute("roles");
List<String> bjxlList=new ArrayList<String>();
for (String role : roles) {
/* List<IsdcAlermType> isdcAlermTypes=isdcAlermType.getByRoleCode(role);
for (IsdcAlermType alermType : isdcAlermTypes) {
bjxlList.add(alermType.getDictionaryDataId());
}*/
}
if (bjxlList.contains(bjxl)) {
return true;
} else {
return false;
}
}