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

2019-09-25 11:23:38 浏览数 (1)

好了,距离上次发表《 不仅会用@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
代码语言:javascript复制
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;
    }
}
  • 定义切面编程
代码语言:javascript复制
@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思维导图,给我点个在看或者转发一下,万分感谢哈!

0 人点赞