场景

  1. JavaWebServlet 3.0规范已经支持异步ServletRequest, 有这个异步请求,容器(
    Tomcat)线程池只需要少量的线程即可处理大量的并发请求,因为处理请求的线程可以把异步AsyncContext交给业务线程池处理之后立即返回。Node.js的异步IO应该也是用的这种技术,用单线程处理异步请求IO。那么在使用Jfinal框架时如何引入异步ServletRequest?

说明

  1. JavaWeb的常规容器处理用户请求都是通过容器内定义的线程池进行处理。当所有线程处理的业务耗时比较长的时候,比如2秒,线程池里没有空余的线程响应用户的请求,就会造成容器阻塞,丢失接下来的所有用户请求。如果用异步IO就没这种情况,只要线程池的线程提交到业务线程足够快,那么就能一直处理用户请求数据,不会丢失用户请求。而业务线程使用完之后,新提交的任务会进入等待,直到内存耗尽才有可能丢失用户请求。
  2. 要使用异步Request,那么需要先配置部署描述符web.xml或者代码配置Filter的异步支持。添加<async-supported>元素值为true
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee web-app_3_0.xsd"
         version="3.0">
    <filter>
        <filter-name>jfinal</filter-name>
        <filter-class>com.jfinal.core.JFinalFilter</filter-class>
        <init-param>
            <param-name>configClass</param-name>
            <param-value>com.demo.common.DemoConfig</param-value>
        </init-param>
        <async-supported>true</async-supported>
    </filter>

    <filter-mapping>
        <filter-name>jfinal</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
  1. 如果使用undertow,那么通过编码在UndertowServer的子类重载configJFinalFilter加入异步支持。
@Override
protected void configJFinalFilter() {
    FilterInfo filterInfo = Servlets.filter("jfinal", getJFinalFilter());
    filterInfo.setAsyncSupported(true);
    filterInfo.addInitParam("configClass", config.getJFinalConfig());
    filterInfo.addInitParam("staticResourcePostfix","jpg;webp;gif;png;js;css;map;ttf;");
    deploymentInfo.addFilter(filterInfo).addFilterUrlMapping("jfinal", "/*", DispatcherType.REQUEST);
}
  1. 如果不加入这个异步支持的配置,会报以下错误:
UT010026: Async is not supported for this request, as not all filters or Servlets were marked as supporting async
  1. 在获取异步IO,首先就要调用Servlet.startAsync得到一个AsyncContext异步上下文,他表示把请求放入异步模式。在将此请求放入异步模式后,在出站方向调用的任何过滤器都可以使用(AsyncContext.hasOriginalRequestAndResponse(), 一直返回true)这一点作为一个指示,即它们在入站调用期间添加的任何请求和响应包装器都不需要在异步操作期间存在,因此它们的任何关联资源都可以被释放。
  2. 通过asyncContext.getRequest()asyncContext.getResponse()来获取原始的ServletRequestServletResponse对象。比如获取参数request.getParameter()或者获取输出流response.getOutputStream().
  3. 在使用jfinal的时候,如果在Controller开始异步模式,需要执行renderNull();来执行一个空渲染,就是不调用response操作,避免response关闭。
  4. 在异步线程执行完之后任务之后,需要调用AsyncContext.complete()来关闭异步模式,这时候response会被关闭。 注意,异步模式启用关闭有可能会抛出异常,最好把它放在异常处理代码里。可以封装一个Controller子类来处理异步模式的启用和完成。

例子

  1. 这个例子是使用异步模式来删除博客文章。关于jfinal的博客例子项目教程参考学院课程.开发JavaWeb网站精讲-基于JFinal框架[5],这里有很多常用功能的例子. 当然也可以去jfinal官网下载原始的例子项目[6]

BaseController.java

package com.demo.common;

import com.jfinal.core.Controller;
import com.jfinal.kit.LogKit;

import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BaseController extends Controller {

    protected AsyncContext asyncContext = null;

    protected AsyncContext startAsync(){
        try {
            asyncContext = getRequest().startAsync();
            asyncContext.addListener(new MyAsyncListener());
            setHttpServletRequest((HttpServletRequest) asyncContext.getRequest());
            setHttpServletResponse((HttpServletResponse) asyncContext.getResponse());

            renderNull();
        }catch (Exception e){
            if(asyncContext != null) {
                asyncContext.complete();
                asyncContext = null;
            }
            LogKit.error(e.getMessage());
        }
        return asyncContext;
    }

    protected void completeAsync(){
        if(asyncContext == null)
            return;

        try{
            asyncContext.complete();
        }catch (Exception e){
            LogKit.error(e.getMessage());
        }
    }

    public void render(){
        if(asyncContext == null)
            return;

        try {
            getRender().setContext((HttpServletRequest) asyncContext.getRequest(),
                    (HttpServletResponse) asyncContext.getResponse()).render();
        }catch (Exception e){
            LogKit.error(e.getMessage());
        }
    }
}

BlogController.java

public class BlogController extends BaseController {

...
public void delete() {

	startAsync();
	ThreadPoolKit.execute(()->{
		service.deleteById(getParaToInt());
		redirect("/blog");
		render();
		completeAsync();
	});
}

DemoConfig

public void configConstant(Constants me) {

int processors = Runtime.getRuntime().availableProcessors();
ThreadPoolKit.init(processors);

参考

  1. 关于servlet3.0中的异步servlet
  2. jfinal如何配置异步的context
  3. Async 拦截器
  4. ServletRequest
  5. 开发JavaWeb网站精讲-基于JFinal框架-1
  6. JFinal 极速开发