package org.superbiz.asyncpost;
import jakarta.annotation.PostConstruct;
import jakarta.ejb.EJB;
import jakarta.ejb.Lock;
import jakarta.ejb.LockType;
import jakarta.ejb.Singleton;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.InvocationContext;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import static java.util.concurrent.TimeUnit.SECONDS;
@Singleton
@Lock(LockType.READ)
public class SlowStarter {
@EJB
private Executor executor;
private Future construct;
private String color;
private String shape;
@PostConstruct
private void construct() throws Exception {
construct = executor.submit(new Callable() {
@Override
public Object call() throws Exception {
Thread.sleep(SECONDS.toMillis(10));
SlowStarter.this.color = "orange";
SlowStarter.this.shape = "circle";
return null;
}
});
}
@AroundInvoke
private Object guaranteeConstructionComplete(InvocationContext context) throws Exception {
construct.get();
return context.proceed();
}
public String getColor() {
return color;
}
public String getShape() {
return shape;
}
}
@Asynchronous @PostConstruct
Placing @Asynchronous
on the @PostConstruct
of an EJB is not a
supported part of Java EE, but this example shows a pattern which works
just as well with little effort.
The heart of this pattern is to:
-
pass the construction
logic
to an@Asynchronous
method via ajava.util.concurrent.Callable
-
ensure the bean does not process invocations till construction is complete via an
@AroundInvoke
method on the bean and thejava.util.concurrent.Future
Simple and effective. The result is a faster starting application that is still thread-safe.
The Executor
is a simple pattern, useful for many things, which
exposes an interface functionally equivalent to
java.util.concurrent.ExecutorService
, but with the underlying thread
pool controlled by the container.
package org.superbiz.asyncpost;
import jakarta.ejb.AsyncResult;
import jakarta.ejb.Asynchronous;
import jakarta.ejb.Lock;
import jakarta.ejb.LockType;
import jakarta.ejb.Singleton;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
@Singleton
@Lock(LockType.READ)
public class Executor {
@Asynchronous
public <T> Future<T> submit(Callable<T> task) throws Exception {
return new AsyncResult<T>(task.call());
}
}
Finally a test case shows the usefulness of @AroundInvoke
call in our
bean that calls construct.get()
package org.superbiz.asyncpost;
import junit.framework.Assert;
import org.junit.Test;
import jakarta.ejb.EJB;
import jakarta.ejb.embeddable.EJBContainer;
public class SlowStarterTest {
@EJB
private SlowStarter slowStarter;
@Test
public void test() throws Exception {
// Start the Container
EJBContainer.createEJBContainer().getContext().bind("inject", this);
// Immediately access the fields initialized in the PostConstruct
// This will fail without the @AroundInvoke call to construct.get()
Assert.assertEquals("orange", slowStarter.getColor());
Assert.assertEquals("circle", slowStarter.getShape());
}
}