• SpringBoot异步任务获取HttpServletRequest


    前言

    在使用框架日常开发中需要在controller中进行一些异步操作减少请求时间,但是发现在使用@Anysc注解后会出现Request对象无法获取的情况,本文就此情况给出完整的解决方案

    原因分析

    @Anysc注解会开启一个新的线程,主线程的Request和子线程是不共享的,所以获取为null

    在使用springboot的自定带的线程共享后,代码如下,Request不为null,但是偶发的其中body/head/urlparam内容出现获取不到的情况,是因为异步任务在未执行完毕的情况下,主线程已经返回,拷贝共享的Request对象数据被清空

    1. ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    2. //设置子线程共享
    3. RequestContextHolder.setRequestAttributes(servletRequestAttributes, true);
    4. HttpServletRequest request = servletRequestAttributes.getRequest();

    解决方案


    前置条件
            启动类添加@EnableAsync注解
            标记@Async的异步方法不能和调用者在同一个class中

    pom配置

    1. com.alibaba
    2. transmittable-thread-local
    3. 2.11.0

    requrest共享
    通过TransmittableThreadLocal对象进行线程对象共享

    1. public class CommonUtil {
    2. public static TransmittableThreadLocal requestTransmittableThreadLocal = new TransmittableThreadLocal();
    3. public static void shareRequest(HttpServletRequest request){
    4. requestTransmittableThreadLocal.set(request);
    5. }
    6. public static HttpServletRequest getRequest(){
    7. HttpServletRequest request = requestTransmittableThreadLocal.get();
    8. if(request!=null){
    9. return requestTransmittableThreadLocal.get();
    10. }else{
    11. ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    12. if(requestAttributes!=null){
    13. return requestAttributes.getRequest();
    14. }else{
    15. return null;
    16. }
    17. }
    18. }
    19. public static void remove(){
    20. requestTransmittableThreadLocal.remove();
    21. }
    22. }

    :系统中所有Request获取需要统一从CommonUtil指定来源,例如token鉴权

    自定义request过滤器

    通过自定义过滤器对Request的内容进行备份保存,主线程结束时Request清除结束不会影响到子线程的相应参数的获取,也适用于增加拦截器/过滤器后body参数无法重复获取的问题。需要注意的是对header参数处理时key要忽略大小写
    1. public class HttpServletRequestReplacedFilter implements Filter, Ordered {
    2. @Override
    3. public void destroy() {
    4. }
    5. @Override
    6. public void doFilter(ServletRequest request, ServletResponse response,
    7. FilterChain chain) throws IOException, ServletException {
    8. ServletRequest requestWrapper = null;
    9. if (request instanceof HttpServletRequest) {
    10. requestWrapper = new RequestWrapper((HttpServletRequest) request);
    11. }
    12. //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
    13. // 在chain.doFiler方法中传递新的request对象
    14. if (requestWrapper == null) {
    15. chain.doFilter(request, response);
    16. } else {
    17. chain.doFilter(requestWrapper, response);
    18. }
    19. }
    20. @Override
    21. public void init(FilterConfig arg0) throws ServletException {
    22. }
    23. @Override
    24. public int getOrder() {
    25. return 10;
    26. }
    27. }
    28. public class RequestWrapper extends HttpServletRequestWrapper{
    29. private final byte[] body;
    30. private final HashMap headMap;
    31. private final HashMap requestParamMap;
    32. public RequestWrapper(HttpServletRequest request) throws IOException {
    33. super(request);
    34. body = CommonUtil.getBodyString(request).getBytes(Charset.forName("UTF-8"));
    35. headMap = new HashMap();
    36. Enumeration headNameList = request.getHeaderNames();
    37. while (headNameList.hasMoreElements()){
    38. String key = headNameList.nextElement();
    39. headMap.put(key.toLowerCase(),request.getHeader(key));
    40. }
    41. requestParamMap = new HashMap<>();
    42. Enumeration parameterNameList = request.getParameterNames();
    43. while (parameterNameList.hasMoreElements()){
    44. String key = parameterNameList.nextElement();
    45. requestParamMap.put(key,request.getParameter(key));
    46. }
    47. }
    48. @Override
    49. public BufferedReader getReader() throws IOException {
    50. return new BufferedReader(new InputStreamReader(getInputStream()));
    51. }
    52. @Override
    53. public ServletInputStream getInputStream() throws IOException {
    54. final ByteArrayInputStream bais = new ByteArrayInputStream(body);
    55. return new ServletInputStream() {
    56. @Override
    57. public int read() throws IOException {
    58. return bais.read();
    59. }
    60. @Override
    61. public boolean isFinished() {
    62. return false;
    63. }
    64. @Override
    65. public boolean isReady() {
    66. return false;
    67. }
    68. @Override
    69. public void setReadListener(ReadListener readListener) {
    70. }
    71. };
    72. }
    73. @Override
    74. public String getHeader(String name) {
    75. return headMap.get(name.toLowerCase());
    76. }
    77. @Override
    78. public String getParameter(String name) {
    79. return requestParamMap.get(name);
    80. }
    81. }

    自定义任务执行器


    用于拦截异步任务执行,在任务执前统一进行Request共享操作,且可以定义多个,不影响原有的异步任务代码

    1. public class CustomTaskDecorator implements TaskDecorator {
    2. @Override
    3. public Runnable decorate(Runnable runnable) {
    4. ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    5. HttpServletRequest request = requestAttributes.getRequest();
    6. System.out.println("异步任务共享request");
    7. return () -> {
    8. try {
    9. CommonUtil.shareRequest(request);
    10. runnable.run();
    11. } finally {
    12. CommonUtil.remove();
    13. }
    14. };
    15. }
    16. }
    17. @Configuration
    18. public class TaskExecutorConfig {
    19. @Bean()
    20. public Executor taskExecutor() {
    21. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    22. executor.setCorePoolSize(10);
    23. executor.setMaxPoolSize(20);
    24. executor.setQueueCapacity(200);
    25. executor.setKeepAliveSeconds(60);
    26. executor.setThreadNamePrefix("taskExecutor-");
    27. executor.setAwaitTerminationSeconds(60);
    28. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    29. executor.initialize();
    30. return executor;
    31. }
    32. @Bean("shareTaskExecutor")
    33. public Executor hpTaskExecutor() {
    34. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    35. executor.setCorePoolSize(10);
    36. executor.setMaxPoolSize(20);
    37. executor.setQueueCapacity(200);
    38. executor.setKeepAliveSeconds(60);
    39. executor.setThreadNamePrefix("shareTaskExecutor-");
    40. executor.setWaitForTasksToCompleteOnShutdown(true);
    41. executor.setAwaitTerminationSeconds(60);
    42. // 增加 TaskDecorator 属性的配置
    43. executor.setTaskDecorator(new CustomTaskDecorator());
    44. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    45. executor.initialize();
    46. return executor;
    47. }
    48. }

    调用示例

    给@Anysc注解指定进行共享拦截的任务执行器即可

    1. @PostMapping("/testAsync")
    2. @ResponseBody
    3. public Object testAsync(@RequestBody Map params) throws Exception{
    4. Result result = Result.okResult();
    5. asyncUtil.executeAsync();
    6. return result;
    7. }
    8. @Component
    9. public class AsyncUtil {
    10. @Async("shareTaskExecutor")
    11. public void executeAsync () throws InterruptedException {
    12. System.out.println("开始执行executeAsync");
    13. Thread.sleep(3000);
    14. System.out.println("结束执行executeAsync");
    15. }
    16. }

  • 相关阅读:
    Stream流
    l8-d10 TCP协议是如何实现可靠传输的
    openGauss的索引组织表
    《前端面试题》- React - 如何区分函数组件和类组件
    SpringBoot整合Sharding-JDBC通过标准分片策略(Standard)实现分表操作
    面向未来的大数据核心技术都有什么?
    超神之路 数据结构 1 —— 关于数组的基本认识
    逻辑回归原理
    循环神经网络(RNN)简单介绍及实现(基于表面肌电信号)
    【CQF Finance Class 1 现金时间价值】
  • 原文地址:https://blog.csdn.net/shun35/article/details/136193477