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 in META-INF or WEB-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.

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;
      
  • 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 and javax.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 from core-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 the queue-length attribute for details on how this value is used to determine the queuing strategy.
    • keepalive-time: defaults to 60000
    • queue-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 to Integer.MAX_VALUE (e.g. unbounded queue). All other values specify an exact queue size. If an unbounded queue or direct hand-off is used, a core-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 default ABORT, which means an exception should be thrown, or RETRY_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.