Neeke

伪全栈攻城狮

Spring MVC 3.2 DeferredResult and long polling

唉...博客四个月没更过了...本来不想说,但是...但是...最近真的很忙啊...眼看再过一个月多点就要升级当爹了...不废话了,说正事,因为最近项目中需要在浏览器上做一个实时监控的功能,目前实现这玩意的方式及框架也挺多的,感觉都挺麻烦的,也不想引入过多的框架进来,尽量在现有的框架上实现,起初用的是ExtJS direct中的polling方式,设置成一秒拉一次数据,后来发现这玩意请求数太多了,而且未必下一秒服务器端就有新数据,总之就是不太好用。 咱继续google查资料,发现Servlet 3.0开始支持async,看了一些介绍【戳这里】,不错哦!原生的!因为现在用的是Spring MVC 3.2,所以想找找看有没有结合Spring MVC的实现,在Spring官方博客上找到两篇文章:
  1. SPRING MVC 3.2 PREVIEW: ADDING LONG POLLING TO AN EXISTING WEB APPLICATION
  2. SPRING MVC 3.2 PREVIEW: CHAT SAMPLE
说实话,看是必须看懂了,但是官方提供的源码却没怎么跑成功...好吧,想吃白食是没戏了,结合第二篇文章中关于DeferredResult的代码,咱自己写个聊天室试试看,用到的东西有jQuery 1.10.1和Spring MVC 3.2。 关于Spring MVC的配置请戳这里:Spring3 MVC 配置,唯一不同的地方就是web.xml,这里必须声明使用Servlet 3.0并且在所有的filter及servlet中需要声明使用async: <async-supported>true</async-supported> 当前完整的web.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>async</display-name>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
    <!-- 字符编码 -->
	<filter>
	  <filter-name>CharacterEncodingFilter</filter-name>
	  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	  <async-supported>true</async-supported>
	  <init-param>
	    <param-name>encoding</param-name>
	    <param-value>UTF-8</param-value>
	  </init-param>
	  <init-param>
	    <param-name>forceEncoding</param-name>
	    <param-value>true</param-value>
	  </init-param>
	</filter>
	<filter-mapping>
	  <filter-name>CharacterEncodingFilter</filter-name>
	  <url-pattern>/*</url-pattern>
	</filter-mapping>
	<servlet>
	    <servlet-name>Spring MVC</servlet-name>
	    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	    <init-param>
	      <param-name>contextConfigLocation</param-name>
	      <param-value>
					/WEB-INF/spring/app-config.xml
			</param-value>
	    </init-param>
	    <load-on-startup>1</load-on-startup>
	    <async-supported>true</async-supported>
	</servlet>

	<servlet-mapping>
	    <servlet-name>Spring MVC</servlet-name>
	    <url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>
配置信息就这么多了,超级简单!接下来创建一个controller,用于处理聊天信息:
/**
 * @作者 Neeke
 * @文件名 ChatController.java
 * @作用 处理聊天消息
 * @Blog http://ineeke.com
 */
@Controller
public class ChatController {

	//存放所有的用户请求
	private final Map<String, DeferredResult<Message>> chatRequests = new ConcurrentHashMap<String, DeferredResult<Message>>();
	//时间格式化
	private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	/**
	 * @作者 Neeke
	 * @作用 登录
	 * @param name 用户名
	 * @param session 会话
	 * @return 聊天室页面
	 */
	@RequestMapping(value = "/login", method = RequestMethod.POST)
	public String login(@RequestParam String name, HttpSession session){
		session.setAttribute("user", name);
		Message msg = new Message();
		msg.setUser("系统");
		msg.setDate(sdf.format(new Date()));
		msg.setContent(name + "已加入");
		//通知所有用户有人进入聊天室
		processMessage(msg);
		return "room";
	}

	/**
	 * 
	 * @作者 Neeke
	 * @作用 读取最新消息
	 * @param session 会话
	 * @return DeferredResult<Message>
	 */
	@RequestMapping(value = "/getMessages", method = RequestMethod.GET)
	@ResponseBody
	public DeferredResult<Message> getMessages(HttpSession session){
		//取出当前登录用户
		final String user = (String)session.getAttribute("user");
		//创建DeferredResult<Message>
		DeferredResult<Message> dr = new DeferredResult<Message>();
		//若用户不存在则直接返回,否则将其放入用户请求列表中然后返回
		if(null == user){
			return dr;
		}else{
			//当DeferredResult对客户端响应后将其从列表中移除
			dr.onCompletion(new Runnable() {

				@Override
				public void run() {
					// TODO 自动生成的方法存根
					chatRequests.remove(user);
				}
			});
			chatRequests.put(user, dr);
			return dr;
		}
	}

	/**
	 * @作者 Neeke
	 * @作用 接收客户端消息
	 * @param session 会话
	 * @param content 消息内容
	 * @return Map<String, String>
	 */
	@RequestMapping(value = "/setMessage", method = RequestMethod.POST)
	@ResponseBody
	public Map<String, String> setMessage(HttpSession session, @RequestParam String content){
		Message msg = new Message();
		msg.setContent(content);
		msg.setDate(sdf.format(new Date()));
		msg.setUser((String)session.getAttribute("user"));
		//发布消息给所有用户
		processMessage(msg);
		Map<String, String> map = new HashMap<String, String>(1);
		map.put("success", "true");
		return map;
	}

	/**
	 * @作者 Neeke
	 * @作用 退出聊天室
	 * @param session 会话
	 * @return Map<String, String>
	 */
	@RequestMapping(value = "/logout", method = RequestMethod.GET)
	@ResponseBody
	public Map<String, String> logout(HttpSession session){

		Message msg = new Message();
		String user = (String)session.getAttribute("user");
		msg.setContent("已离开");
		msg.setDate(sdf.format(new Date()));
		msg.setUser(user);
		chatRequests.remove(user);
		//通知所有用户有人离开聊天室
		processMessage(msg);
		Map<String, String> map = new HashMap<String, String>(1);
		map.put("success", "true");
		return map;
	}

	/**
	 * @作者 Neeke
	 * @作用 将消息信息发布给所有在线用户
	 * @param msg 消息
	 */
	private void processMessage(Message msg){
		Set<String> keys = chatRequests.keySet();
		for(String key : keys){
			chatRequests.get(key).setResult(msg);
		}
	}
}
这里我们还需要一个实体用来存放消息(get/set方法这里就不占篇幅了):
/**
 * @作者 Neeke
 * @文件名 Message.java
 * @作用 封装用户的聊天内容
 * @Blog http://ineeke.com
 */
public class Message {

	private String user;
	private String date;
	private String content;
}
前端登录页面代码:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登录</title>
</head>
<body>
<form action="login" method="post">
name: <input type="text" name="name"/>
<input value="登录" type="submit"/>
</form>
</body>
</html>
聊天室页面代码:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<% String user =(String)session.getAttribute("user"); %>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>聊天室</title>
<script type="text/javascript" src="jquery-1.10.1.min.js"></script>
<script type="text/javascript">
	$(function(){
		(function getMessages(){
			$.ajax({
				dataType: "json",
				url: 'getMessages',
				cache: false,
				success: function(data){
					var v = $('#text').val();
					v += '\r\n' + data.date + ' ' + data.user + ':' + data.content;
					$('#text').val(v);
				}
			}).always(function(){
				getMessages();
			});
		})();

		$('#form').submit(function(event){
			event.preventDefault();
			var values = $(this).serialize();
			$.post('setMessage', values, function(data){
				$('#form>[name=content]').val('');
			}, 'json');
		});

		$('#logout').click(function(){
			$.ajax({
				dataType: "json",
				url: 'logout',
				cache: false,
				success: function(data){
					window.location.href = 'index.jsp';
				}
			});
		});
	});
</script>
</head>
<body>
欢迎:<%=user %><br/>
<textarea id="text" rows="20" style="width: 500;"></textarea>
<form id="form" action="sendMessage" method="post">
<input type="text" name="content" />
<input value="发送" type="submit"/>
<input id="logout" value="离开" type="button"/>
</form>
</body>
</html>
好了,现在跑起来玩玩吧! [caption id="attachment_1487" align="aligncenter" width="500"] Spring MVC 长轮询聊天室[/caption] [caption id="attachment_1488" align="aligncenter" width="500"] Spring MVC 长轮询聊天室[/caption] 我这里采用的是长轮询方式,前端发起一个查询请求后,Spring MVC返回一个DeferredResult类型,这个请求将会被挂起,直到在另一个线程中DeferredResult被放入数据才会响应给客户端浏览器,否则将会向客户端响应超时,然后jQuery会再次发起一个请求,如此循环。 关于响应超时时间,可以在创建DeferredResult对象时传递构造参数来改变: 自定义10秒: DeferredResult<Message> dr = new DeferredResult<Message>(10); 永不超时: DeferredResult<Message> dr = new DeferredResult<Message>(0);
  • 评论列表:
  •  tSt
     发布于 2013-06-08 21:48:48  回复该评论
  • 就快当爹了,我今年也准备要孩子了

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

«   2016年11月   »
123456
78910111213
14151617181920
21222324252627
282930
网站分类
搜索
最新留言
文章归档
友情链接

Powered By Z-BlogPHP 1.5.1 Zero

Copyright Your WebSite.Some Rights Reserved.