欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot Controller异步调用接口实现@Async

程序员文章站 2024-03-24 08:20:10
...

背景:
后端根据前端传的参数,收集所有符合参数的日志并打印为csv,数据量为10W

因此实现要求:接口异步,不阻塞其他功能的调用

原计划打算另起线程处理,用消息队列传递消息给对应的监听进程,参考:由设计模式引起的多线程思考
后来查到SpringBoot有自带的解决方案@Async,其调用时controller层接口实测能够完成异步调用。
参考:
https://www.jianshu.com/p/2d4b89c7a3f1
https://www.cnblogs.com/huanzi-qch/p/11231041.html
整体流程 + 建立多个线程池并制定其中一个 + service执行方法有无返回值的处理 + 异常处理:
https://www.cnblogs.com/lshan/p/10875372.html
异步处理方法的参考:
https://blog.csdn.net/hry2015/article/details/67640534

结合@Async实现的进度实时展示的demo参考:
https://www.cnblogs.com/yjmyzz/p/how-to-use-Async-annotation-in-spring-mvc.html

具体实现:
线程池配置:

@Configuration
@EnableAsync
public class AsyncConfig {
    private static final ExtLogger LOGGER = ExtLogger.create(AsyncConfig.class);
 
    public static final String LOG_EXPORT_EXECUTOR_NAME = "logExportExecutor";
 
    /**
     * Operation Log export executor.
     *
     * @return the executor
     */
    @Bean(LOG_EXPORT_EXECUTOR_NAME)
    public Executor logExportExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置线程初始化数量
        executor.setCorePoolSize(1);
        // 设置线程池最大数量
        executor.setMaxPoolSize(1);
        // 设置等待队列的大小
        executor.setQueueCapacity(100);
        // 设置线程名称前缀
        executor.setThreadNamePrefix("exportThread");
        executor.setRejectedExecutionHandler((Runnable r, ThreadPoolExecutor exe) -> {
            LOGGER.error("Failed to get new Thread");
        });
        executor.initialize();
        return new DelegatingSecurityContextExecutor(executor);
    }
}

return new DelegatingSecurityContextExecutor(executor);
SpringBoot自带的线程池,目的在于拿到启用新线程时能够拿到父线程的认证有关的信息

guiApplication加入async扫描:@EnableAsync

Service层演示代码:

@Async(AsyncConfig.LOG_EXPORT_EXECUTOR_NAME)
    public Future<StreamingResponseBody> exportLog() throws Exception {
        long currentTimeMillis = System.currentTimeMillis();
        StreamingResponseBody str = dataService.download(FileUtil.fileUrl(ExternalConfig.INSTANCE.EXPORT_LOG_SAVE_PATH,
                LoginContext.getCurrentUsername()), "test.csv");
        LOGGER.error("task1 耗时:{}", System.currentTimeMillis() - currentTimeMillis);
        return new AsyncResult<>(str);
    }
 
    @Async(AsyncConfig.LOG_EXPORT_EXECUTOR_NAME)
    public Future<String> task2() throws Exception {
        long currentTimeMillis = System.currentTimeMillis();
        Thread.sleep(2000);
        LOGGER.error("task2 耗时:{}", System.currentTimeMillis() - currentTimeMillis);
        return new AsyncResult<>("task2 执行完毕");
    }
 
    @Async(AsyncConfig.LOG_EXPORT_EXECUTOR_NAME)
    public Future<String> task3() throws Exception {
        long currentTimeMillis = System.currentTimeMillis();
        Thread.sleep(3000);
        LOGGER.error("task3 耗时:{}", System.currentTimeMillis() - currentTimeMillis);
        return new AsyncResult<>("task3 执行完毕");
    }

controller层代码:

@RequestMapping(value = "/export", method = RequestMethod.POST)
    @PreAuthorize("hasAnyAuthority('hpcSecurityAdmin-logView-r','hpcAuditingAdmin-logView-r')")
    public ResponseEntity<StreamingResponseBody> export(OperationLogInputParam operationLogParam, HttpServletRequest request)
            throws Exception{
        long currentTime = System.currentTimeMillis();
        HttpHeaders headers = dataService.getHeadersForDownload(request, "OperationLog.csv");
        Future<StreamingResponseBody> res1 = operationLogService.exportLog();
        Future<String> res2 = operationLogService.task2();
        Future<String> res3 = operationLogService.task3();
//阻塞在此,所有线程完成后启用
        while (true) {
            if(res1.isDone() && res2.isDone() && res3.isDone()) {
                break;
            }
            Thread.sleep(1000);
        }
        StreamingResponseBody srb = res1.get();
        LOGGER.error("主线程执行完毕,返回结果. 耗时 {} ", System.currentTimeMillis() - currentTime);
        return new ResponseEntity(srb, headers, HttpStatus.OK);
    }

需要注意,三个线程调用顺序是1-》2-》3
由于线程池只规定了一个线程可用(防止过多类似请求吃尽资源),因此后端测试时日志打印为1-》2-》3
如果放开线程限制,打印与完成时间有关:2-》3-》1