Although Java SE5 provides an API support for concurrency via the java.util.concurrent package, there were no specific API that allowed enterprise developers to use concurrency utilities in a safe manner within the JavaEE container managed thread pools.
JavaEE 7 containers are now required to provide 4 types of managed objects that implement these interfaces. Applications look up managed objects via JNDI lookup or resource injection using @Resource.
- ManagedExecutorService
- ManagedScheduledExecutorService
- ManagedThreadFactory
- ContextService
On this post, I will use a ManagedExecutorService to create a Asynchronous HTTP request endpoint. This is an AccountService I used on an earlier post, but I added 1 second thread sleep for a demonstration purpose.
@Logging @Stateless public class AccountService { @PersistenceContext(name="demoDB") private EntityManager em; public Account updateBalance(Long id, Integer amountChange){ Account acct = em.find(Account.class, id); try { Thread.sleep(1000); } catch (InterruptedException e) { } acct.setBalance(acct.getBalance() + amountChange); //Updating the updateTime field is omitted on purpose return acct; } }
Synchronous API
Again, this is the same web service I used on a previous post.
@Logging @Path("/account") public class AccountRestEnd { @Inject private Logger logger; @Inject private AccountService accountService; @POST @Path("/balance") @Produces(MediaType.APPLICATION_JSON) public Response changeBalance(@QueryParam("id") Long acctId, @DefaultValue("0") @QueryParam("amount") Integer amount){ Account acct = null; try{ acct = accountService.updateBalance(acctId, amount); }catch(Exception ex){ return Response.status(Response.Status.CONFLICT).build(); } Response rs = Response.ok(acct).build(); logger.info("Finished a balance endpoint synchronously."); return rs; } }
By triggering a POST request http://localhost:8080/demo/account/balance?id=1&amount=1 , we see this logs, which is created by the interceptor Logging explained on this post. Every method call is started and finished in the order and the log shows that all methods are executed with a http-nio-8080-exec-2 thread.
[http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod Entering demo.rest.endpoint.AccountRestEnd - changeBalance [http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod Entering demo.rest.service.AccountService - updateBalance [http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod Exiting demo.rest.service.AccountService - updateBalance Execution Time: 1004ms [http-nio-8080-exec-2] demo.rest.endpoint.AccountRestEnd.changeBalance Finished a balance endpoint synchronously. [http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod Exiting demo.rest.endpoint.AccountRestEnd - changeBalance Execution Time: 1010ms
Asynchronous API
This Asynchronous API uses the ManagedExecutorService along with a AsyncResponse. The Asynchronous method needs to be a void type method and the 'execute' method creates an asynchronous process. To resume the http response, the 'resume' method of the AsyncResponse object.
@Logging @Path("/accountasync") public class AccountAsyncRestEnd { @Inject private Logger logger; @Inject private AccountService accountService; @Resource ManagedExecutorService mes; @POST @Path("/balance") public void changeBalanceAsync(@Suspended AsyncResponse ar, @QueryParam("id") Long acctId, @DefaultValue("0") @QueryParam("amount") Integer amount){ mes.execute(new Runnable() { @Override public void run() { Account acct = null; try{ acct = accountService.updateBalance(acctId, amount); }catch(Exception ex){ ar.resume(Response.status(Response.Status.CONFLICT).build()); } Response rs = Response.ok(acct) .encoding(MediaType.APPLICATION_JSON).build(); ar.resume(rs); } }); logger.info("Finished a balance endpoint Asynchronously."); } }
Let's trigger a POST request http://localhost:8080/demo/accountasync/balance?id=1&amount=1 and see the log again.
[http-nio-8080-exec-38] demo.interceptor.LoggingInterceptor.logMethod Entering demo.rest.endpoint.AccountAsyncRestEnd - changeBalanceAsync [http-nio-8080-exec-38] demo.rest.endpoint.AccountAsyncRestEnd.changeBalanceAsync Finished a balance endpoint Asynchronously. [http-nio-8080-exec-38] demo.interceptor.LoggingInterceptor.logMethod Exiting demo.rest.endpoint.AccountAsyncRestEnd - changeBalanceAsync Execution Time: 0ms [managed-thread-4] demo.interceptor.LoggingInterceptor.logMethod Entering demo.rest.service.AccountService - updateBalance [managed-thread-4] demo.interceptor.LoggingInterceptor.logMethod Exiting demo.rest.service.AccountService - updateBalance Execution Time: 1005ms
The RESTful API endpoint is executed with a http-nio-8080-exec-38 thread and the changeBalanceAsync method is completed immediately.
Exiting demo.rest.endpoint.AccountAsyncRestEnd - changeBalanceAsync Execution Time: 0ms
The business login in the updateBalance method is executed with a separate thread 'manaed-thread-4' and the result is returned 1005 ms later in this example.
No comments:
Post a Comment