引言有点长

前端的宝宝会用ajax,用异步编程到快乐的不行~ 我们java也有异步,用起来比他们还快乐~ 我们biaji一个注(gǒupí)解(gāoyào),也是快乐风男...

且看下面的栗子:

注册一个用户,给他的账户初始化积分(也可以想象成注册奖励),再给用户发个注册通知短信,再发个邮件,(只是举栗子,切莫牛角大法),这样一个流程,短信和邮件我觉得完全可以拆分出来,没必要拖在在主流程上来(再补充上事务[ACID:原子性,一致性,隔离性,持久性]就好了):

今天就这点业务,我在暗想,这不是一个注解就搞掂的嘛~~~ 哈哈哈

结果不是想象中的那么完美~~ 来看我的异(dind)步(ding)历险记

我的首发原创博客地址:你的@Async就真的异步吗 异步历险奇遇记[1] https://juejin.im/post/5d47a80a6fb9a06ad3470f9a 里面有gitHub项目地址,关注我,里面实战多多哦~

奇遇一 循环依赖异常

看code:

@Servicepublic class UserServiceImpl implements UserService { @Autowired UserService userService; @Override @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public int save(UserDTO userDTO) { User user = new User(); BeanCopyUtils.copy(userDTO, user); int insert = userMapper.insert(user); System.out.println("User 保存用户成功:" + user); userService.senMsg(user); userService.senEmail(user); return insert; } @Async @Override public Boolean senMsg(User user) { try { TimeUnit.SECONDS.sleep(2); System.out.println("发送短信中:....."); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",手机号:" + user.getMobile() + "发送短信成功"); return true; } @Async @Override public Boolean senEmail(User user) { try { TimeUnit.SECONDS.sleep(3); System.out.println("发送邮件中:....."); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",邮箱:" + user.getEmail() + "发送邮件成功"); return true; }

结果:启动不起来,Spring循环依赖问题。 Spring不是解决了循环依赖问题吗,它是支持循环依赖的呀?怎么会呢?

不可否认,在这之前我也是这么坚信的,倘若你目前也和我有一样坚挺的想法,那就让异常UnsatisfiedDependencyException,has been injected into other beans [userServiceImpl] in its raw version as part of a circular reference,,来鼓励你,拥抱你, 就是这么的不给面子,赤裸裸的circular reference。

谈到Spring Bean的循环依赖,有的小伙伴可能比较陌生,毕竟开发过程中好像对循环依赖这个概念无感知。其实不然,你有这种错觉,那是因为你工作在Spring的襁褓中,从而让你“高枕无忧”~ 其实我们的代码中肯定被我们写了循环依赖,比如像这样:

@Servicepublic class AServiceImpl implements AService { @Autowired private BService bService; ...}@Servicepublic class BServiceImpl implements BService { @Autowired private AService aService; ...}

通过实验总结出,出现使用@Async导致循环依赖问题的必要条件:

已开启@EnableAsync的支持

@Async注解所在的Bean被循环依赖了

奇遇二 异步失效异常

那么既然不能循环依赖,我们就不循环依赖,我们这么来:

@Servicepublic class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Autowired SendService sendService; @Override @Transactional() public int save(UserDTO userDTO) { User user = new User(); BeanCopyUtils.copy(userDTO, user); int insert = userMapper.insert(user); System.out.println("User 保存用户成功:" + user); this.senMsg(user); this.senEmail(user); return insert; } @Async @Override public Boolean senMsg(User user) { System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",手机号:" + user.getMobile() + "发送短信成功"); return true; } @Async @Override public Boolean senEmail(User user) { System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",邮箱:" + user.getEmail() + "发送邮件成功"); return true; }

结果我们测试了几把,我打印一下结果:

2019-08-05 21:59:32.304 INFO 14360 --- [nio-8080-exec-3] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited2019-08-05 21:59:32.346 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : ==> Preparing: insert into t_user (username, sex, mobile,email) values (?, ?, ?,?) 2019-08-05 21:59:32.454 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : ==> Parameters: 王麻子(String), 男(String), 18820158833(String), 22qq@qq.com(String)2019-08-05 21:59:32.463 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : <== Updates: 1User 保存用户成功:User(id=101, username=王麻子, mobile=18820158833, email=22qq@qq.com, sex=男, password=123435, createTime=Mon Aug 05 12:20:51 CST 2019, updateTime=null)发送短信中:.....http-nio-8080-exec-3给用户id:101,手机号:18820158833发送短信成功发送邮件中:.....http-nio-8080-exec-3给用户id:101,邮箱:22qq@qq.com发送邮件成功

这不白瞎了吗?感知不到我的爱,白写了,难受~~线程依然是http-nio-8080-exec-3,那么为什么了呢? 下面会讲的哦,先说结论:

通过实验总结出,出现使用@Async导致异步失效的原因:

在本类中使用了异步是不支持异步的

调用者其实是this,是当前对象,不是真正的代理对象userService,spring无法截获这个方法调用 所以不在不在本类中去调用,网上的解决方法有applicationContext.getBean(UserService.class)和AopContext.currentProxy()

奇遇三 事务失效异常

那么,既然不能用当前对象,那我们用代理,AopContext.currentProxy(),然鹅,你发现,报错了,对Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.就他:

但是你去网上百度就会发现,都这么搞

@EnableAspectJAutoProxy(exposeProxy = true)

我也这么搞,但是又报错了,细细的看报错内容,才发现少了个jar包这东西要配合切面织入,要配合,懂吗?,来上包

org.aspectjaspectjweaver1.9.2

再来看为撒: 这是一个性感的话题,exposeProxy = true它的作用就是启用切面的自动代理,说人话就是暴露当前代理对象到当前线程绑定, 看个报错Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.就是AopContext搞得鬼.

public final class AopContext {

private static final ThreadLocal currentProxy = new NamedThreadLocal<>("Current AOP proxy");

private AopContext() {

}

// 该方法是public static方法,说明可以被任意类进行调用

public static Object currentProxy() throws IllegalStateException {

Object proxy = currentProxy.get();

// 它抛出异常的原因是当前线程并没有绑定对象

// 而给线程绑定对象的方法在下面:特别有意思的是它的访问权限是default级别,也就是说只能Spring内部去调用

if (proxy == null) {

throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");

}

return proxy;

}

// 它最有意思的地方是它的访问权限是default的,表示只能给Spring内部去调用

// 调用它的类有CglibAopProxy和JdkDynamicAopProxy

@Nullable

static Object setCurrentProxy(@Nullable Object proxy) {

Object old = currentProxy.get();

if (proxy != null) {

currentProxy.set(proxy);

} else {

currentProxy.remove();

}

return old;

}

}

所以我们要做启用代理设置,让代理生效,来走起,主线程的方法使用来调用异步方法,来测试走起: no code said niao:

@Servicepublic class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Override@Transactional(propagation = Propagation.REQUIRED) public int save(UserDTO userDTO) { User user = new User(); BeanCopyUtils.copy(userDTO, user); int insert = userMapper.insert(user); System.out.println("User 保存用户成功:" + user); UserService currentProxy = UserService.class.cast(AopContext.currentProxy()); currentProxy.senMsg(user); currentProxy.senEmail(user); int i = 1 / 0; return insert; } @Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void senMsg(User user) { user.setUsername(Thread.currentThread().getName()+"发短信测试事务...."+ new Random().nextInt()); userMapper.insert(user); System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",手机号:" + user.getMobile() + "发送短信成功"); } @Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void senEmail(User user) { user.setUsername("发邮件测试事务...."+ new Random().nextInt()); userMapper.insert(user); int i = 1 / 0; System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",邮箱:" + user.getEmail() + "发送邮件成功"); }}

测试结果:

如我们所愿,事务和异步都生效了

1. 异步线程SimpleAsyncTaskExecutor-1和SimpleAsyncTaskExecutor-2分别发短信和邮件,主线程保存用户2. 实际结果,主线程保存的那个用户失败,名字叫'发送短信'的也保存失败,只有叫'发邮件'的用户插入成功3. 那么就做到了事务的线程隔离,事务的互不影响,完美4. 亲,你这么写了吗?这么写不优美,虽然有用,但是写的另辟蹊径啊

奇遇四 异步嵌套异常

来,我们看个更骚气的,异步中嵌套异步,来上code:

@Servicepublic class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Override@Transactional(propagation = Propagation.REQUIRED) public int save(UserDTO userDTO) { User user = new User(); BeanCopyUtils.copy(userDTO, user); int insert = userMapper.insert(user); System.out.println("User 保存用户成功:" + user); UserService currentProxy = UserService.class.cast(AopContext.currentProxy()); currentProxy.send(user); return insert; } @Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void send(User user) { //发短信 user.setUsername(Thread.currentThread().getName()+"发短信测试事务...."+ new Random().nextInt()); userMapper.insert(user); System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",手机号:" + user.getMobile() + "发送短信成功"); //发邮件 UserService currentProxy = UserService.class.cast(AopContext.currentProxy()); currentProxy.senEmail(user); } @Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void senEmail(User user) { user.setUsername("发邮件测试事务...."+ new Random().nextInt()); userMapper.insert(user); System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",邮箱:" + user.getEmail() + "发送邮件成功"); }}

看我们猜下结果? 数据库会新增几个数据?3个?2个?1个?0个?纳尼报错?

哈哈``` 上结果:

答案:只有一条数据主线程保存成功,短信和邮件的插入都失败了,最主要的是还报错了,又是那个~~~Set 'exposeProxy' property on Advised to 'true'磨人的小妖精

通过实验总结出,出现导致异步嵌套使用失败的原因:

在本类中使用了异步嵌套异步是不支持的

调用者其实被代理过一次了,再嵌套会出现'二次代理',其实是达不到代理了效果,因为已经异步了.即时你在嵌套中不使用代理去获取,即使不保存,但是事务和异步效果都会跟随当前的代理,即嵌套的效果是达不到再次异步的.

解决办法应该有,但是我觉得我还没找到.这个写法是我们应该规避的,我们应该遵循规范,启用新的服务类去完成我们的异步工作

下面我们举个栗子:正确的写法,优雅的写法

@Servicepublic class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Autowired SendService sendService; @Override@Transactional(propagation = Propagation.REQUIRED) public int save(UserDTO userDTO) { User user = new User(); BeanCopyUtils.copy(userDTO, user); int insert = userMapper.insert(user); System.out.println("User 保存用户成功:" + user); sendService.senMsg(user); sendService.senEmail(user); return insert; }}---------------无责任分割线--------------------@Servicepublic class SendServiceImpl implements SendService { @Override @Async @Transactional(propagation = Propagation.REQUIRES_NEW) public Boolean senMsg(User user) { try { TimeUnit.SECONDS.sleep(2); System.out.println("发送短信中:....."); } catch (InterruptedException e) { e.printStackTrace(); } int i = 1 / 0; System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",手机号:" + user.getMobile() + "发送短信成功"); return true; } @Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public Boolean senEmail(User user) { try { TimeUnit.SECONDS.sleep(3); System.out.println("发送邮件中:....."); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",邮箱:" + user.getEmail() + "发送邮件成功"); return true; }}

结果肯定完美:

因此当你看到你同事就在本类写个方法标注上@Async然后调用,请制止他吧,做的无用功~~~(关键自己还以为有用,这是最可怕的深坑~)

那我补充点:@EnableAspectJAutoProxy(exposeProxy = true)的作用: 此注解它导入了AspectJAutoProxyRegistrar,最终设置此注解的两个属性的方法为:

public abstract class AopConfigUtils { ...正在加(sheng)载(lue)代码中 请稍后....public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);}}public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);}}}

看到此注解标注的属性值最终都被设置到了internalAutoProxyCreator身上,也就是它:自动代理创建器。 首先我们需要明晰的是:@Async的代理对象并不是由自动代理创建器来创建的,而是由AsyncAnnotationBeanPostProcessor一个单纯的BeanPostProcessor实现的,很显然当执行AopContext.currentProxy()这句代码的时候报错了。@EnableAsync给容器注入的是AsyncAnnotationBeanPostProcessor,它用于给@Async生成代理,但是它仅仅是个BeanPostProcessor并不属于自动代理创建器,因此exposeProxy = true对它无效。 所以AopContext.setCurrentProxy(proxy);这个set方法肯定就不会执行,所以,因此,但凡只要业务方法中调用AopContext.currentProxy()方法就铁定抛异常~~

奇遇五 基本类型异常

看嘛,发短信其实是一些网关调用,我想写个看短信,邮件发送成功的标志,是否调用成功的状态,来走起

....省略...UserService---------------无责任分割线--------------------@Servicepublic class SendServiceImpl implements SendService { @Override @Async @Transactional(propagation = Propagation.REQUIRES_NEW) public boolean senMsg(User user) { try { TimeUnit.SECONDS.sleep(2); System.out.println("发送短信中:....."); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",手机号:" + user.getMobile() + "发送短信成功"); return true; } @Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public boolean senEmail(User user) { try { TimeUnit.SECONDS.sleep(3); System.out.println("发送邮件中:....."); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "给用户id:" + user.getId() + ",邮箱:" + user.getEmail() + "发送邮件成功"); return true; }}

瞪大眼睛看,我的返回结果是boolean,属于基本类型,虽然没有用,但是报错了:

org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public boolean com.boot.lea.mybot.service.impl.SendServiceImpl.senMsg(com.boot.lea.mybot.entity.User)

导致我的数据库一条数据都没有,影响到主线程了,可见问题发生在主线程触发异步线程的时候,那我们找原因: 是走代理触发的:我先找这个类CglibAopProxy再顺藤摸瓜

/** * Process a return value. Wraps a return of {@code this} if necessary to be the * {@code proxy} and also verifies that {@code null} is not returned as a primitive. */private static Object processReturnType(Object proxy, Object target, Method method, Object retVal) {// Massage return value if necessaryif (retVal != null && retVal == target &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {// Special case: it returned "this". Note that we can't help// if the target sets a reference to itself in another returned object.retVal = proxy;}Class> returnType = method.getReturnType();if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);}return retVal;}

在这retVal == null && returnType != Void.TYPE && returnType.isPrimitive(),因为我们的这种异步其实是不支持友好的返回结果的,我们的结果应该是void,因为这个异步线程被主线程触发后其实被当做一个任务提交到Spring的异步的一个线程池中进行异步的处理任务了,线程之间的通信是不能之间返回的,其实用这种写法我们就应该用void去异步执行,不要有返回值,而且我们的返回值是isPrimitive(),是基本类型,刚好达标....

那么我大声喊出,使用异步的时候尽量不要有返回值,实在要有你也不能用基本类型.

奇遇六 返回异步结果

有些人就是难受,就是想要返回结果,那么也是可以滴:但是要借助Furtrue小姐姐的get()来进行线程之间的阻塞通信,毕竟小姐姐( ω )害羞.

酱紫写,你就可以阻塞等到执行任务有结果的时候去获取真正的结果了,这个写法和我之前的文章JAVA并发异步编程 原来十个接口的活现在只需要一个接口就搞定![2]是一样的道理了

import com.boot.lea.mybot.service.AsyncService;import com.boot.lea.mybot.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.annotation.Async;import org.springframework.scheduling.annotation.AsyncResult;import org.springframework.stereotype.Service;@Service@Async("taskExecutor")public class AsyncServiceImpl implements AsyncService { @Autowired private UserService userService; @Override public Future queryUserMsgCount(final Long userId) { System.out.println("当前线程:" + Thread.currentThread().getName() + "=-=====queryUserMsgCount"); long countByUserId = userService.countMsgCountByUserId(userId); return new AsyncResult<>(countByUserId); } @Override public Future queryCollectCount(final Long userId) { System.out.println("当前线程:" + Thread.currentThread().getName() + "=-====queryCollectCount"); long collectCount = userService.countCollectCountByUserId(userId); return new AsyncResult<>(collectCount); }

你需要知道的九大异步注意事项

尽量不要在本类中异步调用

尽量不要有返回值

不能使用本类的私有方法或者非接口化加注@Async,因为代理不到失效

异步方法不能使用static修饰

异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类

类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象

如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解

在调用Async方法的方法上标注@Transactional是管理调用方法的事务的

在Async方法上标注@Transactional是管理异步方法的事务,事务因线程隔离

你需要懂的异步原理

@Async的异步:

实际是spring 在扫描bean的时候会扫描方法上是否包含@Async的注解,如果包含的,spring会为这个bean动态的生成一个子类,我们称之为代理类(jdkProxy), 代理类是继承我们所写的bean的,然后把代理类注入进来,那此时,在执行此方法的时候,会到代理类中,代理类判断了此方法需要异步执行,就不会调用父类 (我们原本写的bean)的对应方法。

spring自己维护了一个队列,他会把需要执行的方法,放入队列中,等待线程池去读取这个队列,完成方法的执行, 从而完成了异步的功能。

我们可以关注到再配置task的时候,是有参数让我们配置线程池的数量的。因为这种实现方法,所以在同一个类中的方法调用,添加@Async注解是失效的!,原因是当你在同一个类中的时候,方法调用是在类体内执行的,spring无法截获这个方法调用(为什么呢,这个就是下文讲的...奸笑...嘻嘻嘻嘻...)。

那在深入一步,Spring为我们提供了AOP,面向切面的功能。他的原理和异步注解的原理是类似的,spring在启动容器的时候,会扫描切面所定义的 类。在这些类被注入的时候,所注入的也是代理类,当你调用这些方法的时候,本质上是调用的代理类。通过代理类再去执行父类相对应的方法,那spring只需要在调用之前和之后执行某段代码就完成了AOP的实现了!

SpringBoot环境中,要使用@Async注解,我们需要先在启动类上加上@EnableAsync注解。这个与在SpringBoot中使用@Scheduled注解需要在启动类中加上@EnableScheduling是一样的道理(当然你使用古老的XML配置也是可以的,但是在SpringBoot环境中,建议的是全注解开发),具体原理下面会分析。加上@EnableAsync注解后,如果我们想在调用一个方法的时候开启一个新的线程开始异步操作,我们只需要在这个方法上加上@Async注解,当然前提是,这个方法所在的类必须在Spring环境中。

示例:非spingboot项目

执行流程:

扫描是否开启注解EnableAsync,@EnableAsync注解上有个@Import(AsyncConfigurationSelector.class),springboot的注入老套路了

请您再移步AsyncConfigurationSelector,看到selectImports方法了没,这里使用的是默认使用的是ProxyAsyncConfiguration这个配置类

继续观摩ProxyAsyncConfiguration继承AbstractAsyncConfiguration,它里面的的setConfigurers说明了我们可以通过实现AsyncConfigurer接口来完成线程池以及异常处理器的配置,而且在Spring环境中只能配置一个实现类,否则会抛出异常。 上一点代码:/** * Collect any {@link AsyncConfigurer} beans through autowiring. */@Autowired(required = false)void setConfigurers(Collection configurers) { if (CollectionUtils.isEmpty(configurers)) {return;} //AsyncConfigurer用来配置线程池配置以及异常处理器,而且在Spring环境中最多只能有一个,在这里我们知道了,如果想要自己去配置线程池,只需要实现AsyncConfigurer接口,并且不可以在Spring环境中有多个实现AsyncConfigurer的类。if (configurers.size() > 1) {throw new IllegalStateException("Only one AsyncConfigurer may exist");}AsyncConfigurer configurer = configurers.iterator().next();this.executor = configurer.getAsyncExecutor();this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler();} ProxyAsyncConfiguration注入的bean AsyncAnnotationBeanPostProcessor,这个BeanPostBeanPostProcessor很显然会对带有能够引发异步操作的注解(比如@Async)的Bean进行处理

我们注意到AsyncAnnotationBeanPostProcessor有重写父类的setBeanFactory,这个方法是不是有点熟悉呢,它是BeanFactoryAware接口中的方法,AsyncAnnotationBeanPostProcessor的父类实现了这个接口,在我们很久之前分析过的Bean的初始化中,是有提到过这个接口的,实现了Aware类型接口的Bean,会在初始化Bean的时候调用相应的初始化方法,具体可以查看AbstractAutowireCapableBeanFactory#initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd)方法

处理Bean的postProcessAfterInitialization方法在祖先类AbstractAdvisingBeanPostProcessor中。从源码中可以看到。AsyncAnnotationBeanPostProcessor是对Bean进行后置处理的BeanPostProcessor

最后代理到JdkDynamicAopProxy的invoke方法中,是用了责任链模式:List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);,将代理进行拦截来执行,通知链会包含setBeanFactory()方法生成的通知,执行链会用于创建ReflectiveMethodInvocation对象,最终是调用ReflectiveMethodInvocation的proceed()来完成对方法的增强处理,proceed()方法在这里会执行最后一个分支

具体执行的是AsyncExecutionInterceptor的invoke()

注意:虽然上文Spring环境中只能有一个AsyncConfigurer实现类,但是不意味着,在Spring环境中只能配置一个线程池,在Spring环境中是可以配置多个线程池,而且我们可以在使用@Async注解进行异步操作的时候,通过在value属性上指定线程池BeanName,这样就可以指定相应的线程池来作为任务的载体,参见:determineAsyncExecutor

小结兄弟:

当我们想要在SpringBoot中方便的使用@Async注解开启异步操作的时候,只需要实现AsyncConfigurer接口(这样就配置了默认线程池配置,当然该类需要在Spring环境中,因为是默认的,所以只能有一个,没有多个实现类排优先级的说法),实现对线程池的配置,并在启动类上加上@EnableAsync注解,即可使得@Async注解生效。

我们甚至可以不显式的实现AsyncConfigurer,我们可以在Spring环境中配置多个Executor类型的Bean,在使用@Async注解时,将注解的value指定为你Executor类型的BeanName,就可以使用指定的线程池来作为任务的载体,这样就使用线程池也更加灵活。

参考资料

[1]

你的@Async就真的异步吗 异步历险奇遇记: https://juejin.im/post/5d47a80a6fb9a06ad3470f9a

[2]

JAVA并发异步编程 原来十个接口的活现在只需要一个接口就搞定!: https://juejin.im/post/5d3c46d2f265da1b9163dbce

springboot异步和切面_Spring异步编程 你的@Async就真的异步吗?异步历险奇遇记相关推荐

  1. springboot异步和切面_Spring异步编程 | 你的@Async就真的异步吗 ☞ 异步历险奇遇记...

    引言有点长 前端的宝宝会用ajax,用异步编程到快乐的不行~ 我们java也有异步,用起来比他们还快乐~ 我们bia~ji~一个注(gǒupí)解(gāoyào),也是快乐风男... 且看下面的栗子: ...

  2. springboot异步和切面_Spring异步编程 | 你的@Async就真的异步吗?异步历险奇遇记

    Spring异步编程 | 你的@Async就真的异步吗?异步历险奇遇记 点击上方"java进阶架构师",选择右上角"置顶公众号" 20大进阶架构专题每日送达 引 ...

  3. exception e 是泛类吗_Spring异步编程 | 你的@Async就真的异步吗?异步历险奇遇记

    引言有点长 前端的宝宝会用ajax,用异步编程到快乐的不行~ 我们java也有异步,用起来比他们还快乐~ 我们biaji一个注(gǒupí)解(gāoyào),也是快乐风男... 且看下面的栗子: 注 ...

  4. springboot异步和切面_spring中的切面和异步执行

    1.首先理解异步和多线程的概念,怎么实现接口的异步调用呢?多线程,这是很多人第一眼想到的关键词,没错,多线程就是一种实现异步调用的方式! 2.下面介绍怎么实现异步调用方式 3.首先如果你的项目是spr ...

  5. springboot异步和切面_SpringBoot强化篇(八)-- Spring AOP

    Spring AOP简介 AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善.它以通过预编译方式和运 ...

  6. springboot服务调用超时_Spring Boot 异步请求和异步调用,一文搞定

    一.Spring Boot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如 ...

  7. 【转】异步编程:.NET 4.5 基于任务的异步编程模型(TAP)

    最近我为大家陆续介绍了"IAsyncResult异步编程模型 (APM)"和"基于事件的异步编程模式(EAP)"两种异步编程模型.在.NET4.0 中Micro ...

  8. 【转】1.8异步编程:.NET 4.5 基于任务的异步编程模型(TAP)

    传送门:异步编程系列目录-- 最近我为大家陆续介绍了"IAsyncResult异步编程模型 (APM)"和"基于事件的异步编程模式(EAP)"两种异步编程模型. ...

  9. java 8实战 异步社区_服!看完阿里大牛手写的Java异步编程实战笔记,我惊呆了...

    这份笔记涵盖了Java中常见的异步编程场景,包括单JVM内的异步编程.跨主机通过网络通信的远程过程调用的异步调用与异步处理,以及Web请求的异步处理等. 在讲解Java中每种异步编程技术时都附有案例, ...

最新文章

  1. android-async-http使用例子
  2. Leetcode 207. 课程表 解题思路及C++实现
  3. Ubuntu下apt-get方式Git的安装、配置和更新
  4. 图解 Java 常用数据结构
  5. python 协程_Python多任务协程
  6. 格局打开,带你解锁 prompt 的花式用法
  7. PNG免扣+高清背景素材,帮电商美工\设计师快速出稿!
  8. java 同步块关键字_Java同步关键字,同步方法和块
  9. 关于char, wchar_t, TCHAR, _T(),L,宏 _T、TEXT,_TEXT
  10. Nginx 下载 与 Windows 下访问集群 Tomat
  11. 极化SAR图像特征提取与分类方法研究
  12. Android 微信高性能日志存储库Xlog的使用
  13. 记住,在看小电影前一定要检查下域名是不是 HTTPS 的
  14. AI解梦成为现实 未来还有无限可能道翰天琼认知智能机器人平台API接口大脑为您揭秘
  15. 真正的软件测试实习2
  16. IM界面高仿微信,android表情转ios表情,支持自定义表情,支持语音(实战界面)
  17. 李子柒重回大众视野,拿回商标
  18. E1,CE1,T1,PRI,BRI的区别以及接口
  19. python字符宽度_使用vars或\uyu dict的Python固定宽度字符串格式__
  20. ANSI编码和UTF-8的区别

热门文章

  1. web前端面试题完美整理/涵盖html,CSS、JS、浏览器、Vue、React、移动web。
  2. 嵌入式面试准备一---USART、IIC、SPI、CAN
  3. cluster by、group by操作
  4. Git查看本机 ssh 公钥或生成公钥
  5. GAMES101现代计算机图形学入门——几何表示之曲线与曲面
  6. 代码情诗——一份真情请查收
  7. 引流复盘:从知乎引流20万粉,我只用了1个月
  8. Matplotlib和Seaborn(离散数据的图表选择与一些使用技巧)
  9. linux 如何做共享磁盘阵列,在Linux上玩转磁盘阵列分享
  10. centos(11)-ps进程管理