Servlet 3.0开始起,已经支持异步HTTP请求处理。但是Nutz版本到现在依然没有对异步处理进行支持。我这里给出了一些分析和建议。
首先,在Nutz框架内,通常我们是通过NutFilter来接管用户的所有请求。这是各框架的一种常见做法。但是,它与Servlet中关于异步处理的实现理念有一点点冲突。
根据Nutz框架的设计,显然不认为长时间的工作由NutFilter来完成有任何问题。但在架构上来看,我感觉Servlet的设计者认为,Filter应该是完成一些快速的处理工作,而可能长时间处理的任务应放到Servlet中间完成。因此,Servlet 3.0中,Filter虽然包括了异步支持这个选项,但实际它本身并不支持内部有异步的操作。这就让扩展NutFilter来支持异步处理存在天然的难度。
下面提供了我使用Nutz支持异步处理的一些实践。
为了支持异步请求,首先,需要将拦截器设到NutServlet上,而不是NutFilter上。web.xml修改如下:
nutzmvc
org.nutz.mvc.NutServlet
modules
com.example.MainModule
true
nutzmvc
/*
其次,修改可能触发异步操作的MethodInvokeProcessor,自定义一个如下:
public class AsyncMethodInvokeProcessor extends AbstractProcessor {
@Override
public void process(ActionContext ac) throws Throwable {
Object module = ac.getModule();
Method method = ac.getMethod();
Object[] args = ac.getMethodArgs();
try {
Object invokeResult = null;
// 原有过程有可能被打断,准备接受一个异步Future对象。
if (Mvcs.disableFastClassInvoker)
invokeResult = method.invoke(module, args);
else
invokeResult = FastClassFactory.invoke(module, method, args);
// 根据是否返回一个异步对象,来判断是否该启动异步过程
if (invokeResult instanceof CompletableFuture) {
AsyncContext async = Mvcs.getReq().startAsync(Mvcs.getReq(), Mvcs.getResp());
ac.set("AsyncContext", async);
CompletableFuture<?> future = (CompletableFuture<?>) invokeResult;
future.thenAccept(result->{
// 异步模式下,收到结果再继续进行下一步处理。
ac.setMethodReturn(result);
try {
doNext(ac);
} catch (Throwable e) {
// 如何将异步过程的错误进行处理?没有好的想法。
e.printStackTrace();
}
});
} else {
ac.setMethodReturn(invokeResult);
doNext(ac);
}
}
catch (IllegalAccessException e) {
throw Lang.unwrapThrow(e);
}
catch (IllegalArgumentException e) {
throw Lang.unwrapThrow(e);
}
catch (InvocationTargetException e) {
throw e.getCause();
}
}
}
这里假设异步操作返回的是一个CompletableFuture对象,这也是Java8中间对异步操作支持非常好的一个系统对象,与Promise对象很类似。
最后,需要在全部IO操作完成后,结束异步过程。
public class AsyncEndProcessor extends AbstractProcessor {
@Override
public void process(ActionContext ac) throws Throwable {
AsyncContext async = (AsyncContext) ac.get("AsyncContext");
if (async!=null) {
async.complete();
}
}
}
自定义Nutz的chain文件,注意一下里面改了两个Processor:
{
"async" : {
"ps" : [
"org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor",
"org.nutz.mvc.impl.processor.EncodingProcessor",
"org.nutz.mvc.impl.processor.ModuleProcessor",
"!org.nutz.integration.shiro.NutShiroProcessor",
"org.nutz.mvc.impl.processor.ActionFiltersProcessor",
"org.nutz.mvc.impl.processor.AdaptorProcessor",
"!org.nutz.plugins.validation.ValidationProcessor",
"com.example.async.AsyncMethodInvokeProcessor",
"org.nutz.mvc.impl.processor.ViewProcessor",
"com.example.async.AsyncEndProcessor",
],
"error" : 'org.nutz.mvc.impl.processor.FailProcessor'
}
}
来个支持该方式的样例方法:
@At("/HelloAsync")
@Chain("async")
public CompletableFuture Hello(HttpServletRequest request, HttpServletResponse response) throws IOException{
User user = new User();
user.name = "abc";
return CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(10000);
} catch (Exception e) {
}
user.surname = "ttyt";
return user;
});
}
到此为止,一个能够支持异步的Nutz框架就大功告成了。
希望有兴趣的朋友踊跃测试和提意见,也欢迎各种讨论。本人QQ:356386。
也希望对异步处理的支持能够尽快进入到Nutz的主版本内。