参考资料:
1.http://spring.io/blog/2012/05/16/spring-mvc-3-2-preview-chat-sample/
2.spring mvc异步请求处理的资料(比如http://blog.csdn.net/xiejx618/article/details/41285085)
3.Thymeleaf:http://www.thymeleaf.org/
4.knockoutjs:http://knockoutjs.com/
这个例子的源码主要来自于https://github.com/rstoyanchev/spring-mvc-chat,其中加入了Thymeleaf和knockoutjs两大技术点,增加了此例子的难度,但两大官网都有比较详细的资料介绍.
一.前期准备:
1.确保spring mvc支持异步请求,可参考前两篇文章.
2.集成Thymeleaf:
2.1.pom.xml主要加入依赖:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>${thymeleaf.version}</version>
</dependency>
2.2.继承WebMvcConfigurerAdapter的MvcConfig加入:
@Bean
public TemplateResolver templateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
resolver.setCacheable(false);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
@Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
return resolver;
}
2.3.建立chat.html,以上设置HTML5,标签格式是比较严格的,不严格可能即时抛异常.
2.4.在DefaultController写一个方法,启动jetty测试
@RequestMapping(value = "/",method = RequestMethod.GET)
public String index(){
return "chat";
}
二.后端处理分析.
ChatController
1.使用了ConcurrentHashMap来装请求,key是DeferredResult<List<String>>类型,因为不同的请求是不一样的,所以不会覆盖已有的元素.
2.先看用于获取消息的getMessages方法.final DeferredResult<List<String>> deferredResult = new DeferredResult<List<String>>(null, Collections.emptyList());这里并不是使用于构造一个deferredResult,null的意思使用servlet容器的默认超时时间,当异步处理超时时,返回空集.
onCompletion回调是用于处理请求完成时做的清理.chatRepository.getMessages(messageIndex);是传入一个信息的索引,获取从此索引之后的所有消息.
3.再看用于发送消息的postMessage方法.this.chatRepository.addMessage(message);用于向messages添加消息.然后将已有未处理的请求处理并返回消息到页面.
三.前端处理分析.
前端好像没有什么可以说的,但要强调的一点是一旦调用了pollForMessages之后,就开始长轮询了.
四.测试调试.
从调试结果来看发现一个问题:
当进入聊天界面发送消息之后,立刻退出,不关闭页面,界面没刷新,再进入聊天界面发送消息.此时后台抛异常了,前台也报错了.引起这问题的原因:
1.当进入聊天界面,轮询开始,发送消息之后,轮询继续.因为传到后台的索引已经达到获取消息的最大值,所以后台并没有立即提交响应.
2.退出聊天,界面没刷新,刚才那个响应还没提交的响应并没有断开,再次进入聊天界面,页面又开始轮询了,也就是说又发起了一个未处理的请求(因为请求的索引也达到了最大值),再次发送消息,此时postMessage方法会将这两个请求的结果发回来,此时success回调会两次加1,再去轮询.虽然message的大小因为刚才的发送消息加1,但这次的轮询已经越界了,所以后台会抛异常.
对于这个异常可以认为是编程错误了,可以这样处理:
将InMemoryChatRepository的getMessages改为如下
public List<String> getMessages(int index) {
if (index<0||this.messages.isEmpty()||index > this.messages.size()) {
return Collections.<String> emptyList();
}
return this.messages.subList(index, this.messages.size());
}
这样messages的取子集就不会越界了,但还是没解决界面两次返回同样的消息处理结果.因为还是同样存在有相同的请求索引去拿消息,这个解决应该要从页面入手,当退出聊天页面,关闭一个页闭,让之前的那个请求断开连接,这样即使后台处理成超时,也不会再返回到success使messageIndex加两次1.