使用業務場景: 對於有的請求業務處理流程可能比較耗時,比如長查詢,遠程調用等,主線程會被一直占用,而tomcat線程池線程有限,處理量就會下降
servlet3.0以后提供了對異步處理的支持,springmvc封裝了異步處理,滿足用戶請求后,主線程很快結束,並開啟其它線程處理任務,並將處理結果響應用戶,而主線程就可以接收更多請求。
參考官方解釋:
https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support
原理簡介:對於一次請求,比如front/test
1,springmvc開啟副線程處理業務(將Callable 提交到 TaskExecutor)
2,DispatcherServlet和所有的Filter退出web容器的線程,但是response 保持打開狀態
3,Callable返回結果,SpringMVC將請求front/test重新派發給容器(再重新請求一次front/test),恢復之前的處理;
4,DispatcherServlet重新被調用,將結果返回給用戶
使用條件:mvc4.0以上,servlet3.0以上
使用示例
1 import java.util.concurrent.Callable;
2 import java.util.concurrent.TimeUnit; 3 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RestController; 8 @RestController 9 public class RestfulAsync { 10 private Logger logger = LoggerFactory.getLogger(getClass()); 11 @RequestMapping("/front/test") 12 public Callable<String> order() { 13 logger.info("主線程開始"); 14 Callable<String> result = () -> { 15 logger.info("副線程開始"); 16 TimeUnit.SECONDS.sleep(1); 17 logger.info("副線程返回"); 18 return "success"; 19 }; 20 logger.info("主線程返回"); 21 return result; 22 } 23 }
配置文件:
1,web.xml 3.0以上
<!--mvc配置--> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!--在此處增加異步支持--> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> 注意所有的過濾器filter也必須增加異步支持<async-supported>true</async-supported>如: <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 該值缺省為false,表示生命周期由SpringApplicationContext管理,設置為true則表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 2,spring-mv.xml配置 注意必須指定所需的命名空間 xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- spring mvc 異步處理基於任務線程池 --> <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="10" /><!--核心線程數 --> <property name="maxPoolSize" value="100" /><!--最大線程數 --> <property name="queueCapacity" value="1024" /><!--緩沖隊列大小 --> <property name="threadNamePrefix" value="http-do-business-pool-" /><!--線程池中產生的線程名字前綴 --> <property name="keepAliveSeconds" value="30" /><!--線程池中空閑線程的存活時間單位秒 --> </bean> <mvc:annotation-driven> <mvc:async-support task-executor="taskExecutor" //此處指定自定義線程池,否則異步處理會使用默認線程池SimpleAsyncTaskExecutor,這個線程大小無限制,會有問題 // 會提示:An Executor is required to handle java.util.concurrent.Callable return values.Please, configure a TaskExecutor in the MVC config under "async support".The SimpleAsyncTaskExecutor currently in use is not suitable under load default-timeout="1000"> //超時時間,單位毫秒 <mvc:callable-interceptors> <bean class="com.ronglian.bms.commons.interceptor.MyCallableInterceptor" /> // 指定異步處理超時攔截器 </mvc:callable-interceptors> </mvc:async-support> </mvc:annotation-driven>
3,異步處理超時攔截器示例:
import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import javax.servlet.http.HttpServletResponse; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.async.CallableProcessingInterceptor; import com.google.gson.Gson; import com.ronglian.bms.restful.ResultEnum; /** * 異步處理超時攔截器,處理業務超時的情況,xml配置見 * <mvc:annotation-driven> <mvc:async-support task-executor="taskExecutor" default-timeout="1000"> <mvc:callable-interceptors> <bean class="com.ronglian.bms.commons.interceptor.MyCallableInterceptor" /> </mvc:callable-interceptors> </mvc:async-support> </mvc:annotation-driven> * @author zli * @Date 2019-03-06 */ public class MyCallableInterceptor implements CallableProcessingInterceptor{ public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception { HttpServletResponse servletResponse = request.getNativeResponse(HttpServletResponse.class); if (!servletResponse.isCommitted()) { //return "service time out"; servletResponse.setContentType("application/json;charset=utf-8"); String obj = this.buildResult(ResultEnum.ERROR_TIME_OUT.getCode(), ResultEnum.ERROR_TIME_OUT.getMsg(), null); servletResponse.getWriter().write(obj); servletResponse.getWriter().close(); } return null; } private String buildResult(String headerCode, String headMsg, Object bodyResult) { Map<String, Object> resultMap = new HashMap<String, Object>(); Map<String, String> headMap = new HashMap<String, String>(); headMap.put("retCode", headerCode); headMap.put("retMsg", headMsg); resultMap.put("header", headMap); resultMap.put("body", bodyResult); Gson g = new Gson(); return g.toJson(resultMap); } }
另外一種帶超時處理的Callable,即WebAsyncTask,示例代碼如下
/** * WebAsyncTask示例,帶處理超時的Callable * @return */ @RequestMapping("/front/testwithtimeout") public WebAsyncTask<String> test2() { logger.info("主線程開始"); Callable<String> result = () -> { logger.info("副線程開始"); try { TimeUnit.SECONDS.sleep(4); } catch (Exception e) { // TODO: handle exception } logger.info("副線程返回"); return "success"; }; logger.info("主線程返回"); WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result); wat.onTimeout(new Callable<String>() { @Override public String call() throws Exception { // TODO Auto-generated method stub return "超時"; } }); return wat; }