好了,距离上次发表《 不仅会用@Async,我把源码也梳理了一遍(上)》已经好几天了,上一篇文章中我们介绍了@EnableAsync @Async的简单用法,今天我们来分析一下它的底层原理。
不过在分析源码之前,我们先来做下猜想,也就是利用我们现有知识去猜想一下它的原理。
猜想
- @EnableAsync
- @Async
@EnableAsync是开启异步的功能,应该需要完成某个配置的初始化,初始化过程至少应该包括扫描所有的@Async的注解,既然是异步执行,我们看了输入日志,线程名称为task,所以可以理解为一个任务或者一个线程,创建线程的方式常用的有如下3种:
- 继承Thread类创建
- 通过Runnable接口创建线程类
- 使用Callable和Future创建线程
基于使用@Aysnc的方法可以返回值为Future,所以应该倾向于使用Callable创建线程的方式。
我们再回到扫描@Async的过程,@Async是个注解,一般要处理一个注解标识的方法,我们可以联想到spring aop,在注解方法前后添加一些业务。
所以总结一下我们的联想:@EnableAsync注解加载模块过程中,定义了一个切面,使用@Async作为切点,然后执行环绕通知过程中,新建了一个Callable线程,把我们的原本方法的执行放到了线程内部执行。
我的推理强不强?请叫我工藤新一!
预备知识
其实呀,上面我所说的已经很接近真相了,不过在说源码之前,我还是要给大家先来一次预备知识的预习。
Callable创建线程
说不定很多人都不懂这个,先讲讲吧~
代码语言:javascript复制class CallableDemo implements Callable<String>{
@Override
public String call() throws Exception {
//线程业务逻辑
return "关注公众号:java思维导图";
}
}
执行Callable,需要FutureTask 的支持,可以接受线程结果。所以执行线程是这样的:
代码语言:javascript复制CallableDemo demo = new CallableDemo();
FutureTask<String> future = new FutureTask<>(demo);
new Thread(future).start();
// 获取线程结果
String result = future.get();
上篇文章中,我们是不是也是使用了future.get()来获取结果呀?哈哈~
切面编程aop
说起spring aop,我们一般都是通过注解式定义一个aop,常用的的几个注解如下:
- @Aspect
- 标注增强处理类(切面类)
- @Pointcut
- 自定义切点位置
- @Around
- 定义增强,环绕通知处理
这aop注解我就不举例子了,其实你知道用编码实现怎么去定义一个切面么,因为注解式只是编码式实现的简便方式而已。我们来看一下,使用编码式如何去实现一个切面:
- 定义一个需要被切面处理方法 com.example.demo.TestMethod
public class TestMethod {
public void test() {
System.out.println("关注公众号:java思维导图!");
}
}
- 通知方法 com.example.demo.TestMethodInterceptor,相当于@Around
虽然长得像拦截器,确实一个继承了*extends *Advice的拦截器,所以这个拦截器其实也是个通知。
代码语言:javascript复制@Component
public class TestMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before...");
Object resObj = invocation.proceed();
System.out.println("after...");
return resObj;
}
}
- 定义切面编程
@Autowired
TestMethodInterceptor interceptor;
@Test
public void testAop() {
TestMethod delegate = new TestMethod();
// 准备通知
TestMethodInterceptor interceptor = new TestMethodInterceptor();
ProxyFactory factory = new ProxyFactory();
// 准备切点
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern("com.example.demo.TestMethod.*");
// 切面 = 切点 通知
Advisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);
// 给代理工厂一个切面
factory.addAdvisor(advisor);
// 需要被代理的对象
factory.setTarget(delegate);
TestMethod proxy = (TestMethod) factory.getProxy();
proxy.test();
}
从上面代码可以看到,我们需要执行TestMethod 的test方法,但是我们以这个方法为切点做了个切面代理,切面的通知处理方法中我们加入了自己的一些业务逻辑,是不是和我们常用的aop功能一致了。
怕有些人还是懵逼,再梳理一下,上面的代码中,我使用ProxyFactory定义了一个代理,需要代理的切点是com.example.demo.TestMethod.*,然后还要定义一个切面环绕处理方法,就是这个拦截器又是通知处理器的TestMethodInterceptor,处理的方法就是我们的invoke方法。所以你会发现,通常我们说拦截器拦截的都是url,这里我们拦截的是一个类的所有方法。这个其实就是aop的原理。
测试结果:
好了,讲了这么多预备知识,有了预备知识之后我们再去看源码会清晰很多,因为其实你都可以使用这两个预备知识自己去实现一个@Async了。
总结
一步一步来,从猜想到预备知识,再到源码分析,逐步验证我们的结果,既能学到源码,又能从源码中加深已有知识的综合运用。强大的框架不都是这样写出来的吗?
好啦,今天的文章先到这里了。
如果你喜欢我的文章,欢迎关注我的公众号:java思维导图,给我点个在看或者转发一下,万分感谢哈!