首页 > 其他分享 >SpringBoot异步任务获取HttpServletRequest

SpringBoot异步任务获取HttpServletRequest

时间:2023-12-18 13:45:40浏览次数:35  
标签:HttpServletRequest 异步 return SpringBoot request executor Override new public

 

前言

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

原因分析

  • @Anysc注解会开启一个新的线程,主线程的Request和子线程是不共享的,所以获取为null
  • 在使用springboot的自定带的线程共享后,代码如下,Request不为null,但是偶发的其中body/head/urlparam内容出现获取不到的情况,是因为异步任务在未执行完毕的情况下,主线程已经返回,拷贝共享的Request对象数据被清空
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//设置子线程共享
RequestContextHolder.setRequestAttributes(servletRequestAttributes, true);
HttpServletRequest request = servletRequestAttributes.getRequest();

解决方案

前置条件

  • 启动类添加@EnableAsync注解

  • 标记@Async的异步方法不能和调用者在同一个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();
    }
}

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

自定义request过滤器

通过自定义过滤器对Request的内容进行备份保存,主线程结束时Request清除结束不会影响到子线程的相应参数的获取,也适用于增加拦截器/过滤器后body参数无法重复获取的问题。需要注意的是对header参数处理时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);
        }
        //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新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);
    }
}

自定义任务执行器

用于拦截异步任务执行,在任务执前统一进行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;
    }
}

调用示例

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

    @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("开始执行executeAsync");
        Thread.sleep(3000);
        System.out.println("结束执行executeAsync");
    }
}
    链接:https://www.jianshu.com/p/1f7a9a68b45a

 

标签:HttpServletRequest,异步,return,SpringBoot,request,executor,Override,new,public
From: https://www.cnblogs.com/softidea/p/17911007.html

相关文章

  • spirng、springboot、jdk、maven、tomcat版本问题
    引入springboot依赖时会自动安装spring对应依赖,版本由springboot决定。springboot2.x.x及以下使用jdk11、jdk8都可以,springboot3.x.x最低要求jdk17maven与jdk版本关系,参照链接:https://maven.apache.org/docs/history.htmlApacheTomcat是JakartaEE(JavaEE)技术子集的开源......
  • 异步编码规范
    异步编码规范手写promisepromiseA+规范asyncawait原理generator--忽略Promise1.特点1.1状态不可逆转==》不可从一个状态变为另外一个状态promise的方法finallyfinally方法没有参数,也不会改变Promise的状态,它只是在Promise结束时提供了一个通知机制,让......
  • 数据持久层框架mybatis学习:使用mybatis+SpringBoot完成增删改查
    目录一、MyBatis的应用配置二、使用mybatis+SpringBoot完成增删改查2.1代码实现2.2增删改查接口调用一、MyBatis的应用配置依赖pom.xml注意:版本号的依赖冲突问题<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xm......
  • 使用React+SpringBoot开发一个协同编辑的表格文档
    前言随着云计算和团队协作的兴起,协同编辑成为了许多企业和组织中必不可少的需求。通过协同编辑,多个用户可以同时对同一个文档进行编辑和更新,从而提高工作效率和协作能力。本文小编就将为大家介绍如何使用React+SpringBoot简单的开发一个协同编辑的表格文档。环境准备用到的开发......
  • 2、SpringBoot2之入门案例
    2.1、创建Maven工程2.1.1、创建空项目2.1.2、设置项目名称和路径2.1.3、设置项目sdk2.1.4、项目初始状态注意:需要关闭项目再重新打开,才能看到SpringBoot-Part文件夹2.1.5、配置maven2.1.6、创建module右击SpringBoot-Part文件夹,创建新module选择maven配......
  • SpringBoot开启注册发现并使用feign进行远程调用
    1、安装nacos服务端nacos下载地址https://github.com/alibaba/nacos/releases下载nacos之后解压并启动Nacos默认端口是88482、配置maven坐标信息2.1配置坐标管理<dependencyManagement><dependencies><dependency><groupId>com.alibaba.cloud</......
  • springboot连接mysql出现的SSL问题
    1、连接配置spring:datasource:username:rootpassword:rooturl:jdbc:mysql://192.168.0.1:3307/admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaidriver-class-name:com.mysql.cj.jdbc.Driver出现SSL连接问题2、解......
  • Java医院3D人体智能导诊系统源码 Uniapp+springboot
    “智能导诊”以人工智能手段为依托,为人们提供智能分诊、问病信息等服务,在一定程度上满足了人们自我健康管理、精准挂号等需求。智能导诊可根据描述的部位和病症,给出适合病症的科室参考。智慧导诊页面会显示男性或女性的身体结构图,可切换正面/背面。通过点击部位选项,选择自己身体不......
  • SpringBoot集成Swagger的使用
    一、前言Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务。目标是使客户端和文件系统作为服务器以同样的速度来更新文件的方法,参数和模型紧密集成到服务器。Swagger能够在线自动生成RESTFul接口的文档,同时具备测试接口的功能。简单点来讲就是说......
  • SpringBoot集成多个RabbitMq(多个MQ链接)
    ##2023年12月16日20:25:36 项目中使用RabbitMQ作为应用间信息互通,本次梳理下关于MQ的使用。1、引入依赖<!--引入依赖,使用v2.5.6版本--><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-b......