Hola a todos!
Y aquí nos
entregamos a los bollos y lanzamos la segunda secuencia del curso
"Java Enterprise Developer" . El creador y profesor permanente del curso,
Vitaly Ivanov , incluso escribió un artículo sobre este tema, que, esperamos, les sea útil :)
Así que vamos :)
Este artículo explora la API de especificación de concurrencia JavaEE (
JSR 236 ), que define el estándar para tareas paralelas en un contenedor JavaEE utilizando el concepto de recursos gestionados. El lanzamiento de la séptima versión de JavaEE hizo posible ejecutar tareas paralelas en contenedores Enterprise, proporcionando al desarrollador herramientas y utilidades convenientes para trabajar con multitarea. Hasta ese momento, toda la multitarea se dejaba a la implementación específica del servidor de aplicaciones utilizado, que decide independientemente sobre la optimización de las tareas. La violación de este principio se consideró una mala práctica en la construcción de la arquitectura de las aplicaciones empresariales. Como resultado, no se recomendó al desarrollador que creara nuevos subprocesos y, a veces, dicho comportamiento a nivel de contenedor estaba prohibido.
El enterprise bean no debe intentar gestionar subprocesos. Enterprise Bean no debe intentar iniciar, detener, suspender o reanudar un hilo, o cambiar la prioridad o el nombre de un hilo. El enterprise bean no debe intentar gestionar grupos de subprocesos.(traducción gratuita del autor: los EJB no deben tratar de administrar hilos, es decir, intentar iniciar, detener, pausar y restaurar su ejecución, o cambiar la prioridad o cambiar el nombre de un hilo. Además, los EJB no deben tratar de administrar grupos de hilos).De hecho, prohibir la creación de sus propios subprocesos en contenedores JavaEE es bastante problemático, sin embargo, con este enfoque, los servicios en segundo plano del contenedor no pueden garantizar la exactitud de su trabajo. Por ejemplo, cerrar una transacción al finalizar el método EJB podría funcionar incorrectamente si las tareas se iniciaran en un nuevo subproceso utilizando herederos de subprocesos (o implementaciones ejecutables) de JavaSE. Además, el uso de los tipos de interfaz básicos proporcionados por la API Executor, como ExecutorService y ScheduledExecutorService, cuando se crea a través de los métodos estáticos de la clase Executors, provocaría errores potenciales e interrumpiría la ejecución de los servicios de contenedor.
De las herramientas recomendadas por la especificación JavaEE para la ejecución asincrónica de tareas, el desarrollador tuvo que usar EJB asíncronos sin estado / con estado y / o beans controlados por mensajes, cuyas capacidades son suficientes para un cierto rango de tareas, y lo más importante, la administración de las cuales es inicialmente completamente y completamente controlada por el servidor de aplicaciones, a saber, un contenedor EJB.
Sin embargo, como se señaló anteriormente, gracias a
JSR 236 , han aparecido recursos administrados por contenedor que implementan soporte para ejecución de tareas multiproceso y asincrónica, ampliando las capacidades del paquete
java.util.concurrent
de JavaSE. Para la pila JavaEE, las clases de recursos gestionados se encuentran en el paquete
javax.enterprise.concurrent
, y el acceso a los objetos de estas clases se proporciona a través de la inyección de recursos utilizando la anotación
@Resource
, o mediante el contexto JNDI (en particular, InitialContext). Al mismo tiempo, se han agregado las posibilidades de usar objetos Future / ScheduledFuture / CompletableFuture familiares para un entorno de subprocesos múltiples dentro de aplicaciones JavaEE.
Por lo tanto, hay suficientes letras y echemos un vistazo más de cerca a cada uno de los recursos administrados proporcionados por la especificación desde un punto de vista práctico, es decir, en el contexto del uso de la aplicación en el código de la aplicación, así como desde el punto de vista de la configuración de recursos usando el servidor de aplicaciones Glassfish 5 como ejemplo.
Bueno, la clase ManagedExecutorService fue la primera en considerarse, que (ya entendiendo por el nombre) amplía las capacidades del conocido JavaSE ExecutorService y está diseñado para la ejecución asincrónica de tareas en el entorno JavaEE.
Para configurar no solo este tipo de ExecutorService dentro del servidor de aplicaciones Glassfish, debe consultar el archivo de configuración domain.xml, cuya ubicación está determinada por el directorio $ {GLASSFISH_HOME} / domains / <domainname> / config. A continuación se presenta un fragmento de este archivo:
<domain application-root="${com.sun.aas.instanceRoot}/applications" version="25" log-root="${com.sun.aas.instanceRoot}/logs"> <resources> <context-service object-type="system-all" jndi-name="concurrent/__defaultContextService" /> <managed-executor-service object-type="system-all" jndi-name="concurrent/__defaultManagedExecutorService" /> <managed-scheduled-executor-service object-type="system-all" jndi-name="concurrent/__defaultManagedScheduledExecutorService" /> <managed-thread-factory object-type="system-all" jndi-name="concurrent/__defaultManagedThreadFactory" /> </resources> <servers> <server config-ref="server-config" name="server"> <resource-ref ref="concurrent/__defaultContextService" /> <resource-ref ref="concurrent/__defaultManagedExecutorService" /> <resource-ref ref="concurrent/__defaultManagedScheduledExecutorService" /> <resource-ref ref="concurrent/__defaultManagedThreadFactory" /> </server> </servers> </domain>
Entrando en la interfaz del panel de administración de Glassfish 5, configurando
ManagedExecutorService es el siguiente:

Esta sección permite la creación de nuevos recursos del mismo tipo, la gestión de los recursos existentes, la eliminación, así como el bloqueo y desbloqueo.
Para los fanáticos de la administración de la consola en Glassfish, se presenta una poderosa utilidad asadmin, que utiliza el comando
create-managed-executor-service
en ella, puede crear nuevos recursos ManagedExecutorService:

En el código de la aplicación, es más conveniente usar la inyección de recursos para obtener una referencia al objeto creado por ManagedExecutorService, pero también puede usar las herramientas JNDI, como se muestra a continuación:
@Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; InitialContext context = new InitialContext(); ManagedExecutorService managedExecutorServiceWithContext = (ManagedExecutorService) context.lookup( "concurrent/OtusExecutorService");
Me gustaría llamar la atención del lector sobre el hecho de que el parámetro de búsqueda es opcional para la anotación
@Resource
, y si el desarrollador no lo define en el código de la aplicación, el contenedor inyecta recursos predeterminados con el prefijo
__default
en su
__default
. En este caso, para el desarrollador, el código se vuelve aún más conciso:
@Resource ManagedExecutorService executor;
Después de recibir una referencia a este objeto, utilizando los métodos
execute()
y
submit()
, puede ejecutar tareas implementando la interfaz Runnable o Callable dentro del contenedor.
En cuanto a un ejemplo, me gustaría señalar que, entre toda la variedad de casos posibles, los más interesantes son las tareas realizadas en un entorno JavaEE distribuido y en las que es importante proporcionar soporte transaccional en un entorno multiproceso. Como sabe, JavaEE ha desarrollado la especificación JTA (Java Transaction API), que le permite determinar los límites de una transacción comenzando explícitamente con el método
begin()
y terminando con los métodos
commit()
,
commit()
cambios o
rollback()
, que revierte las acciones realizadas.
Considere una tarea de ejemplo que devuelve un mensaje de una lista de cien elementos por índice en una transacción de usuario:
public class TransactionSupportCallableTask implements Callable<String> { private int messageIndex; public TransactionSupportCallableTask(int messageId) { this. messageIndex = messageId; } public String call() { UserTransaction tx = lookupUserTransaction(); String message = null; try { tx.begin(); message = getMessage(messageIndex); tx.commit(); } catch (Exception e) { e.printStackTrace(); try { tx.rollback(); } catch (Exception e1) { e1.printStackTrace(); } } return message; } private void getMessage(int index) { … } private UserTransaction lookupUserTransaction () { … } }
El código para el servlet que muestra un mensaje de la lista en un índice seleccionado al azar:
@WebServlet("/task") public class ManagedExecutorServiceServlet extends HttpServlet { @Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Future<String> futureResult = executor.submit(new TransactionSupportCallableTask(Random.nextInt(100))); while (!futureResult.isDone()) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } try { response.getWriter().write("Callable task has received message with following content '" + futureResult.get() + "'"); } catch(Exception e) { e.printStackTrace(); } } }
El siguiente a considerar es el recurso ManagedScheduledExecutorService, cuyo objetivo principal es planificar tareas que se repiten a intervalos regulares o que requieren una ejecución retrasada.

Desde el punto de vista de configurar este recurso a través de la consola de administración GlassFish, no se encontraron cambios especiales en comparación con el tipo anterior:

Para crear rápidamente un recurso de tipo ManagedScheduledExecutorService,
asadmin
tiene el
asadmin
create-managed-scheduled-executor-service

En el código de la aplicación, todavía usamos la inyección de recursos:
@Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor;
Los principales métodos para ejecutar tareas para este tipo de ExecutorService son
schedule()
, que recibe tareas del tipo Runnable o Callable como entrada, y
scheduleAtFixedRate()
, que determina adicionalmente el retraso inicial en la tarea y establece el intervalo de repetición en TimeUnit (segundos, minutos, etc.) .).
El caso anterior se puede reescribir de la siguiente manera:
@WebServlet("/scheduledTask") public class ManagedScheduledExecutorServiceServlet extends HttpServlet { @Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ScheduledFuture<String> futureResult = scheduledExecutor.schedule( new TransactionSupportCallableTask(Random.nextInt(100)), 5, TimeUnit.SECONDS); while (!futureResult.isDone()) { try { Thread.sleep(50);
Además, la API de concurrencia para el entorno Enterpise proporciona la capacidad de crear flujos controlados. Para estas tareas, debe usar las capacidades de una fábrica de subprocesos administrados que implementa su funcionalidad a través de la clase ManagedThreadFactory del mismo nombre y también se accede a través del servicio JNDI:
@Resource ManagedThreadFactory factory;
La ventana de administración para la consola Glassfish se ve "pasada de moda":

Al usar una fábrica de subprocesos administrados, es posible no solo proporcionar al contenedor mecanismos de control de flujo, sino también inicializar las propiedades de los subprocesos generados: establecer nombres y priorizar, lo que en el futuro puede simplificar en gran medida la búsqueda de problemas al analizar un volcado de subprocesos al detectar fácilmente el orden de ejecución de subprocesos previamente nombrados.
En nuestro caso, definimos la clase de la secuencia que muestra información sobre un amigo con quien esta tarea está indisolublemente vinculada a la consola:
public class SimpleThreadTask implements Runnable { private String friend; public SimpleThreadTask(String friend){ this.friend = friend; } @Override public void run() { System.out.println("Hello, " + friend); } }
Deje que el servlet inicie el hilo e informe esto a la salida:
@WebServlet("/thread") public class ManagedThreadFactoryServlet extends HttpServlet { @Resource ManagedThreadFactory factory; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Thread thread = factory.newThread(new SimpleThreadTask("Otus")); thread.setName("ManagedThreadFromPool"); thread.setPriority(7); thread.start(); response.getWriter().write("Custom thread has been running."); } }
En cuanto a la característica final de JavaEE en el campo de los subprocesos múltiples: Servicios de contexto, debe tenerse en cuenta que estos servicios crean objetos proxy contextuales dinámicos. Todos estamos familiarizados con las capacidades de los proxies dinámicos de JavaSE (
java.lang.reflect.Proxy
), que le permiten generar una implementación dinámica de las interfaces requeridas, cuyas capacidades se utilizan activamente para crear conexiones de bases de datos y gestión de transacciones, se utilizan para todo tipo de interceptores AOP, etc. Además, para los servidores proxy creados a través de los servicios de contexto JavaEE, se supone que pueden funcionar dentro del marco de un contexto JNDI común, contexto de seguridad y clase de contenedor.
Para conectar el servicio, solo use el código:
@Resource ContextService service;
Desde el punto de vista de administrar y configurar este recurso, todo es extremadamente familiar y similar a los tipos ya considerados:

A continuación se muestra un ejemplo de un hilo que inicia una tarea proxy en el contexto de un contenedor:
public class SampleProxyTask implements Runnable { @Override public void run() {
Bean EJB sin estado para crear proxys contextuales:
@Stateless public class ContextServiceBean { @Resource ContextService service; @Resource ManagedExecutorService executor; public void perform(Runnable task) { Runnable proxy = service.createContextualProxy(task, Runnable.class); executor.submit(proxy); } }
Y finalmente, el código para el servlet que ejecuta la tarea:
@WebServlet("/context") public class ContextServiceServlet extends HttpServlet { @Inject ContextServiceBean contextServiceBean; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { contextServiceBean.perform(new SampleProxyTask()); } }
En realidad, esto finaliza las capacidades del desarrollador JavaEE para trabajar en un entorno multiproceso. Gracias a ellos, todos los procesos y servicios que ocurren en el contenedor estarán bajo el estricto control del servidor, coordinando su trabajo y sin violar la orden de ejecución habitual. Para los objetivos del desarrollador Enterprise, estas capacidades son a menudo suficientes y en la octava versión esta API no ha cambiado.
El fin
Como siempre, estamos esperando preguntas y comentarios y asegúrese de ver a
Vitaly para
una lección abierta , donde también puede hacer preguntas y escuchar / participar en el tema "CDI en acción @