springboot2.0 非同步操作,@Async失效,無法進入非同步
阿新 • • 發佈:2018-11-16
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 implementsLogService { @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}