@Async
来自ling
https://segmentfault.com/a/1190000008981884
https://baijiahao.baidu.com/s?id=1700394969228178875&wfr=spider&for=pc
失效原因
- 没有在@SpringBootApplication启动类当中添加注解@EnableAsync注解。
- 异步方法使用注解@Async的返回值只能为void或者Future。
- 没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
- 同一个类中的方法调用任然需要走proxy
PROPAGATION_NOT_SUPPORTED不生效的原因 同一个类中的方法调用任然需要走proxy 直接调用是不会有效果的
第二点和第三点容易犯......
解决方法:
这里具体说一下第三种情况的解决方法。
- 注解的方法必须是public方法。
- 方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。
- 如果需要从类的内部调用,需要先获取其代理类,下面上代码
@Service
public class XxxService{
public void methodA(){
...
XxxService xxxServiceProxy = SpringUtil.getBean(XxxService.class);
xxxServiceProxy.methodB();
...
}
@Async
public void methodB() {
...
}
- @Transactional 加于private方法, 无效
- @Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效
- @Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效
- @Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效
- @Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效
- @Transactional 加于接口方法后, 被它类的接口方法调用, 有效
- @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效
PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。 PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。 PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。 PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。 PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。 前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
xml方式启用
component-scan.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName" default-lazy-init="false">
<!-- <context:component-scan base-package="com.ling2.core.security.login"
/> <context:component-scan base-package="com.ling2.springbatch" /> -->
<context:annotation-config />
<context:component-scan base-package="com.deloitte.*" />
<!--使用spring的异步@Async简单定义方式 -->
<task:annotation-driven executor="asyncExecutor" />
<task:executor id="asyncExecutor" pool-size="100-10000"
queue-capacity="10" />
</beans>
@Async中的事务No Session found for current thread
注意ling框架中,代码必须放在server下才有事务否则加了@Transactional也没有用 参考MailSendService
http://blog.csdn.net/blueheart20/article/details/44648667
- @Async调用中的事务处理机制
在@Async标注的方法,同时也适用了@Transactional进行了标注;在其调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。
那该如何给这些操作添加事务管理呢?可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional.
例如:
- 方法A,使用了@Async/@Transactional来标注,但是无法产生事务控制的目的。
- 方法B,使用了@Async来标注, B中调用了C、D,C/D分别使用@Transactional做了标注,则可实现事务控制的目的。
参考代码
IitDeuctionClaimAsyncServiceImpl
/**
* 一定要批量读取结果, 否则不能达到异步的效果!!
* 异步方法和调用类不要在同一个类中
* 注解扫描时,要注意过滤,避免重复实例化,因为存在覆盖问题,@Async就失效了
* @param params
* @return
* @throws Exception
*/
public PageResult<DeductionClaimDetailHVo> queryValidateIitDeductionClaimHVoAsync(Map params) throws Exception {
PageResult<IitDeductionClaimH> claimHs = findIitDeductionClaimH(params, true);
List<Future<DeductionClaimDetailHVo>> futures = new ArrayList<>();
long start = System.currentTimeMillis();
for (IitDeductionClaimH claimH : claimHs.getList()) {
Future<DeductionClaimDetailHVo> future = iitDeductionClaimAsyncService.convertIitDeductionClaimHToDeductionClaimDetailHVoAsync(claimH);
futures.add(future);
}
List<DeductionClaimDetailHVo> hVos = new ArrayList<>();
for (Future future : futures) {
DeductionClaimDetailHVo vo = (DeductionClaimDetailHVo) future.get();
hVos.add(vo);
}
logger.info("queryValidateIitDeductionClaimHVoAsync合计消耗时间:"+String.format("任务执行成功,耗时{%s}毫秒", System.currentTimeMillis() - start));
PageResult<DeductionClaimDetailHVo> results = new PageResult<>();
results.setList(hVos);
results.setPageIndex(claimHs.getPageIndex());
results.setPageSize(claimHs.getPageSize());
results.setPageIndexString(claimHs.getPageIndexString());
results.setTotal(claimHs.getTotal());
return results;
}
private void buildDetail_lvo(IitDeductionClaimH iitDeductionClaimH, DeductionClaimDetailHVo result,
boolean withAttachment) throws Exception {
long prestart = System.currentTimeMillis();
..............................................................................
logger.info("buildDetail_lvo 前序任务消耗时间:"+String.format("任务执行成功,耗时{%s}毫秒", System.currentTimeMillis() - prestart));
List<Future<DeductionClaimDetailHVo>> futures = new ArrayList<>();
long endstart = System.currentTimeMillis();
Future<DeductionClaimDetailHVo> futureMonthAmount = iitDeductionClaimAsyncService.processMonthAmount(result);
futures.add(futureMonthAmount);
Future<DeductionClaimDetailHVo> futureYearSum = iitDeductionClaimAsyncService.processYearSum(result);
futures.add(futureYearSum);
Future<DeductionClaimDetailHVo> futurePersionInfo = iitDeductionClaimAsyncService.processPersionInfo(iitDeductionClaimH,result);
futures.add(futurePersionInfo);
for (Future future : futures) {
future.get();
}
logger.info("buildDetail_lvo 后续任务消耗时间:"+String.format("任务执行成功,耗时{%s}毫秒", System.currentTimeMillis() - endstart));
}
private Future<DxlUserVO> buildDxlUserVO(RemoteUserService remoteUserService, SysUserVO userVO) {
.....
return new AsyncResult<>(dxlUserVO);
}
线程池设置
@Bean("buildCusTaskExecutor") public Executor taskExecutor() { ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(50); //最大个数 executor.setThreadNamePrefix("buildCus-"); //线程池名称 //该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁 executor.setWaitForTasksToCompleteOnShutdown(true); //该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。 executor.setAwaitTerminationSeconds(3); return executor; }
@Async("buildCusTaskExecutor")
@EnableAsync
让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync