spring的异步调用

简介: 异步调用除了可以使用多线程以外,spring自已也实现了通过注解进行异步调用的功能,我们只需要进行一些简单的配置,并且在需要异步调用的方法上添加对应的注解即可。 在applicationContext.xml中添加如下: 调用test:@Servicepublic class AsyncTestServiceImpl implements AsyncTestService{ @Override public void testAsync() { System.out.println("准备调用异步方法。

异步调用除了可以使用多线程以外,spring自已也实现了通过注解进行异步调用的功能,我们只需要进行一些简单的配置,并且在需要异步调用的方法上添加对应的注解即可。
在applicationContext.xml中添加如下:

<task:annotation-driven executor="defaultTaskExecutor" scheduler="defaultTaskScheduler" />  
<task:executor id="defaultTaskExecutor" pool-size="5" queue-capacity="100"/>  
<task:scheduler id="defaultTaskScheduler" pool-size="1" /> 

调用test:

@Service
public class AsyncTestServiceImpl implements AsyncTestService{

    @Override
    public void testAsync() {
        System.out.println("准备调用异步方法。。。。。");
        asyncMethod();
        System.out.println("调用异步方法结束。。。。。");
    }

    @Override
    @Async
    public void asyncMethod(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("正在调用异步方法。。。");
    }

}

调用结果如下:

准备调用异步方法。。。。。
正在调用异步方法。。。
调用异步方法结束。。。。。

我们发现结果和我们预想的不一样,原因是如果调用方和被调用方是在同一个类中,是无法产生切面,除非我们指定调用的时候要调用其代理对象,或者将被调用方放入spring管理的其他类中。
想要指定调用代理对象,需要添加如下配置:

<aop:aspectj-autoproxy expose-proxy="true"/>

这个配置的作用就是告诉spring,在获取代理对象的时候,将代理对象放入到AopContext这个类中的currentProxy属性中,那么我们只需要通过AopContext这个类就可以获取到自身的代理对象,我们将调用代码稍作修改:

@Override
public void testAsync() {
    System.out.println("准备调用异步方法。。。。。");
    ((AsyncTestService)AopContext.currentProxy()).asyncMethod();
    System.out.println("调用异步方法结束。。。。。");
}

再次调用,我们发现竟然抛出了异常(有些人可能会成功):

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

查看源码我们发现,当我们currentProxy()的时候,发现获取到的代理对象为null,所以抛出这样的异常,可是我们已经添加了配置,为什么还会出现这种情况呢?
我们从spring的解析xml文件的标签的代码开始跟踪,标签的解析在XmlBeanDefinitionReader类中:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    documentReader.setEnvironment(this.getEnvironment());
    int countBefore = getRegistry().getBeanDefinitionCount();
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

我们发现在这里spring已经将xml文件抽象成document对象了,并且开始调用registerBeanDefinitions方法了,一直跟进去,我们会看到:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

这个方法就是对xml文件中标签的处理,delegate.parseCustomElement(ele)方法是对自定义标签的解析,继续跟进去,直到AopNamespaceUtils的useClassProxyingIfNecessary方法:

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {
    if (sourceElement != null) {
        boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
        if (proxyTargetClass) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
        if (exposeProxy) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}

这个方法是对我们刚刚那个配置属性的解析,debug时我们发现,exposeProxy确实是true,也就是说我们的配置是没有问题的,那么问题出在哪里呢?根据报错信息,我们知道是由于AopContext.currentProxy()出现问题,那么,我们就通过它的set方法反向跟踪,最后跟到ProxyCreatorSupport的createAopProxy方法:

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    return getAopProxyFactory().createAopProxy(this);
}

这里是获取每个被ioc的对象的代理对象,也就是说setCurrentProxy是在创建代理对象的时候执行的,而要产生代理对象就必须要产生一个切面,我们发现上面的例子中只是简单的ioc,而没有aop,所有我们会发现this对象中的exposeProxy属性还是false,所以出现了这种结果。

找到了原因,解决就简单了,我们只需要将这个类定义成是一个切面就可以了,最简单的方法就是在被调用方法上添加一个事务的注解:

@Override
@Async
@Transactional
public void asyncMethod(){
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("正在调用异步方法。。。");
}

再次调用,我们发现一切正常。异步调用也生效了。
其实还有一种更为简单的解决方案,就是将被调用方法放到另一个类中即可。

目录
相关文章
|
4月前
|
Java 测试技术 Spring
Spring Boot使用@Async实现异步调用
Spring Boot使用@Async实现异步调用
25 0
|
5月前
|
Java Spring
使用spring boot的@Async实现异步调用和线程池复用
使用spring boot的@Async实现异步调用和线程池复用
|
8月前
|
监控 Java 调度
基于 Spring 事务的可靠异步调用实践
pringTxAsync 组件是仓储平台组(WMS6)自主研发的一个专门用于解决可靠异步调用问题的组件。 通过使用 SpringTxAsync 组件,我们成功地解决了在仓储平台(WMS6)中的异步调用需求。经过近二年多的实践并经历了两次 618 活动以及两次双 11 活动,该组件已经在我们的所有应用中稳定运行并成功应用于各种业务场景。 该组件的主要功能是实现可靠的异步调用。在异步任务的执行过程中,我们能
87 0
|
Java Spring
使用spring boot的@Async实现异步调用和线程池复用
使用spring boot的@Async实现异步调用和线程池复用
731 0
使用spring boot的@Async实现异步调用和线程池复用
|
Java 测试技术 Spring
Spring boot如何实现异步调用
Spring boot如何实现异步调用
|
Java 测试技术 Spring
Spring Boot使用@Async实现异步调用
异步调用对应的是同步调用,同步调用可以理解为按照定义的顺序依次执行,有序性;异步调用在执行的时候不需要等待上一个指令调用结束就可以继续执行。
176 0
|
监控 Java 测试技术
听说可以十分钟掌握Spring Boot 集成定时任务、异步调用?
在项目开发中,经常需要定时任务来帮助我们来做一些内容,比如定时发送短信/站内信息、数据汇总统计、业务监控等,所以就要用到我们的定时任务,在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务
121 0
听说可以十分钟掌握Spring Boot 集成定时任务、异步调用?
|
消息中间件 监控 Java
Spring Boot 异步请求和异步调用,一文搞定!
一、Spring Boot中异步请求的使用 1、异步请求与同步请求
Spring Boot 异步请求和异步调用,一文搞定!
|
消息中间件 监控 Java
Spring Boot 异步请求和异步调用,一文搞定!
可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。 一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)。
189 0
Spring Boot 异步请求和异步调用,一文搞定!
|
Java 测试技术 Spring
Spring Boot中使用@Async实现异步调用
Spring Boot中使用@Async实现异步调用
123 0