springboot2.0 如何異步操作,@Async失效,無法進入異步


springboot異步操作可以使用@EnableAsync@Async兩個注解,本質就是多線程和動態代理。

一、配置一個線程池

 

@Configuration
@EnableAsync//開啟異步
public class ThreadPoolConfig {
    @Bean("logThread")
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 設置核心線程數
        executor.setCorePoolSize(4);
        // 設置最大線程數
        executor.setMaxPoolSize(8);
        // 設置隊列容量
        executor.setQueueCapacity(100);
        // 設置線程活躍時間(秒)
        executor.setKeepAliveSeconds(60);
        // 設置默認線程名稱
        executor.setThreadNamePrefix("home.bus.logThread-");
        // 設置拒絕策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任務結束后再關閉線程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

 

 

二、異步操作

比如有一個日志服務需要異步入庫

@Service
public class LogServiceImpl implements LogService {

    @Resource
    SysLogRepository sysLogRepository;

    private Logger logger = LoggerFactory.getLogger(LogServiceImpl.class);

    @Override
    @Async("logThread")//對應線程池里的bean
    public void writeLog(SysLog sysLog)
    {
        long start = System.currentTimeMillis();
        try {
            Thread.sleep(3000);//為了測試加入,絕對不是為了以后給客戶優化性能加入
        }catch (Exception e)
        {
            e.printStackTrace();
        }
        sysLogRepository.save(sysLog);
        long end = System.currentTimeMillis();
        logger.info("異步日志入庫完成,耗時:"+(end-start)+"毫秒,入庫內容:"+sysLog);
    }
   
}

這里有一個小坑,writeLog函數不能由本類內其他函數調用,必須是外部使用者調用,如果內部函數調用會出現代理繞過的問題,從而無法執行異步,不會出錯,會變成同步操作。看起來就是@Async失效的狀態。

例如:

 

@Service
public class LogServiceImpl implements LogService {

    @Resource
    SysLogRepository sysLogRepository;

    private Logger logger = LoggerFactory.getLogger(LogServiceImpl.class);

    @Override
    @Async("logThread")//對應線程池里的bean
    public void writeLog(SysLog sysLog)
    {
        long start = System.currentTimeMillis();
        try {
            Thread.sleep(3000);
        }catch (Exception e)
        {
            e.printStackTrace();
        }
        sysLogRepository.save(sysLog);
        long end = System.currentTimeMillis();
        logger.info("異步日志入庫完成,耗時:"+(end-start)+"毫秒,入庫內容:"+sysLog);
    }

 public void doSysLog(String action,String event)
    {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        SysLog sysLog = new SysLog();
        sysLog.setAction(action);
        sysLog.setEvent(event);
        sysLog.setHost(NetworkUtils.getIpAddress(request));
        sysLog.setUserName((String)request.getSession().getAttribute("userName"));
        sysLog.setInsertTime(LocalDateTime.now());

        wirteLog(sysLog);//這里不會進入異步
    }
   
}

 

使用doSyslog調用異步函數wirteLog,最終會是一個同步方法。為什么不直接在doSysLog函數加上異步注解?因為RequestContextHolder在異步里取不到信息。

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

 

 

三、調用異步

比如登錄controller,登錄成功后調用異步日志入庫

        LoginResult loginResult = loginService.login(userName, password);
        if (loginResult.isLogin()) {
            map.put("userName", userName);
            SysLog sysLog = LogFactory.createSysLog("登錄","登錄成功");
            logService.writeLog(sysLog);//這里異步,完全阻塞,如果之前函數內嵌套調用,這里就阻塞了,把sleep設置大一些可以看得明顯
            return "/index";
        } else {
            map.put("msg", loginResult.getResult());
            map.put("userName", userName);
            return "/user/login";
        }

這里SysLog 對象直接在調用層生成,也就是把doSysLog拆分成兩個部分處理,logService直接調用異步方法,正常情況不會阻塞,直接就到下一步。

結果:

 

[home.bus.logThread-1] INFO  c.h.bus.service.impl.LogServiceImpl - 異步日志入庫完成,耗時:3089毫秒,入庫內容:SysLog{logId=367, userName='admin', host='0:0:0:0:0:0:0:1', action='登錄', event='登錄成功', insertTime=2018-11-16T00:18:32.522} 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM