Long Polling with Spring 3.2's DeferredResult
Implement Spring’s Deferred Result technique using the Servlet 3 specification as implemented on Tomcat 7^1 to fix a server problem.
Join the DZone community and get the full member experience.
Join For FreeIn our last episode, the CEO of Agile Cowboys Inc had just hired a Java/Spring consultant by giving him the Porsche that he originally bought for his girlfriend. Being upset by the loss of her prize Porsche, the CEO’s girlfriend has told his wife of their affair. His wife, after cutting up the CEO’s suites has filed for divorce. Meanwhile the CEO has implemented a new ‘casual’ dress code at the office and the Java/Spring consultant has just arrived back from a spin in his new Porsche and is sitting down at his desk about to fix the TV company’s software... If this doesn’t mean anything to you then take a look at Long Polling Tomcat With Spring.
The Java/Spring Consultant has to fix the TV Company’s server resource problem before the next big game, and he knows he can do this by implementing Spring’s Deferred Result technique using the Servlet 3 specification as implemented on Tomcat 71
The first thing that the Java/Spring consultant does is to check the project’s pom.xml file. For an asynchronous Servlet 3 project, you must include the following dependency:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
Next you must tell Tomcat that the Spring DispatcherServlet supports Servlet 3 asynchronous communications. This is achieved by adding the following line to your web.xml:
<async-supported>true</async-supported>
The complete DispatcherServlet configuration is:
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
Having sorted out the project configuration the Java/Spring Consultant swiftly moves on to the controller code. He replaces the Graduate Trainee’s SimpleMatchUpdateController with a new DeferredMatchUpdateController:
@Controller()public class DeferredMatchUpdateController {
@Autowiredprivate DeferredResultService updateService;
@RequestMapping(value = "/matchupdate/begin" + "", method = RequestMethod.GET)@ResponseBodypublic String start() {updateService.subscribe();return "OK";}
@RequestMapping("/matchupdate/deferred")@ResponseBodypublic DeferredResult<Message> getUpdate() {
final DeferredResult<Message> result = new DeferredResult<Message>();
updateService.getUpdate(result);return result;}
}
The new DeferredMatchUpdateController is fairly simple. Like the SimpleMatchUpdateController it contains two methods:start() and getUpdate(), which do exactly the same job as their simple counterparts. This makes this controller a plugin replacement for the SimpleMatchUpdateController. The big difference is that the getUpdate() methods creates an instance of Spring’s DeferredResult, which it passes to the new DeferredResultService before returning it to Spring. Spring then parks the HTTP request allowing it to hang until the DeferredResult object has some data to return to the browser.
@Service("DeferredService")public class DeferredResultService implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(DeferredResultService.class);
private final BlockingQueue<DeferredResult<Message>> resultQueue = new LinkedBlockingQueue<>();
private Thread thread;
private volatile boolean start = true;
@Autowired
@Qualifier("theQueue")private LinkedBlockingQueue<Message> queue;
@Autowired
@Qualifier("BillSkyes")private MatchReporter matchReporter;
public void subscribe() {logger.info("Starting server");
matchReporter.start();
startThread();}
private void startThread() {
if (start) {synchronized (this) {if (start) {start = false;
thread = new Thread(this, "Studio Teletype");
thread.start();}
}
}
}
@Overridepublic void run() {
while (true) {try {
DeferredResult<Message> result = resultQueue.take();
Message message = queue.take();
result.setResult(message);
} catch (InterruptedException e) {throw new UpdateException("Cannot get latest update. " + e.getMessage(), e);}
}
}
public void getUpdate(DeferredResult<Message> result) {resultQueue.add(result);}
}
Again, like its counterpart SimpleMatchUpdateService the DeferredResultService contains two methods: subscribe() andgetUpdate()
Dealing with getUpdate(...), all it does it to add the newly created DeferredResult object to a LinkedBlockingQueue calledresultQueue, so that it can be dealt with later when a match update is available.
The real work is done by the subscribe() method. First, this method starts the matchReporter, which feeds match updates into the autowired queue instance at the appropriate moment. It then calls the private startThread() method to start a worker thread. This is only started once and uses double check locking to ensure that this is done efficiently and without problems.
The thread’s run() method infinitely loops firstly taking a DeferredResult object from the resultQueue, if available, and then aMessage object, representing a match update from the update queue, again if available. It then calls DeferredResult’ssetResult(...) using the message object as the argument. Spring will now take over and the original long poll request will be completed and the data belatedly returned to the browser.
After a hard couple of hours work, the Java/Spring Consultant promotes his code to live, picks up the keys to the Porsche and takes off for a spin. The next Saturday, using Spring’s DeferredResult, the servers cope wonderfully: the users are happy, the President of the TV company is happy and the CEO of Agile Cowboys Inc is happy, although he has a nagging suspicion that he’s paid the consultant too much, but hey, it’s only money.
1When writing this blog I used Tomcat version 7.0.42
The code that accompanies this blog is available on Github at: https://github.com/roghughe/captaindebug/tree/master/long-poll
Published at DZone with permission of Roger Hughes, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments