不仅会用@Async,我把源码也梳理了一遍(中)

  • 2019 年 10 月 7 日
  • 筆記

好了,距离上次发表《 不仅会用@Async,我把源码也梳理了一遍(上)》已经好几天了,上一篇文章中我们介绍了@EnableAsync+@Async的简单用法,今天我们来分析一下它的底层原理。

不过在分析源码之前,我们先来做下猜想,也就是利用我们现有知识去猜想一下它的原理。

猜想

  • @EnableAsync
  • @Async

@EnableAsync是开启异步的功能,应该需要完成某个配置的初始化,初始化过程至少应该包括扫描所有的@Async的注解,既然是异步执行,我们看了输入日志,线程名称为task,所以可以理解为一个任务或者一个线程,创建线程的方式常用的有如下3种:

  • 继承Thread类创建
  • 通过Runnable接口创建线程类
  • 使用Callable和Future创建线程

基于使用@Aysnc的方法可以返回值为Future,所以应该倾向于使用Callable创建线程的方式。

我们再回到扫描@Async的过程,@Async是个注解,一般要处理一个注解标识的方法,我们可以联想到spring aop,在注解方法前后添加一些业务。

所以总结一下我们的联想:@EnableAsync注解加载模块过程中,定义了一个切面,使用@Async作为切点,然后执行环绕通知过程中,新建了一个Callable线程,把我们的原本方法的执行放到了线程内部执行。

我的推理强不强?请叫我工藤新一!

预备知识

其实呀,上面我所说的已经很接近真相了,不过在说源码之前,我还是要给大家先来一次预备知识的预习。

Callable创建线程

说不定很多人都不懂这个,先讲讲吧~

class CallableDemo implements Callable<String>{        @Override      public String call() throws Exception {          //线程业务逻辑          return "关注公众号:java思维导图";      }  }

执行Callable,需要FutureTask 的支持,可以接受线程结果。所以执行线程是这样的:

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的拦截器,所以这个拦截器其实也是个通知。

@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思维导图,给我点个在看或者转发一下,万分感谢哈!