Я пытаюсь реализовать универсальный класс, который асинхронно выполняет вызываемый объект, но я не уверен в семантике.
@Component
public class MyCallerImpl implements MyCaller {
@Async
@Override
public <T> Future<T> runAsync(Callable<T> callable) throws Exception {
return new AsyncResult<T>(callable.call());
}
}
По сути, этот компонент выполняет произвольные действия из любого вызываемого объекта асинхронно, используя аннотацию @Async.
Я не уверен в Exception в предложении throws сигнатуры метода.
Юнит-тест:
@ContextConfiguration("classpath:test-config.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class RunnerTest{
@Resource(name="myCallerImpl")
private MyCaller myCaller;
@Test
public void testException(){
final Callable<String> callable = new Callable<String>(){
@Override
public String call() throws Exception{
throw new MyException("foobar");
}
};
try
{
final Future<String> future = myCaller.runAsync(callable); // this can throw Exception due to Callable.call()
future.get(); // this can throw InterruptedException and ExecutionException
}
catch (final InterruptedException ie)
{
// do someting
}
catch (final ExecutionException ee)
{
// we want to check the cause
final Throwable cause = ee.getCause();
assertTrue(cause instanceof MyException);
}
catch (final Exception e)
{
// Not sure what to do here.
// Must be caught as it is declared to
// be thrown from the MyCaller.runAsync() method
// but nothing will really ever get here
// since the method is @Async and any exception will be
// wrapped by an ExecutionException and thrown during Future.get()
fail("this is unexpected);
}
Мой вопрос: что делать с Exception, объявленным в предложении throws MyCallerImpl.runAsync()?
Единственная причина, по которой я это объявил, заключается в том, как я вызываю callable. Первоначально у меня было следующее в асинхронном методе:
FutureTask<T> futureTask = new FutureTask<T>(callable);
futureTask.run();
return futureTask;
Но когда исключение генерируется из вызываемого объекта в этом экземпляре, оно дважды оборачивается в ExecutionException, первый раз, когда вызывается FutureTask.run(), в конечном итоге FutureTask.Sync.innerRun() перехватывает исключение и вызывает innnerSetException() и второй раз, когда AsyncExecutionIntercepter получает результат из Future через Future.get(), который в конечном итоге снова проверяет, есть ли исключение, и выдает новое ExecutionException, обертывающее ExecutionException, пойманное в innerRun()
Я также попытался сделать следующее в методе:
FutureTask<T> futureTask = new FutureTask<T>(callable);
return futureTask;
Я полагал, что, поскольку AsyncExecutionInterceptor вызывает Future.get(), вызываемый объект будет вызываться немедленно, но это было не так. Он просто зависает на FutureTask.acquireSharedInterruptably() и никогда не возвращается.
Может быть, я здесь не в своей тарелке. Он работает так, как я настроил его с вызываемым сейчас, но я предпочитаю, чтобы сигнатура метода не объявляла исключение throws.
Любой совет? Должен ли я забыть об этом общем способе выполнения асинхронных вызовов с вызываемым объектом?