1. 实现原理
当主页加载完毕时候,一个ajax(jquery)请求到servlet端, servlet启动异步处理上下文AsyncContext,然后请求一直等待,直到上下文AsyncContext调用complete()方法,或者这个请求timeout,这个请求才会返回到UI
,这样一次连接就结束。请求返回到UI后,ajax马上又发送连接请求到Servlet,这样反反复复进行这个操作
2. 实现代码
Servlet端实现
//需要导入该类包名 import java.io.IOException; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @WebServlet(urlPatterns = { "/servlet/asyn" }, asyncSupported = true) public class AsynServlet extends HttpServlet{ private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); String timeoutStr = request.getParameter("timeout"); long timeout; if (StringUtils.isNumeric(timeoutStr)) { timeout = Long.parseLong(timeoutStr); } else { // 设置10分钟 timeout = 10 * 60 * 1000; } final HttpServletResponse finalResponse = response; final AsyncContext ac = request.startAsync(request, finalResponse); // 设置成长久链接 ac.setTimeout(timeout); ac.addListener(new AsyncListener() { public void onComplete(AsyncEvent event) throws IOException { log.info("onComplete Event!"); ServletService.getInstance().removeAsyncContext(ac); } public void onTimeout(AsyncEvent event) throws IOException { log.info("onTimeout Event!"); ServletService.getInstance().removeAsyncContext(ac); ac.complete(); } public void onError(AsyncEvent event) throws IOException { ServletService.getInstance().removeAsyncContext(ac); ac.complete(); } public void onStartAsync(AsyncEvent event) throws IOException { log.info("onStartAsync Event!"); } }); ServletService.getInstance().addAsyncContext(ac); } }发消息业务方法
//需要导入该类包名 import java.io.IOException; import java.io.PrintWriter; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import javax.servlet.AsyncContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class ServletService { //异步Servlet上下文队列. private final Map<Integer, AsyncContext> ASYNC_CONTEXT_MAP = new ConcurrentHashMap<Integer, AsyncContext>(); //消息队列. private final BlockingQueue<TextMessage> TEXT_MESSAGE_QUEUE = new LinkedBlockingQueue<TextMessage>(); //单一实例. private static ServletService instance = new ServletService(); //构造函数,创建发送消息的异步线程. private ServletService() { new Thread(this.notifierRunnable).start();//线程发发消息给多个用户 } //单一实例. public static ServletService getInstance() { return instance; } /** * * 注册异步Servlet上下文. * * @param asyncContext * 异步Servlet上下文. */ public void addAsyncContext(final AsyncContext asyncContext) { HttpServletRequest req = (HttpServletRequest) asyncContext.getRequest(); User user = (User) req.getSession().getAttribute("loginuser"); if (null!=user) { ASYNC_CONTEXT_MAP.put(user.getId(), asyncContext); } } /** * * 删除异步Servlet上下文. * * @param asyncContext * 异步Servlet上下文. */ public void removeAsyncContext(final AsyncContext asyncContext) { HttpServletRequest req = (HttpServletRequest) asyncContext.getRequest(); User user = (User) req.getSession().getAttribute("loginuser"); if (null!=user) { ASYNC_CONTEXT_MAP.remove(user.getId()); } } /** * * 发送消息到异步线程,最终输出到http response 流 . * * @param text 发送给客户端的消息. * */ public void putMessage(final int userId, final String text) { try { TextMessage tm = new TextMessage(userId, text); TEXT_MESSAGE_QUEUE.add(tm); } catch (Exception ex) { throw new RuntimeException(ex); } } public void putMessage(final TextMessage tm) { try { TEXT_MESSAGE_QUEUE.add(tm); } catch (Exception ex) { throw new RuntimeException(ex); } } public boolean pushMessage(final TextMessage tm) { boolean result = false; AsyncContext ac = ASYNC_CONTEXT_MAP.get(tm.getUserId()); try { if (null != ac) { write(ac, tm.getText()); result = true; } } catch (Exception e) { ASYNC_CONTEXT_MAP.remove(tm.getUserId()); log.info(e); } return result; } /** * * 异步线程,当消息队列中被放入数据,将释放take方法的阻塞,将数据发送到http response流上. * 该方法暂时没用,用于并发测试 */ private Runnable notifierRunnable = new Runnable() { public void run() { boolean done = false; while (!done) { try { final TextMessage tm = TEXT_MESSAGE_QUEUE.take();//当消息队列没有数据时候,线程执行到这里就会被阻塞 if (tm.getUserId()==0) {//发送给所有人 for (Entry<Integer, AsyncContext> entry : ASYNC_CONTEXT_MAP.entrySet()) { try { write(entry.getValue(), tm.getText()); } catch (IOException ex) { log.info(ex); } } } else { pushMessage(tm); } Thread.sleep(100);//暂停100ms,停止的这段时间让用户有足够时间连接到服务器 } catch (InterruptedException iex) { done = true; log.info(iex); } } } }; private void write(AsyncContext ac, String text) throws IOException { PrintWriter acWriter = ac.getResponse().getWriter(); acWriter.write(text); acWriter.flush(); acWriter.close(); ac.complete(); } }发消息实体对象
public final class TextMessage { private final int userId; private final String text; public TextMessage(final int userId, final String text) { super(); this.userId = userId; this.text = text; } public int getUserId() { return userId; } public String getText() { return text; } }
3. 前端js实现(重点)
主页要引入下面脚本代码,当主页加载完毕后,就会发送ajax请求到servet/asyn.do,然后就建立连接等待servlet返回消息到主页,返回到主页调用showMessage,这个方法调用ext组件弹出消息内容
initServlet(); function initServlet() { $.ajax({ url:'servlet/asyn', type:'get', dataType:'text', contentType: "text/plain; charset=UTF-8", timeout:600000, success: function(msg){ if (msg!=null && msg!='') {//这个返回值要改成json对象 showMessage(msg); } callSelf();//把这个方法里面的代码放在这里,IE8会一直死循环的调用(其它浏览器不会),不知道什么原因 } }); } function callSelf() { initServlet(); } function showMessage(data) { Ext.create('widget.uxNotification', { title: '消息提醒', position: 'br', cls: 'ux-notification-light', iconCls: 'ux-notification-icon-information', html: data, width: 200, height: 90, autoCloseDelay: 4000, slideBackDuration: 500, slideInAnimation: 'bounceOut', slideBackAnimation: 'easeIn' }).show(); } //js获取项目根路径,如: localhost:8080/test function getRootPath(){ //获取当前网址,如: http://localhost:8080/test/test.jsp var curWwwPath=window.document.location.href; //获取主机地址之后的目录,如: test/test.jsp var pathName=window.document.location.pathname; var pos=curWwwPath.indexOf(pathName); //获取主机地址,如: http://localhost:8080 var localhostPaht=curWwwPath.substring(0,pos); var wsPath = localhostPaht.replace('http://',''); //获取带"/"的项目名,如:/test var projectName=pathName.substring(0,pathName.substr(1).indexOf('/')+1); return(wsPath+projectName); }
前端要引用ext右下角弹出消息组件:
扫描二维码关注公众号,回复:
509287 查看本文章
Notification extension for Ext JS 4.0.2+
Version: 2.1.3
从这里下载
https://github.com/EirikLorentsen/Ext.ux.window.Notification