異步Servlet有時(shí)需要一個(gè)攔截器,但必須是異步的Filter,否則將會(huì)報(bào)錯(cuò):
嚴(yán)重: Servlet.service() for servlet [com.learn.servlet3.async.DemoAsyncLinkServlet] in context with path [/servlet3] threw exceptionjava.lang.IllegalStateException: Not supported.
因此異步的Filter攔截異步Servlet,不要搞錯(cuò)。
我們需要預(yù)先定義這么一個(gè)異步連接,每秒輸出一個(gè)數(shù)字字符串,從0到99,諸如下面HTML字符串:
<div>2</div>
最后輸出Done!
給出兩個(gè)訪問地址,一個(gè)用于被攔截(/demoAsyncLink),一個(gè)用于單獨(dú)訪問(/demoAsyncLink2),便于對照:
/**
* 模擬長連接實(shí)現(xiàn),每秒輸出一些信息
*
* @author yongboy
* @date 2011-1-14
* @version 1.0
*/
@WebServlet(
urlPatterns = { "/demoAsyncLink", "/demoAsyncLink2" },
asyncSupported = true
)
public class DemoAsyncLinkServlet extends HttpServlet {
private static final long serialVersionUID = 4617227991063927036L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
response.setHeader("Connection", "Keep-Alive");
response.setHeader("Proxy-Connection", "Keep-Alive");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<div>Start ...</div>");
out.flush();
AsyncContext asyncContext = request.startAsync(request, response);
new CounterThread(asyncContext).start();
}
private static class CounterThread extends Thread {
private AsyncContext asyncContext;
public CounterThread(AsyncContext asyncContext) {
this.asyncContext = asyncContext;
}
@Override
public void run() {
int num = 0;
int max = 100;
int interval = 1000;
// 必須設(shè)置過期時(shí)間,否則將會(huì)出連接過期,線程無法運(yùn)行完畢異常
asyncContext.setTimeout((max + 1) * interval);
PrintWriter out = null;
try {
try {
out = asyncContext.getResponse().getWriter();
} catch (IOException e) {
e.printStackTrace();
}
while (true) {
out.println("<div>" + (num++) + "</div>");
out.flush();
if (num >= max) {
break;
}
Thread.sleep(interval);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (out != null) {
out.println("<div>Done !</div>");
out.flush();
out.close();
}
asyncContext.complete();
}
}
}
若想讓HttpServletResponse包裝器發(fā)揮包裝的效果,須調(diào)用帶有參數(shù)的startAsync(request, response)方法開啟異步輸出,否則MarkWapperedResponse將不起作用。因?yàn)椋舨粋鬟f現(xiàn)有的request,response對象,將會(huì)調(diào)用原生的request和response對象。
在tomcat7下面,異步連接超時(shí)時(shí)間為10000單位,若不指定超時(shí)時(shí)間,遞增的數(shù)字不會(huì)按照預(yù)想完整輸出到99。
我們假設(shè)需要定義這樣一個(gè)Filter,為每一次的異步輸出的內(nèi)容增加一個(gè)特殊標(biāo)記:
<!--marked filter-->
<div>2</div>
邏輯很簡單,作為示范也不需要多復(fù)雜。
再看看一個(gè)異步Filter的代碼:
/**
* 異步攔截器
*
* @author yongboy
* @date 2011-1-14
* @version 1.0
*/
@WebFilter(
dispatcherTypes = {
DispatcherType.REQUEST,
DispatcherType.FORWARD,
DispatcherType.INCLUDE
},
urlPatterns = { "/demoAsyncLink" },
asyncSupported = true //支持異步Servlet
)
public class AsyncServletFilter implements Filter {
private Log log = LogFactory.getLog(AsyncServletFilter.class);
public AsyncServletFilter() {
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
log.info("it was filted now");
MarkWapperedResponse wapper = new MarkWapperedResponse(
(HttpServletResponse) response);
chain.doFilter(request, wapper);
}
public void init(FilterConfig fConfig) throws ServletException {
}
}
很簡單,添加上asyncSupported = true屬性即可。在上面Filter中包裝了一個(gè)HttpServletResponse對象,目的在于返回一個(gè)定制的PrintWriter對象,簡單重寫flush方法(不見得方法多好):
/**
* HttpServletResponse簡單包裝器,邏輯簡單
*
* @author yongboy
* @date 2011-1-14
* @version 1.0
*/
public class MarkWapperedResponse extends HttpServletResponseWrapper {
private PrintWriter writer = null;
private static final String MARKED_STRING = "<!--marked filter--->";
public MarkWapperedResponse(HttpServletResponse resp) throws IOException {
super(resp);
writer = new MarkPrintWriter(super.getOutputStream());
}
@Override
public PrintWriter getWriter() throws UnsupportedEncodingException {
return writer;
}
private static class MarkPrintWriter extends PrintWriter{
public MarkPrintWriter(OutputStream out) {
super(out);
}
@Override
public void flush() {
super.flush();
super.println(MARKED_STRING);
}
}
}
在瀏覽器端請求被包裝的/demoAsyncLink鏈接,截圖以及firebug檢測截圖如下:
可以在瀏覽器內(nèi)同時(shí)請求/demoAsyncLink2前后作為對比一下。
