springboot2.0 如何异步操作,@Async失效,无法进入异步
</h1>
<div ></div>
<div class="postBody">
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;
</span><span style="color: #0000ff;">private</span> Logger logger = LoggerFactory.getLogger(LogServiceImpl.<span style="color: #0000ff;">class</span><span style="color: #000000;">);
@Override
@Async(</span>"logThread"<span style="color: #000000;">)//对应线程池里的bean
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> writeLog(SysLog sysLog)
{
</span><span style="color: #0000ff;">long</span> start =<span style="color: #000000;"> System.currentTimeMillis();
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
Thread.sleep(</span>3000<span style="color: #000000;">);//为了测试加入,绝对不是为了以后给客户优化性能加入
}</span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e)
{
e.printStackTrace();
}
sysLogRepository.save(sysLog);
</span><span style="color: #0000ff;">long</span> end =<span style="color: #000000;"> System.currentTimeMillis();
logger.info(</span>"异步日志入库完成,耗时:"+(end-start)+"毫秒,入库内容:"+<span style="color: #000000;">sysLog);
}
}
这里有一个小坑,writeLog函数不能由本类内其他函数调用,必须是外部使用者调用,如果内部函数调用会出现代理绕过的问题,从而无法执行异步,不会出错,会变成同步操作。看起来就是@Async失效的状态。
例如:
@Service public class LogServiceImpl implements LogService {
@Resource
SysLogRepository sysLogRepository;
</span><span style="color: #0000ff;">private</span> Logger logger = LoggerFactory.getLogger(LogServiceImpl.<span style="color: #0000ff;">class</span><span style="color: #000000;">);
@Override
@Async(</span>"logThread")<span style="color: #008000;">//</span><span style="color: #008000;">对应线程池里的bean</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> writeLog(SysLog sysLog)
{
</span><span style="color: #0000ff;">long</span> start =<span style="color: #000000;"> System.currentTimeMillis();
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
Thread.sleep(</span>3000<span style="color: #000000;">);
}</span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e)
{
e.printStackTrace();
}
sysLogRepository.save(sysLog);
</span><span style="color: #0000ff;">long</span> end =<span style="color: #000000;"> System.currentTimeMillis();
logger.info(</span>"异步日志入库完成,耗时:"+(end-start)+"毫秒,入库内容:"+<span style="color: #000000;">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}