为什么添加 Spring AOP 方面会在 Spring 上下文初始化期间中断此异步调用?
Why does adding Spring AOP aspects break this asynchronous call during Spring context initialization?
我很难理解手头的问题,我认为这是 Spring 代理创建方式的问题。
在这个最小的示例中,我有两个 类、AccountLoader
和 BankImpl
,它们实现了一个接口 Bank
。启动时,AccountLoader
对自动装配的 Bank
实例执行一些并发调用,其中 BankImpl
中的方法被建议使用一个方面。
在此设置中,完成未来 (Future.get
) 的调用以 TimeoutException
结束,因为调用似乎永远不会终止。但是,如果我在可调用对象提交给执行程序之前调用相同的方法,所有调用都会成功完成。
这里的Spring是怎么回事?为什么这个异步调用没有终止?如果我在异步调用之前添加一个同步调用,为什么在所有七个地狱中它都会终止?
您可能会找到下面的代码,一个完整的工作示例也是 available on Github
public interface Bank {
Map<String, String> getAccounts(String q);
}
简单的实现
@Service
public class BankImpl implements Bank {
private static final Logger LOGGER = LoggerFactory.getLogger(BankImpl.class);
@Override
public Map<String, String> getAccounts(String q) {
LOGGER.info("Listing accounts for {}", q);
return Collections.singletonMap(q, "q");
}
}
最后是来电者
@Service
public class AccountLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountLoader.class);
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
@Autowired
private Bank bank;
@PostConstruct
public void refresh() {
LOGGER.info("Refreshing accounts");
// Uncommenting the following line will let the calls terminate
// bank.getAccounts("sync");
try {
executorService.submit(() -> { bank.getAccounts("async"); })
.get(5L, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
为了完整起见,这里是看点
@Aspect
@Component
public class SomeAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(SomeAspect.class);
@AfterReturning(pointcut = "execution(* com.github.mtritschler.aspects.BankImpl.getAccounts(..))", returning = "returnValue")
public Map<String, String> logCallee(Map<String, String> returnValue) {
LOGGER.info("Result is {}", returnValue);
return returnValue;
}
}
最后但并非最不重要的配置
@EnableAspectJAutoProxy
@Configuration
public class MyConfig {
}
更新:如果我删除 @EnableAspextJAutoProxy
,我也不会得到异常。切换到加载时编织也没有改变任何东西。
原来是主线程中的应用程序初始化和并发访问注入的依赖之间存在竞争条件。
一旦我们为 ContextRefreshedEvent
上的侦听器切换 @PostConstruct
,它工作得很好。
我很难理解手头的问题,我认为这是 Spring 代理创建方式的问题。
在这个最小的示例中,我有两个 类、AccountLoader
和 BankImpl
,它们实现了一个接口 Bank
。启动时,AccountLoader
对自动装配的 Bank
实例执行一些并发调用,其中 BankImpl
中的方法被建议使用一个方面。
在此设置中,完成未来 (Future.get
) 的调用以 TimeoutException
结束,因为调用似乎永远不会终止。但是,如果我在可调用对象提交给执行程序之前调用相同的方法,所有调用都会成功完成。
这里的Spring是怎么回事?为什么这个异步调用没有终止?如果我在异步调用之前添加一个同步调用,为什么在所有七个地狱中它都会终止?
您可能会找到下面的代码,一个完整的工作示例也是 available on Github
public interface Bank {
Map<String, String> getAccounts(String q);
}
简单的实现
@Service
public class BankImpl implements Bank {
private static final Logger LOGGER = LoggerFactory.getLogger(BankImpl.class);
@Override
public Map<String, String> getAccounts(String q) {
LOGGER.info("Listing accounts for {}", q);
return Collections.singletonMap(q, "q");
}
}
最后是来电者
@Service
public class AccountLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountLoader.class);
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
@Autowired
private Bank bank;
@PostConstruct
public void refresh() {
LOGGER.info("Refreshing accounts");
// Uncommenting the following line will let the calls terminate
// bank.getAccounts("sync");
try {
executorService.submit(() -> { bank.getAccounts("async"); })
.get(5L, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
为了完整起见,这里是看点
@Aspect
@Component
public class SomeAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(SomeAspect.class);
@AfterReturning(pointcut = "execution(* com.github.mtritschler.aspects.BankImpl.getAccounts(..))", returning = "returnValue")
public Map<String, String> logCallee(Map<String, String> returnValue) {
LOGGER.info("Result is {}", returnValue);
return returnValue;
}
}
最后但并非最不重要的配置
@EnableAspectJAutoProxy
@Configuration
public class MyConfig {
}
更新:如果我删除 @EnableAspextJAutoProxy
,我也不会得到异常。切换到加载时编织也没有改变任何东西。
原来是主线程中的应用程序初始化和并发访问注入的依赖之间存在竞争条件。
一旦我们为 ContextRefreshedEvent
上的侦听器切换 @PostConstruct
,它工作得很好。