Jakarta EE
created: 14 May 2021
modified: 19 December 2021
revision: 2
Managed Beans - an object with a lifecycle managed by a EE container.
- Enterprise Java have many lifecycle management containers like JSF, EJB, CDI, Servlet, etc., where each of these provides different functionalities.
- CDI was made to support (almost) everithing supported by JSF and EJB together with a lot of other stuff: pojo injections, producer methods, stereotypes, events,interceptors, decorators, integration SPI, etc.
- Once thre is a
beans.xml
inMETA-INF
orWEB-INF
every bean in the package becomes a CDI managed bean. - EJBS can be injected in CDI and vice-versa, where different scopes are managed by the CDI thru the use of proxies.
- EJBs (
@Stateful
or@Stateless
) however have a few differences with CDI, as ejbs are:- Transactional
- Remote/Local
- can passivate stateful beans
- have timers
- can be async // TODO: verify CDI changes for Java7,8,9
- CDI does not support injection of remote beans.
a few useful links
- See “Conclusion” part ofr EJB vs CDI instantiation
- See default scopes for services
- CDI documentation form JBoss
- EJB usage recommendations by Oracle
- [CDI usage recommendations by Oracle][https://docs.oracle.com/javaee/6/tutorial/doc/gjbbk.html]
Examples and Details
- CDI needs a default constructor to inject an instance, or else use a factory with
@Produces
method. - For different implementation of an interface a custom annotation can be used:
public enum ProfileType { ADMIN, OPERATOR }
@Qualifier // @Retention, @Target
public @interface Profile { ProfileType value(); }
@Profile(ProfileType.ADMIN) // add @Default for the default one
public class A implements I { }
@Path("myservice/") @RequestScoped
public class MyService {
// without @Profile, the default implementation will be injected.
@Inject @Profile(ProfileType.ADMIN)
private I i;
// @Context ask CDI to give the proper context when the servlet miss one
@GET @Path("getUser")
public Response getUser(@Context HttpServletRequest request,
@Context HttpServletResponse response) { ... }
}
- Events
@Path("myservice/") @RequestScoped
public class MyService {
@Inject private User u;
@Inject private Event<User> e;
public class Observer {
public void observes1(@Observes User user) {...}
public void observes2(@ObservesAsync User user) {...}
}
- JTA
- With
transaction-type='JTA'
, the server will take care of all transactions made under this context. - With
transaction-type='RESOURCE-LOCAL
instead, the developer is taking care of the transactions<persistence-unit name="my-persistence-unit" transaction-type="JTA">
@PersistenceContext(unitName = "ch02-jpa-pu", type = PersistenceContextType.TRANSACTION) private EntityManager em;
- With
- HealthChecks
@Liveness // @Readiness
@ApplicationScoped
public class LivenessHealthCheck implements HealthCheck{
@Override
public HealthCheckResponse call() {
return HealthCheckResponse.up("I'm up!");
}
}
- Metrics
- Open API Definition - method annotation
@OpenAPIDefinition(...)
. See API for more annotations.
Concurrency
Examples derived from (Moraes, 2020) and (Juneau, 2020)
Singleton Concurrency management
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@AccessTimeout(value = 10000)
public class UserMethodLevelBean {
private int userCount;
@Lock(LockType.READ)
public int getUserCount(){
return userCount;
}
@Lock(LockType.WRITE)
public void addUser(){
userCount++;
}
}
Async tasks
public class Pojo { /* getters, setters */ }
@Stateless
public class PojoFacade { public Pojo getPojo() {...} }
// not the best apporach, but gives the main ides
public class Task implements Callable<Pojo> {
private UserTransaction transaction;
private PojoFacade facade;
public Pojo call() {
performLookups();
try {
transaction.begin();
Pojo pojo acade.getPojo();
transaction.commit();
return pojo;
} catch (...) {
transaction.rollback();
retrun null;
}
}
private performLookups() {
transaction = CDI.current().select(UserTransaction.class).get();
facade = CDI.current().select(PojoFacade.class).get();
}
}
- ManagedExecutorService
@Path("service1") @RequestScoped
public class Service1 {
@Resource(name = "LocalManagedExecutorService")
private ManagedExecutorService executor;
// The @Suspended annotation, combined with AsyncResponse, resumes the response once the processing is complete.
@GET
public void callMe1(@Suspended AsyncResponse response) {
Future<Pojo> result = executor.submit(new Task());
while(!result.isDone()) {
TimeUnit.SECONDS.sleep(1); // in try catch
}
response.resume(Response.ok(result.get()).build()); // in try catch
}
- ManagedThreadFactory
@Path("service2") @RequestScoped
public class Service2 {
@Inject
private PojoFacade facade;
@Resource(name = "LocalManagedThreadFactory")
private ManagedThreadFactory factory;
@GET
public void callMe2(@Suspended AsyncResponse response) {
Thread t = factory.newThread(() -> response.resume(Response.ok(result.get()).build()));
t.start();
}
- Custom ExecutorService
@Stateless
public class AsyncTask implements Callable<Pojo>{
@Override
public Pojo call() throws Exception {
PojoFacade facade = CDI.current().select(PojoFacade.class).get()
return facade.getPojo();
}
}
@Singleton // there must be only 1 proxy
public class ExecutorProxy {
@Resource(name = "LocalManagedThreadFactory")
private ManagedThreadFactory factory;
@Resource(name = "LocalContextService")
private ContextService context;
private ExecutorService executor;
@PostConstruct
public void init() {
// use the ManagedThreadFactory for the new executor !!
executor = new ThreadPoolExecutor(1, 5, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), factory);
}
public Future<Pojo> submit(Callable<Pojo> task) {
Callable<Pojo> ctxProxy = context.createContextualProxy(task, Callable.class);
return executor.submit(ctxProxy);
}
}
@Stateless @Path("asyncService")
public class AsyncService {
@Inject // inject the singleton executor proxy
private ExecutorProxy executor;
@GET
public void asyncService(@Suspended AsyncResponse response) {
Future<User> result = executor.submit(new AsyncTask());
response.resume(Response.ok(result).build());
}
}
- CompletableFuture
@Stateless
public class PojoFacade {
public Pojo getPojo() { return new Pojo(); }
}
@Stateless @Path("asyncService")
public class AsyncService {
@Inject
private PojoFacade facade;
// TODO: shoudn't three be a managed execution service within the supplyAsync parameters ?! Otherwise the default ForkJoinPool is used?!
@GET
public void asyncService(@Suspended AsyncResponse response) {
CompletableFuture.supplyAsync(() -> facade.getPojo()).
.thenAccept(pojo -> response.resume(Response.ok(pojo).build()));
}
}
- Asynchronous Session Beans
@Stateless
public class PojoFacade {
@Asynchronous
public Future<Pojo> getPojo() {
return new AsyncResult(new Pojo());
}
}
@Stateless @Path("asyncService")
public class AsyncService {
@Inject
private PojoFacade facade;
@GET
public void asyncService(@Suspended AsyncResponse response) {
Future<Pojo> result = facade.getPojo();
while(!result.isDone()) { /* wait a while */ }
response.resume(Response.ok(result).build());
}
}
- Asynchronous Servlet
@Stateless
public class PojoFacade {
public Pojo getPojo() { return new Pojo(); }
}
@WebServlet(name = "PojoServlet", urlPatterns = {"/PojoServlet"}, asyncSupported = true)
public class PojoServlet {
@Inject
private PojoFacade facade;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
AsyncContext ctx = req.startAsync();
ctx.start(() -> {
ctx.getResponse().getWriter();
...
ctx.complete();
});
}
}
WebSocket
MDB
Concurrency Service Management (jboss examples)
<subsystem xmlns="urn:jboss:domain:ee:4.0">
<spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
<concurrent>
<context-services>
<context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" transaction-setup-provider="true"/>
</context-services>
<managed-thread-factories>
<managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" ext-service="default"/>
</managed-thread-factories>
<managed-executor-services>
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" ext-service="default" hung-task-threshold="60000" keepalive-time="5000"/>
</managed-executor-services>
<managed-scheduled-executor-services>
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" service="default" hung-task-threshold="60000" keepalive-time="3000"/>
</managed-scheduled-executor-services>
</concurrent>
<default-bindings context-service="java:jboss/ee/concurrency/context/default" datasource="java:jboss/datasources/ExampleDS" managed-executor-service="java:jboss/ee/concurrency/executor/default" managed-scheduled-executor-service="java:jboss/ee/concurrency/scheduler/default" managed-thread-factory="java:jboss/ee/concurrency/factory/default"/>
</subsystem>
- The context service
javax.enterprise.concurrent.ContextService
allows you to build contextual proxies from existing objects. Contextual proxy prepares the invocation context, which is used by other Jakarta Concurrency utilities when the context is created or invoked, before transferring the invocation to the original object. - The managed thread factory
javax.enterprise.concurrent.ManagedThreadFactory
concurrency utility allows Jakarta EE applications to create Java threads. JBoss EAP handles the managed thread factory instances, hence Jakarta EE applications cannot invoke any lifecycle related method.priority
: Optional. Indicates the priority for new threads created by the factory, and defaults to 5`
- The Managed executor service
javax.enterprise.concurrent.ManagedExecutorService
andjavax.enterprise.concurrent.ManagedScheduledExecutorService
allows Jakarta EE applications to submit tasks for asynchronous execution. JBoss EAP handles managed executor service instances, hence Jakarta EE applications cannot invoke any lifecycle related method.max-threads
: Defines the maximum number of threads used by the executor. If undefined, the value fromcore-threads
is used.thread-factory
: References an existing managed thread factory by its name, to handle the creation of internal threads. If not specified, then a managed thread factory with default configuration will be created and used internally.core-threads
: Defines the minimum number of threads to be used by the executor. If this attribute is undefined, the default is calculated based on the number of processors. A value of 0 is not recommended. See thequeue-length
attribute for details on how this value is used to determine the queuing strategy.keepalive-time:
defaults to 60000queue-length
: Indicates the executor’s task queue capacity. A value of 0 means direct hand-off and possible rejection will occur. If this attribute is undefined or set toInteger.MAX_VALUE
(e.g. unbounded queue). All other values specify an exact queue size. If an unbounded queue or direct hand-off is used, acore-threads
value greater than 0 is required.reject-policy
: Defines the policy to use when a task is rejected by the executor. The attribute value can be the defaultABORT
, which means an exception should be thrown, orRETRY_ABORT
, which means the executor will try to submit it once more, before throwing an exception.
Testing
References
- Juneau, J. (2020). Get started with concurrency in Jakarta EE. https://blogs.oracle.com/javamagazine/get-started-with-concurrency-in-jakarta-ee
- Moraes, E. (2020). Jakarta EE Cookbook, 2nd Edition. Packt Publishing.