Blogs

SpringSource Blog

Spring MVC 3.2 Preview: Chat Sample

Rossen Stoyanchev

Last updated on November 5th, 2012 (Spring MVC 3.2 RC1)

In previous blog posts I introduced the Servlet 3 based async capability in Spring MVC 3.2 and used the spring-mvc-showcase and the Spring AMQP stocks sample to demonstrate it. This post presents a chat sample where the external events are not AMQP messages but rather HTTP POST requests with chat messages. In the second part of the post, I'll switch to a distributed chat where the events are Redis notifications.

Chat is not a common requirement for web applications. However it is a good example of a requirement that can only be met with real-time notifications. It is more sensitive to time delays than email or status alerts and it is not that uncommon to chat in a browser with a friend, or with a colleague during a webinar, or with a live person on a shopping site. You can imagine other types of online collaboration.

The Sample

The spring-mvc-chat sample is available on Github. Although not the focus of this blog post, the client side uses Thymeleaf, knockout.js, and jQuery. Thymeleaf is an excellent alternative to JSPs that enables clean HTML templates with support for previews allowing a designer to double-click an HTML template and view it unlike a JSP that requires a Servlet container. knockout.js is a client-side MVC framework that's very handy for attaching behavior to HTML elements. To get an idea about it quickly, follow one of its excellent tutorials. jQuery is used for DOM scripting and Ajax requests.

ChatController

The ChatController exposes operations to get and post chat message. Here is the method to get messages:

@RequestMapping(method=RequestMethod.GET)
@ResponseBody
public DeferredResult<List<String>> getMessages(@RequestParam int messageIndex) {

  final DeferredResult<List<String>> deferredResult = new DeferredResult<List<String>>(null, Collections.emptyList());
  this.chatRequests.put(deferredResult, messageIndex);

  deferredResult.onCompletion(new Runnable() {
    @Override
    public void run() {
      chatRequests.remove(deferredResult);
    }
  });

  List<String> messages = this.chatRepository.getMessages(messageIndex);
  if (!messages.isEmpty()) {
    deferredResult.setResult(messages);
  }

  return deferredResult;
}

A new DeferredResult is created and saved in a Map from where it will be removed by the registered onCompletion callback when the async request completes. Then the method checks for new messages using an in-memory ChatRepository. If new messages are found, the DeferredResult is set immediately. Otherwise it will be set later, when a new message arrives.

Below is the method that saves chat messages and updates all saved DeferredResult instances:

@RequestMapping(method=RequestMethod.POST)
@ResponseBody
public void postMessage(@RequestParam String message) {

  this.chatRepository.addMessage(message);

  // Update all chat requests as part of the POST request
  // See Redis branch for a more sophisticated, non-blocking approach

  for (Entry<DeferredResult<List<String>>, Integer> entry : this.chatRequests.entrySet()) {
    List<String> messages = this.chatRepository.getMessages(entry.getValue());
    entry.getKey().setResult(messages);
  }
}

A Distributed Chat

The above chat sample uses simple, in-memory persistence and works only when deployed to a single server. The redis branch uses a Redis-backed ChatRepository. Redis is a simple key-value store that is easy to use in Java with the help of the Spring Redis project.

The RedisChatRepository uses the Spring Redis RedisTemplate to look up and save chat messages. Feel free to take a look at the code.

The controller method that saves new chat messages is now a single line:

@RequestMapping(method=RequestMethod.POST)
@ResponseBody
public void postMessage(@RequestParam String message) {
  this.chatRepository.addMessage(message);
}

Receiving new messages is also very simple. It involves implementing the Spring Redis MessageListener interface, which can be done directly in the controller:

@Controller
@RequestMapping("/mvc/chat")
public class ChatController implements MessageListener {

  // ...

  public void onMessage(Message message, byte[] pattern) {
    for (Entry<DeferredResult<List<String>>, Integer> entry : this.chatRequests.entrySet()) {
      List<String> messages = this.chatRepository.getMessages(entry.getValue());
      entry.getKey().setResult(messages);
    }
  }

}

The Redist version of the chat will work in a cluster. A message can be posted on any server and all other servers will receive Redis notifications. The Spring Redis project makes it really simple to receive those notifications in message-driven POJO style.

Chat messages can be posted from non-Java, Redis clients too. For example, connect with the Redis command-line shell, type the below commands, and the chat messages will be deliverd to all subscribed servers and connected browsers:

redis 127.0.0.1:6379> RPUSH chat:archive "hello from the redis cli"
redis 127.0.0.1:6379> PUBLISH chat "a new chat message is available"

This concludes the blog posts covering the Spring MVC 3.2, Servlet 3 based, async support. Thanks for reading!

Similar Posts

Share this Post
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Slashdot
  • Technorati
  • Twitter
 

41 responses


  1. Thanks for the great post. I had to make a slight change to get the redis branch working:
    RedisChatRepository was not able to autowire redisTemplate via constructor; I removed the constructor and annotated the redisTemplate directly (removing final):
    @Autowired
    private StringRedisTemplate redisTemplate;


  2. John, thanks. I can't quite see why that would be but if there were any errors you noticed, feel free to add some detail by creating an issue in the project Github repository and I'll take a look.


  3. Maybe it was obvious to everyone else, but it's probably worth mentioning that this requires WebSocket for the long poll, so if you're importing this into STS, you probably want the latest STS 2.9.2, which bundles tc Server 7, which bundles Tomcat 7.0.27, which supplies WebSocket.


  4. tc Server 2.7 I meant of course… argh


  5. Tomcat 7.0.27 does have WebSockets support but the sample doesn't use WebSockets. See my second post.


  6. Thanks; obviously I leapt to conclusions rather than knowing what I'm talking about.

    There must be some other reason this blew up on tc Server 2.6 and worked fine on 2.7… I'll investigate further.


  7. Awesome, This reminds me of the college days when we tried a chat client in plain java using socket opening code and were so happy to see it working, almost 12 years back :) …. Spring surely makes things look so simple, however I can appreciate the things may be going behind the scene just to make it so simple. Thanks for illustration.


  8. How can I download source code? can you please provide the Source code??


  9. There is a link to the Github repository above. There you'll find an option to download the project as a zip if you don't want to clone it.


  10. What DeferredResult exactly does?


  11. Dom, take a look at the class Javadoc for a start or in the source code if necessary. Essentially a DeferredResult is a simple type on which you can set a value when it becomes available. Spring MVC will then process that value.


  12. Hey,
    How does all this come together with REST ?
    I have a Rest-Spring web service , I what to handle it the "async" way.

    How does spring 3.2 suport this ?


  13. I'm not very sure what you mean. Can you be more specific? This and the last 4 blog posts should give plenty of examples and detail. Especially the previous one, on adding long polling to an existing web application.


  14. hi Rossen Stoyanchev,

    Do you have any schedule of WebSocket supporting now? I'm looking forward to seeing you'll implement this feature.

    From my understanding, to implement this there are two key things need to do, the basic one is the implementation of WebSocket protocol, some J2EE servers like tomcat have already done that, will you build on it or implement it by yourself?
    the second one I think would be some application layer supporting, on which developers can implement their business logic. For this point, I don't know if you noticed some interesting framework like JWebSocket, which is actually a rough product based on WebSocket protocol and Spring Frameworks. In this project, they imported event model, so what do you think about this kind of architecture?

    Thanks.


  15. Though i have downloaded the source code and followed the instruction from README file, but still not working i have tomcat 7.0 will it work on this version?

    Thanks.


  16. Anu, is it Tomcat 7.0.30? If so it should work. You can record the details of the issue under the Issues tab of the project on Github.


  17. I try out this example to extend my spring security application but there were mistakes. Is there any example to use asynchronous data transfer from controller witch is authenticated?


  18. Kris, generally speaking Spring Security 3.2 will provide official support for Servlet 3 async requests (see SEC-1998) in version 3.2. That said in many situations it may already work so it all depends on the specific errors. Are they related to Spring Security or something else? It may also be that the Spring Security filter mapping needs to have the ASYNC dispatcher type added.


  19. Rossen, I resolve my problem using Atmosphere for a web-socket support and it's working very well with Spring Security.


  20. Hi Rossen,

    I'm trying to build chat into my Spring-Hibernate-Tomcat application.

    For my case, are the classes DispatchServletInitializer and WebMvcConfig required? My web.xml has DispatchServlet and spring mvc configured in a spring context file.

    – Mahboob


  21. The DispatcherServletInitializer is a Java replacement of web.xml in Servlet 3 containers while the WebMvcConfig is a Java config replacement of the MVC namespace. You can use web.xml and Spring XML configuration instead.


  22. Hello Rossen,
    I've deployed and gone through the chat application and I there are a couple of points about it that I still don't understand:
    -Why do you use a list of strings to parameterize the deferredResult? I tested the app and noticed there is only one string at most in the deferredResult…
    -Is the message index globally unique or unique to a given use having joined the chat?
    Regards,
    Julien.


  23. ChatRepository returns a list of messages. If there were more users chatting and sending more messages at the same time, it's possible that multiple messages have accumulated.


  24. Hi everyone,
    I would like build a springmvc sample ,but I don't konw This example requires what package.


  25. The example code above doesn't show package names to make it more readable. It's there for convenience but you have links to the full source code.


  26. this example project build by maven, but I don't konw how to use maven.
    when I build a helloworld(springmvc),I need some *.jar.
    I don't know which needs some *.jar files .

    ps:my english is so bad.


  27. Hi Rossen,

    Thanks a lot for the excellent set of Spring MVC posts! They are well written, and leave me itching to update my project from 3.1 to 3.2.

    One thing I've been curious about is support for HTTP streaming/server-sent events. In your first post, you mention that this is now possible in Servlet 3.0, and I've seen a few examples of this on the web. However, does Spring provide any high-level functionality to support it? For example, a class like DeferredPeriodicResult would be useful? Something that allows us to write multiple "results" to the DeferredPeriodicResult over the lifetime of the HTTP connection!

    Thanks a lot,
    Muel.


  28. Thanks Muel. There is no higher level support for server-sent events (SSE). However, for the next release we're planning more extensive support around web messaging architectures. Stay tuned for 4.0 M1 announcements here by April and also watch SPR-9356.

    The idea of using a DeferredResult multiple times is something we can consider as well. At present DeferredResult supports completing the response through HTTP message conversion as well as view resolution (e.g. JSP rendering). Obviously when setting multiple values, in the event streaming scenario, we'll need to significantly narrow things down to simply write some data to the response. Feel free to open a ticket in JIRA. An example of types of values you'd like to write through such a DeferredResult would be useful. Thanks!


  29. like many people I am seeing

    java.lang.IllegalStateException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "true" to servlet and filter declarations in web.xml.
    at org.springframework.util.Assert.state(Assert.java:384) ~[spring-core-3.2.1.RELEASE.jar:3.2.1.RELEASE]

    After downloading and running your examples using STS 2.9.2 and 3.0.0 with a v2.7 tc server. Any idea what could be causing this issue? I know others are having a similar issue and no one seems to be providing any suggestions.


  30. Answer to my question:

    You must use STS 3.1


  31. Hi all,

    There is one thing I don't really understand about the spring-mvc-chat application: I noticed there are always as many entries in the chatRequests Map as there are users connected to the chat application…

    When there are 3 users connected to the app and user #3 posts a message, then the for loop in the postMessage of the controller iterates 3 times…

    I have opened a thread on SO here.

    http://stackoverflow.com/questions/15357990/understanding-the-spring-mvcs-deferredresult-class-in-the-context-of-the-spring

    Can anyone please comment?


  32. That's correct, the chatRequests map contains DeferredResult instances, for long running requests from all participants in the chat. What part confuses you more specifically?


  33. Hi Rossen,
    Thanks for your reply. You say:

    "That's correct, the chatRequests map contains DeferredResult instances, for long running requests from all participants in the chat. What part confuses you more specifically?"

    What I don't understand is that it seems that there are always exactly N DeferredResult instances (and not more) for N users at a given time. Is this right?

    Julien.


  34. Yes, this is right. You will notice the onCompletion handler registered with a DeferredResult when it is added to chatRequests. That ensures there is always only one outstanding DeferredResult per user — the one for the current request. Does that make sense?


  35. Ummm. If I understand correctly, there are as many DeferredResult instances as there are open requests as there are users. Is this right?


  36. Roughly speaking. A DeferredResult is associated with an open request. When the request completes, the DeferredResult is removed from the map, and then, the client issues a new long polling request, which adds a new DeferredResult instance


  37. I quote your message:

    "Roughly speaking. A DeferredResult is associated with an open request. When the request completes, the DeferredResult is removed from the map, and then, the client issues a new long polling request, which adds a new DeferredResult instance"

    I understand now. Great! Thanks a lot for your input Rossen.


  38. Hi Rossen,

    I am missing something important about threading and DeferredResults.

    Basically, I have an app with a controller method that returns a DeferredResult of a List of Messages (where Message is a JPA entity).

    The controller method calls a @Transactional service method.

    The fact that the service method is @Transactional causes the following exception:
    AbandonedObjectException: DBCP object created 2013-03-26 15:54:42 by the following code was never closed:

    If I replace the service method call by something else (not @Transactional) then the app works OK.

    I know almost for certain it is a threading issue with my code and I really want to understand what is wrong…

    Can you please "enlighten me"?

    Here is the full post I opened on the Spring forums:
    my post on spring forums


  39. If the @Transactional method is invoked and completes before the controller method even returns then there is only one thread involved, so I'm not sure what else is going on. It would be best if you could provide an example of the relevant code and configuration parts (mainly the controller but possibly other details as well, do you have an OpenSessionInViewFilter or interceptor? etc) Ideally provide a repro project if you can demonstrate the issue. Or try modifying the existing samples (spring-mvc-showcase, spring-mvc-chat).


  40. Thanks Rossen,

    I have updated my post on the Spring forums to answer your questions.

    See here

    I am in the process of creating a repro project as requested.

    Regards,

    J.


  41. Hi Rossen,

    I finally managed to reproduce the bug using the spring-mvc-chat app.

    The sample app can be found here.

    Let me know if you have issues reproducing the bug.

    Regards,

    Julien.

Leave a Reply