Asynchronous
Servlet Implementation
Let’s
see steps to implement async servlet and then we will provide async supported
servlet for above example.
- First
of all the servlet where we want to provide async support should have @WebServlet annotation with asyncSupported value as true.
- Since
the actual work is to be delegated to another thread, we should have a thread
pool implementation. We can create thread
pool using Executors framework and use servlet
context listener to initiate
the thread pool.
- We
need to get instance of AsyncContext through
ServletRequest.startAsync()
method. AsyncContext provides methods
to get the ServletRequest and ServletResponse object references. It also
provides method to forward the request to another resource using dispatch()
method.
- We
should have a Runnable
implementation where we will
do the heavy processing and then use AsyncContext object to either dispatch
the request to another resource or write response using ServletResponse
object. Once the processing is finished, we should call
AsyncContext.complete() method to let container know that async processing is
finished.
- We
can add AsyncListener implementation to the AsyncContext object to implement
callback methods – we can use this to provide error response to client incase
of error or timeout while async thread processing. We can also do some cleanup
activity here.
Initializing
Worker Thread Pool in Servlet Context Listener
AppContextListener.java
01 |
package com.journaldev.servlet.async; |
03 |
import java.util.concurrent.ArrayBlockingQueue; |
04 |
import java.util.concurrent.ThreadPoolExecutor; |
05 |
import java.util.concurrent.TimeUnit; |
07 |
import javax.servlet.ServletContextEvent; |
08 |
import javax.servlet.ServletContextListener; |
09 |
import javax.servlet.annotation.WebListener; |
12 |
public class AppContextListener implements ServletContextListener
{ |
14 |
public void contextInitialized(ServletContextEvent
servletContextEvent) { |
17 |
ThreadPoolExecutor
executor = new ThreadPoolExecutor( 100 , 200 ,
50000L, |
18 |
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>( 100 )); |
19 |
servletContextEvent.getServletContext().setAttribute( "executor" , |
24 |
public void contextDestroyed(ServletContextEvent
servletContextEvent) { |
25 |
ThreadPoolExecutor
executor = (ThreadPoolExecutor)
servletContextEvent |
26 |
.getServletContext().getAttribute( "executor" ); |
The
implementation is pretty straight forward, if you are not familiar with
Executors framework, please read Thread
Pool Executor.
For
more details about listeners, please read Servlet
Listener Tutorial.
Worker
Thread Implementation
AsyncRequestProcessor.java
01 |
package com.journaldev.servlet.async; |
03 |
import java.io.IOException; |
04 |
import java.io.PrintWriter; |
06 |
import javax.servlet.AsyncContext; |
08 |
public class AsyncRequestProcessor implements Runnable
{ |
10 |
private AsyncContext
asyncContext; |
13 |
public AsyncRequestProcessor()
{ |
16 |
public AsyncRequestProcessor(AsyncContext
asyncCtx, int secs)
{ |
17 |
this .asyncContext
= asyncCtx; |
23 |
System.out.println( "Async
Supported? " |
24 |
+
asyncContext.getRequest().isAsyncSupported()); |
27 |
PrintWriter
out =
asyncContext.getResponse().getWriter(); |
28 |
out.write( "Processing
done for " +
secs + "
milliseconds!!" ); |
29 |
} catch (IOException
e) { |
33 |
asyncContext.complete(); |
36 |
private void longProcessing( int secs)
{ |
40 |
} catch (InterruptedException
e) { |
Notice
the use of AsyncContext and it’s usage in getting request and response objects
and then completing the async processing with complete() method call.
AsyncListener
Implementation
AppAsyncListener.java
01 |
package com.journaldev.servlet.async; |
03 |
import java.io.IOException; |
04 |
import java.io.PrintWriter; |
06 |
import javax.servlet.AsyncEvent; |
07 |
import javax.servlet.AsyncListener; |
08 |
import javax.servlet.ServletResponse; |
09 |
import javax.servlet.annotation.WebListener; |
12 |
public class AppAsyncListener implements AsyncListener
{ |
15 |
public void onComplete(AsyncEvent
asyncEvent) throws IOException
{ |
16 |
System.out.println( "AppAsyncListener
onComplete" ); |
21 |
public void onError(AsyncEvent
asyncEvent) throws IOException
{ |
22 |
System.out.println( "AppAsyncListener
onError" ); |
27 |
public void onStartAsync(AsyncEvent
asyncEvent) throws IOException
{ |
28 |
System.out.println( "AppAsyncListener
onStartAsync" ); |
33 |
public void onTimeout(AsyncEvent
asyncEvent) throws IOException
{ |
34 |
System.out.println( "AppAsyncListener
onTimeout" ); |
36 |
ServletResponse
response =
asyncEvent.getAsyncContext().getResponse(); |
37 |
PrintWriter
out = response.getWriter(); |
38 |
out.write( "TimeOut
Error in Processing" ); |
Notice
the implementation of onTimeout() method where we are sending timeout response
to client.
Here
is the implementation of our async servlet, notice the use of AsyncContext and
ThreadPoolExecutor for processing.
AsyncLongRunningServlet.java
01 |
package com.journaldev.servlet.async; |
03 |
import java.io.IOException; |
04 |
import java.util.concurrent.ThreadPoolExecutor; |
06 |
import javax.servlet.AsyncContext; |
07 |
import javax.servlet.ServletException; |
08 |
import javax.servlet.annotation.WebServlet; |
09 |
import javax.servlet.http.HttpServlet; |
10 |
import javax.servlet.http.HttpServletRequest; |
11 |
import javax.servlet.http.HttpServletResponse; |
13 |
@WebServlet (urlPatterns
= "/AsyncLongRunningServlet" ,
asyncSupported = true ) |
14 |
public class AsyncLongRunningServlet extends HttpServlet
{ |
15 |
private static final long serialVersionUID
= 1L; |
17 |
protected void doGet(HttpServletRequest
request, |
18 |
HttpServletResponse
response) throws ServletException,
IOException { |
19 |
long startTime
= System.currentTimeMillis(); |
20 |
System.out.println( "AsyncLongRunningServlet
Start::Name=" |
21 |
+
Thread.currentThread().getName() + "::ID=" |
22 |
+
Thread.currentThread().getId()); |
24 |
request.setAttribute( "org.apache.catalina.ASYNC_SUPPORTED" , true ); |
26 |
String
time = request.getParameter( "time" ); |
27 |
int secs
= Integer.valueOf(time); |
32 |
AsyncContext
asyncCtx = request.startAsync(); |
33 |
asyncCtx.addListener( new AppAsyncListener()); |
34 |
asyncCtx.setTimeout( 9000 ); |
36 |
ThreadPoolExecutor
executor = (ThreadPoolExecutor) request |
37 |
.getServletContext().getAttribute( "executor" ); |
39 |
executor.execute( new AsyncRequestProcessor(asyncCtx,
secs)); |
40 |
long endTime
= System.currentTimeMillis(); |
41 |
System.out.println( "AsyncLongRunningServlet
End::Name=" |
42 |
+
Thread.currentThread().getName() + "::ID=" |
43 |
+
Thread.currentThread().getId() + "::Time
Taken=" |
44 |
+
(endTime - startTime) + "
ms." ); |
Run
Async Servlet
Now
when we will run above servlet with URL as http://localhost:8080/AsyncServletExample/AsyncLongRunningServlet?time=8000
we get the same response and logs
as:
1 |
AsyncLongRunningServlet
Start::Name=http-bio-8080- exec -50::ID=124 |
2 |
AsyncLongRunningServlet
End::Name=http-bio-8080- exec -50::ID=124::Time
Taken=1 ms. |
4 |
AppAsyncListener
onComplete |
If
we run with time as 9999, timeout occurs and we get response at client side as
“TimeOut Error in Processing” and in logs:
01 |
AsyncLongRunningServlet
Start::Name=http-bio-8080- exec -44::ID=117 |
02 |
AsyncLongRunningServlet
End::Name=http-bio-8080- exec -44::ID=117::Time
Taken=1 ms. |
04 |
AppAsyncListener
onTimeout |
05 |
AppAsyncListener
onError |
06 |
AppAsyncListener
onComplete |
07 |
Exception in thread "pool-5-thread-6" java.lang.IllegalStateException:
The request associated with the AsyncContext has already completed
processing. |
08 |
at
org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:439) |
09 |
at
org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:197) |
10 |
at
com.journaldev.servlet.async.AsyncRequestProcessor.run(AsyncRequestProcessor.java:27) |
11 |
at
java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) |
12 |
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) |
13 |
at
java.lang.Thread.run(Thread.java:680) |
Notice
that servlet thread finished execution quickly and all the major processing work
is happening in other thread.
Thats
all for Async Servlet, I hope you liked it.