I had a task to make my current webservice calls asynchronous and wanted to use the @Async annotation in Spring 3.0x to accomplish the task.

The @Async annotation can be provided on a method so that invocation of that method will occur asynchronously. In other words, the caller will return immediately upon invocation and the actual execution of the method will occur in a task that has been submitted to a Spring TaskExecutor. In the simplest case, the annotation may be applied to a void-returning method.

This blog is going to detail how I accomplished unit testing this service.

The new @Async annotation from SpringSource version 3.0.x can be found on http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/scheduling.html section 25.5.2

The first thing that stumped me when creating this poc, is that the Object that is annotated, cannot be the came Object that is executed as a Callable<V>.

First I take my existing component I want to change from serial, to asynchonous

public interface AppointmentComponent {

    public XMLGregorianCalendar getAppointmentDate(String accountNumber);

}

Then the actual implmentation was this

    public XMLGregorianCalendar getAppointmentDate(String accountNumber) {
        logger.debug("getAppointmentDate");

        XMLGregorianCalendar appointmentDate = null;

        try {
            AppointmentRequest request = AppointmentAssembler.assembleAppointmentRequest(accountNumber);
            AppointmentResponse response = client.lookupAppointment(request);

            if (response.getAppointment() != null) {
                appointmentDate = response.getAppointment().getAppointmentDate();
                logger.debug("found appointment date of {}.", appointmentDate);
            } else {
                appointmentDate = DatatypeFactory.newInstance().newXMLGregorianCalendar();
                appointmentDate.setYear(BigInteger.ZERO);
            }

        } catch (Exception ex) {
            logger.error("DAS returned an Exception ", ex.getMessage());
        }
        return appointmentDate;
    }

Then I created an Async version Interface

public interface AppointmentComponentExecutor {

    public Future<XMLGregorianCalendar> getAppointmentDate(String accountNumber);

    public XMLGregorianCalendar getFromFuture(Future<XMLGregorianCalendar> futureAccountInfo);

    public void setAppointmentComponent(AppointmentComponent component);

}

then the implementation was in 2 parts.

  1. I wanted to make the asynchronous call, and get Future<V> back to the caller.
  2. Next I wanted to provide a way to get the response object from the Future<V> and wrap the exceptions.
public class AppointmentComponentExecutorImpl implements AppointmentComponentExecutor {

    @Autowired
	public AppointmentComponent appointmentComponent;

    // FIXME need to externalize this property.
    static long timeout = 20L;

    private static final Logger logger = LoggerFactory.getLogger(AppointmentComponentExecutorImpl.class);

    @Async
    public Future<XMLGregorianCalendar> getAppointmentDate(String accountNumber){
        logger.debug("@Async getAppointmentDate(accountNumber)");
    	XMLGregorianCalendar appointmentDate = appointmentComponent.getAppointmentDate(accountNumber);
        return new AsyncResult<XMLGregorianCalendar>(appointmentDate);
    }

    public XMLGregorianCalendar getFromFuture(Future<XMLGregorianCalendar> futureAccountInfo){
    	logger.debug("getFromFuture");

    	XMLGregorianCalendar appointmentDate = null;
		try {
			appointmentDate = futureAccountInfo.get(timeout, TimeUnit.SECONDS);
		} catch (TimeoutException e) {
			// TODO retry logic?
		} catch (Exception e) {
            logger.error("Async request returned an exception: ", e.getMessage());
		}

        return appointmentDate;
    }

    public void setAppointmentComponent(AppointmentComponent appointmentComponent){
    	this.appointmentComponent = appointmentComponent;
    }
}

I wanted to ensure I could get some form of Unit Test done, so I used JUnit and Mockito. I mocked out my component.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application-context-test.xml"})
public class AppointmentComponentExecutorTest {

    @Autowired
    private AppointmentComponentExecutor componentExecutor;

    private AppointmentComponent component;

    protected AppointmentResponse response = new AppointmentResponse();

    static final long timeout = AppointmentComponentExecutorImpl.timeout;

    protected String accountNumber;

    protected XMLGregorianCalendar appointmentDate;

    @Before
    public void setup() {
        component = Mockito.mock(AppointmentComponent.class);

        componentExecutor.setAppointmentComponent(this.component);
        accountNumber = "1113034566667890";
    }

    @Test
    public void testGetAppointmentDateAsync() throws Exception {

        final Stopwatch stopwatch = new Stopwatch();

        final XMLGregorianCalendar appointmentDate = AppointmentDateFixture
                .createDateFutureAppointment();

        when(component.getAppointmentDate(accountNumber))
                .thenAnswer(MockUtils.getThreadDelayAnswer(appointmentDate, 20));

        stopwatch.start();
        long beforeAsyncCall = stopwatch.stop();

        System.out.println("******* beforeAsyncCall: " + beforeAsyncCall);

        Future<XMLGregorianCalendar> futureApptDate = componentExecutor
                .getAppointmentDate(accountNumber);

        long afterAsyncCall = stopwatch.stop();

        System.out.println("******* afterAsyncCall: " + stopwatch.stop());

        // Should be returned before the delayed timeout.
        assertThat(futureApptDate.isDone(), is(false));

        XMLGregorianCalendar dateReturned = futureApptDate.get();

        long afterFutureGet = stopwatch.stop();
        System.out.println("******* afterFutureGet: " + afterFutureGet);

        assertThat(futureApptDate.isDone(), is(true));
        verify(component).getAppointmentDate(accountNumber);

        // should be the total time it takes to finish the async call.
        assertThat(afterFutureGet, is(greaterThan(afterAsyncCall + 10L)));
    }

    @SuppressWarnings("unused")
    @Test(expected = TimeoutException.class)
    public void testGetAppointmentDate_Timeout() throws Exception {

        // Should take 40ms
        when(component.getAppointmentDate(accountNumber)).thenAnswer(
                MockUtils.getThreadDelayAnswer(appointmentDate, 40));

        Future<XMLGregorianCalendar> futureApptDate = componentExecutor
                .getAppointmentDate(accountNumber);

        XMLGregorianCalendar result = futureApptDate.get(5L, TimeUnit.MILLISECONDS);
        fail();
    }

    @Test
    public void testGetAppointmentDate_Null_Date() throws Exception {

        when(component.getAppointmentDate(any(String.class)))
                .thenReturn(null);

        Future<XMLGregorianCalendar> futureApptDate = componentExecutor
                .getAppointmentDate(accountNumber);

        futureApptDate.get();

    }

    @Test
    public void testGetFromFuture() throws Exception {

        Future<XMLGregorianCalendar> future = mock(Future.class);

        when(future.get(timeout, TimeUnit.SECONDS))
                .thenReturn(appointmentDate);

        XMLGregorianCalendar result = componentExecutor.getFromFuture(future);

        verify(future).get(timeout, TimeUnit.SECONDS);

    }

    @Test
    public void testGetFromFuture_TimeoutException() throws Exception {

        Future<XMLGregorianCalendar> future = mock(Future.class);

        when(future.get(timeout, TimeUnit.SECONDS))
                .thenThrow(
                        new TimeoutException());

        XMLGregorianCalendar result = componentExecutor.getFromFuture(future);

        assertNull(result);

        verify(future).get(timeout, TimeUnit.SECONDS);

    }
    @Test
    public void testGetFromFuture_ExecutionException() throws Exception {

        Future<XMLGregorianCalendar> future = mock(Future.class);

        // throws java.util.concurrent.ExecutionException()
        when(future.get(timeout, TimeUnit.SECONDS))
                .thenThrow(
                        new RuntimeException());

        XMLGregorianCalendar result = componentExecutor.getFromFuture(future);

        assertNull(result);

        verify(future).get(timeout, TimeUnit.SECONDS);

    }
}

Then I create aMock Helper to aid in creating a Mockito Answer delay.

import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class MockUtils {

	@SuppressWarnings("rawtypes")
	public static Answer getThreadDelayAnswer(final Object o, final int delay) {
		return new Answer() {
			public Object answer(InvocationOnMock invocation) {
				try {
					Thread.sleep(delay);
				} catch (InterruptedException e) {
					//ignore this error.
					//Will happen while running unit tests.
					//e.printStackTrace();
				}
				return o;
			}
		};
	}
}

The stopwatch implmentation is just a simple timer I have been using

public class Stopwatch {

	private long start;
	private long stop;

	public Stopwatch() {}

	public void start() {
		start = System.currentTimeMillis();
	}

	public long stop() {
		stop = System.currentTimeMillis();
		return stop - start;
	}
}

NOTE: There is an issue with Session Scoped Beans and @Async. Here is the error:

11:31:58,917 11/04 741CE4739B8E1BC7A87CA317155100C9 ERROR IdentifyComponentImpl - DAS returned an Exception:
org.springframework.remoting.RemoteAccessException: Could not access remote service at [http://pacdcdtadeva03:8050/DAS010WSWAR/services/IdentifyService]; nested exception is javax.xml.ws.WebServiceException: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.callSession': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

https://jira.springframework.org/browse/SPR-6479

I will post a further blog about this issue, but not now.

Conclusion

The @Async is a very handy and easy to use annotation that hide much of the details of auto-proxying asynchronous calls. The downside, is that it hides the complexity, and when issues arise, might be difficult to track down. Also there is the mentioned issue trying to proxy requests that rely on Session scoped Objects.

Mick Knutson

Java, JavaEE, J2EE, WebLogic, WebSphere, JBoss, Tomcat, Oracle, Spring, Maven, Architecture, Design, Mentoring, Instructor and Agile Consulting. http://www.baselogic.com/blog/resume

View all posts

Java / JavaEE / Spring Boot Channel

BLiNC Supporters

BLiNC Adsense

Archives

Newsletter