Asynchronous method invocation and RequestScoped beans

The problem

This week I ran into a problem with asynchronous method invocation and a RequestScoped bean. Consider the following situation.

I have a JAX-WS web service with a SOAPHandler handling the authentication. In the SOAPHandler an object (let’s call it RequestContext) is filled with parameters specific to the request like the user and customer information) This object is a regular class, annotated with the @RequestScoped annotation, so it lives during the handling of the SOAP request.

As defined, the @RequestScoped object is available in EJBs which get called by the JAX-WS implementation class. However, when in one of the EJBs an @Asynchronous method gets invoked, the RequestScope gets reinitialized. So far I haven’t found any spec yet documenting this behaviour, but in a way I think it makes sense. The @Asynchronous method may outlive the actual SOAP request, and I figure that’s why RequestScoped beans get reinitialized.

Although the async method won’t outlive the SOAP request in my case, I’ve constructed a workaround. Instead of injecting the RequestScoped bean, I’m passing a Callable<V> to the async method. See the following examples.

The code

@RequestScoped
public class RequestContext implements Cloneable
{
	private String text;

	public String getText()
	{
		return text;
	}

	public void setText(String text)
	{
		this.text = text;
	}

	public RequestContext clone()
	{
		RequestContext cloned = new RequestContext();
		cloned.text = text;
		return cloned;
	}

}
public class ContextUpdater implements SOAPHandler<SOAPMessageContext>
{
	@Inject
	private RequestContext requestContext;

	@Override
	public boolean handleMessage(SOAPMessageContext context)
	{
		Boolean isOutbound = (Boolean) context.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
		if (isOutbound != null && isOutbound)
			return true;

		requestContext.setText("Text set from SOAPHandler");
		return true;
	}

// ... Other SOAPHandler methods are irrelevant to this example 
}
@WebService
@HandlerChain(file = "/handlerchain.xml")
public class TestService
{
	@EJB
	private TestBean testBean;
	@Inject
	private RequestContext requestContext;
	private static final Logger LOGGER = LoggerFactory.getLogger(TestService.class);

	@WebMethod(operationName = "TestCall")
	public void testCall()
	{
		// This is important! The requestContext needs to be cloned. I'll explain this in a few moments.
		RequestContext clonedRequest = requestContext.clone();
		Future<String> asyncResult = testBean.asyncMethod(() -> clonedRequest)
		// ...
	}
}
@Stateless
public class TestBean
{
	@Asynchronous
	public Future<String> async(Callable<RequestContext> requestContextCallable) throws Exception
	{
		String result = requestContextCallable.call().getText();
		return new AsyncResult<>(requestContext.getText());
	}
}

The code explained

As I outlined in the comment of TestService, the injected requestContext needs to be cloned. If you pass in the injected requestContext, you’re actually passing a proxy to the original requestContext. If you call clone() on the proxy, you’ll get the actual clone and so a plain non-CDI object. The Callable will exist for as long as the async method exists, because the async method holds a reference to it.

This is probably not the best way to do it, and there are obvious downsides to it. It’s not possible to modify the request context in the async method, but I guess it’s not a good idea to do such modifications in scoped beans from async methods anyway.

Conclusion

When you need this kind of a construct, this may be a way to do it. If you have better ways of doing this, please tell me so in the comments 🙂