异步调用除了可以使用多线程以外,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("正在调用异步方法。。。");
}
再次调用,我们发现一切正常。异步调用也生效了。
其实还有一种更为简单的解决方案,就是将被调用方法放到另一个类中即可。