SpringBoot詳細講解異步任務如何獲取HttpServletRequest
原因分析
- @Anysc注解會開啟一個新的線程,主線程的Request和子線程是不共享的,所以獲取為null
- 在使用springboot的自定帶的線程共享后,代碼如下,Request不為null,但是偶發(fā)的其中body/head/urlparam內(nèi)容出現(xiàn)獲取不到的情況,是因為異步任務在未執(zhí)行完畢的情況下,主線程已經(jīng)返回,拷貝共享的Request對象數(shù)據(jù)被清空
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); //設置子線程共享 RequestContextHolder.setRequestAttributes(servletRequestAttributes, true); HttpServletRequest request = servletRequestAttributes.getRequest();
解決方案
前置條件
- 啟動類添加@EnableAsync注解
- 標記@Async的異步方法不能和調(diào)用者在同一個class中
pom配置
<!-- 阿里線程共享 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.11.0</version> </dependency>
requrest共享
通過TransmittableThreadLocal對象進行線程對象共享
public class CommonUtil { public static TransmittableThreadLocal<HttpServletRequest> requestTransmittableThreadLocal = new TransmittableThreadLocal<HttpServletRequest>(); public static void shareRequest(HttpServletRequest request){ requestTransmittableThreadLocal.set(request); } public static HttpServletRequest getRequest(){ HttpServletRequest request = requestTransmittableThreadLocal.get(); if(request!=null){ return requestTransmittableThreadLocal.get(); }else{ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if(requestAttributes!=null){ return requestAttributes.getRequest(); }else{ return null; } } } public static void remove(){ requestTransmittableThreadLocal.remove(); } }
注:系統(tǒng)中所有Request獲取需要統(tǒng)一從CommonUtil指定來源,例如token鑒權等
自定義request過濾器
通過自定義過濾器對Request的內(nèi)容進行備份保存,主線程結(jié)束時Request清除結(jié)束不會影響到子線程的相應參數(shù)的獲取,也適用于增加攔截器/過濾器后body參數(shù)無法重復獲取的問題。需要注意的是對header參數(shù)處理時key要忽略大小寫
public class HttpServletRequestReplacedFilter implements Filter, Ordered { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper = null; if (request instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) request); } //獲取請求中的流如何,將取出來的字符串,再次轉(zhuǎn)換成流,然后把它放入到新request對象中。 // 在chain.doFiler方法中傳遞新的request對象 if (requestWrapper == null) { chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } } @Override public void init(FilterConfig arg0) throws ServletException { } @Override public int getOrder() { return 10; } }
public class RequestWrapper extends HttpServletRequestWrapper{ private final byte[] body; private final HashMap<String,String> headMap; private final HashMap<String,String> requestParamMap; public RequestWrapper(HttpServletRequest request) throws IOException { super(request); body = CommonUtil.getBodyString(request).getBytes(Charset.forName("UTF-8")); headMap = new HashMap(); Enumeration<String> headNameList = request.getHeaderNames(); while (headNameList.hasMoreElements()){ String key = headNameList.nextElement(); headMap.put(key.toLowerCase(),request.getHeader(key)); } requestParamMap = new HashMap<>(); Enumeration<String> parameterNameList = request.getParameterNames(); while (parameterNameList.hasMoreElements()){ String key = parameterNameList.nextElement(); requestParamMap.put(key,request.getParameter(key)); } } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } @Override public String getHeader(String name) { return headMap.get(name.toLowerCase()); } @Override public String getParameter(String name) { return requestParamMap.get(name); } }
自定義任務執(zhí)行器
用于攔截異步任務執(zhí)行,在任務執(zhí)前統(tǒng)一進行Request共享操作,且可以定義多個,不影響原有的異步任務代碼
public class CustomTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); System.out.println("異步任務共享request"); return () -> { try { CommonUtil.shareRequest(request); runnable.run(); } finally { CommonUtil.remove(); } }; } }
@Configuration public class TaskExecutorConfig { @Bean() public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(200); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("taskExecutor-"); executor.setAwaitTerminationSeconds(60); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } @Bean("shareTaskExecutor") public Executor hpTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(200); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("shareTaskExecutor-"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); // 增加 TaskDecorator 屬性的配置 executor.setTaskDecorator(new CustomTaskDecorator()); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }
調(diào)用示例
給@Anysc注解指定進行共享攔截的任務執(zhí)行器即可
@PostMapping("/testAsync") @ResponseBody public Object testAsync(@RequestBody Map<String, Object> params) throws Exception{ Result result = Result.okResult(); asyncUtil.executeAsync(); return result; }
@Component public class AsyncUtil { @Async("shareTaskExecutor") public void executeAsync () throws InterruptedException { System.out.println("開始執(zhí)行executeAsync"); Thread.sleep(3000); System.out.println("結(jié)束執(zhí)行executeAsync"); } }
到此這篇關于SpringBoot詳細講解異步任務如何獲取HttpServletRequest的文章就介紹到這了,更多相關SpringBoot獲取HttpServletRequest內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- 在 Spring Boot 中使用異步線程時的 HttpServletRequest 復用問題記錄
- SpringBoot異步線程父子線程數(shù)據(jù)傳遞的5種方式
- Spring?Boot異步線程間數(shù)據(jù)傳遞的四種方式
- springboot?正確的在異步線程中使用request的示例代碼
- SpringBoot?異步線程間傳遞上下文方式
- SpringBoot獲取HttpServletRequest的3種方式總結(jié)
- SpringBoot實現(xiàn)任意位置獲取HttpServletRequest對象
- Spring Boot 中正確地在異步線程中使用 HttpServletRequest的方法
相關文章
Springboot整合mybatis開啟二級緩存的實現(xiàn)示例
在一級緩存中,是查詢兩次數(shù)據(jù)庫的,顯然這是一種浪費,既然SQL查詢相同,就沒有必要再次查庫了,直接利用緩存數(shù)據(jù)即可,這種思想就是MyBatis二級緩存的初衷,本文就詳細的介紹了Springboot整合mybatis開啟二級緩存,感興趣的可以了解一下2022-05-05淺析javax.servlet.Servlet,ServletContext接口
本篇文章是對javax.servlet.Servlet,ServletContext接口進行了纖細的分析介紹,需要的朋友參考下2013-07-07連續(xù)調(diào)用多個外部系統(tǒng)寫接口保證數(shù)據(jù)一致性的思路
今天小編就為大家分享一篇關于連續(xù)調(diào)用多個外部系統(tǒng)寫接口保證數(shù)據(jù)一致性的思路,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12