8. RxJava en el entorno Swing
8.1. Introduction
Aquí volveremos a la aplicación Swing presentada en el párrafo 2.
![]() |
Para trabajar con RxJava en un entorno Swing, utilizaremos la biblioteca RxSwing, que agrega a RxJava clases e interfaces útiles en un entorno Swing. Para ello, el archivo Gradle del ejemplo de Swing es el siguiente:
![]() |
buildscript {
repositories {
mavenCentral()
}
}
apply plugin: 'java'
jar {
baseName = 'exemples-01'
version = '0.0.1-SNAPSHOT'
}
repositories {
mavenCentral()
}
dependencies {
compile('io.reactivex:rxswing:0.25.0')
compile('io.reactivex:rxjava:1.1.3')
compile('com.fasterxml.jackson.core:jackson-databind:2.7.3')
}
task wrapper(type: Wrapper) {
gradleVersion = '2.9'
}
- línea 15: la dependencia de RxSwing;
Solo utilizaremos un único objeto específico de RxSwing: el programador [SwingScheduler.getInstance()], que se encarga de ejecutar y observar los observables en el hilo del bucle de eventos de Swing. Lo utilizaremos exclusivamente para observar observables que se ejecutan en hilos distintos al del bucle de eventos. Recordemos la arquitectura de la aplicación de ejemplo:

- la capa de servicio asíncrona cuenta con métodos que devuelven observables. Ejecutamos estos observables en hilos distintos al del bucle de eventos. De esta manera, la interfaz gráfica no se congela. Puede reaccionar a las acciones del usuario. La más evidente es permitir que el usuario haga clic en un botón [Annuler] para interrumpir una operación asíncrona que se demore demasiado. Para que pueda hacerlo, basta con que la interfaz gráfica esté congelada (frozen);
- la capa Swing quiere aprovechar los resultados devueltos por las operaciones asincrónicas y, a partir de ellos, actualizar la interfaz gráfica. Sin embargo, esto solo puede hacerse en el hilo del bucle de eventos. Para ello, estos resultados se observan en el programador [SwingScheduler.getInstance()];
Así, en el código de manejo de eventos de la interfaz gráfica, la interacción con la capa asíncrona [rxService] se realiza de la siguiente manera:
Observable obs=rxService.doSomething(...).subscribeOn(Schedulers.computation()).observeOn(SwingScheduler.getInstance()) ;
donde el programador [Schedulers.computation()] podrá ser reemplazado por otro programador según los casos de uso.
Se invita al lector a volver a leer el párrafo 2. Ahora cuenta con los conocimientos necesarios para comprenderlo por completo.
8.2. La estructura del código
El código implementa la siguiente arquitectura:

El proyecto de IntelliJ IDEA que implementa esta arquitectura es el siguiente:
![]() |
- el paquete [rxswing.service] implementa las capas de servicio síncronas (IService, Service) y asíncronas (IRxService, RxService);
- el paquete [rxswing.ui] implementa la interfaz Swing;
8.3. Ejecución del proyecto
Para ejecutar el proyecto en IntelliJ IDEA, siga estos pasos:
![]() |
8.4. El servicio síncrono

![]() |
La capa de servicio síncrono presenta la siguiente interfaz [IService]:
package dvp.rxswing.service;
public interface IService {
// números aleatorios en el intervalo [a,b]
// se generan n números, siendo n en sí mismo un número aleatorio en el intervalo [minCount, maxCount]
// los números se generan tras una espera de delay milisegundos,
// donde [delay] es a su vez un número aleatorio en el intervalo [minDelay, maxDelay]
public ServiceResponse getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay);
}
El tipo [ServiceResponse] de la respuesta del servicio es el siguiente:
package dvp.rxswing.service;
import java.util.List;
public class ServiceResponse {
// tiempo de espera del servicio
private int delay;
// números aleatorios
private List<Integer> aleas;
// hilo de ejecución
private String executedOn;
// constructores
public ServiceResponse() {
// hilo de ejecución
executedOn = Thread.currentThread().getName();
}
public ServiceResponse(int delay, List<Integer> aleas) {
// constructor local
this();
// otras inicializaciones
this.delay = delay;
this.aleas = aleas;
}
// getter y setter
...
}
La interfaz [IService] está implementada por la siguiente clase [Service]:
package dvp.rxswing.service;
import java.util.*;
public class Service implements IService {
@Override
public ServiceResponse getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay) {
// números aleatorios en el intervalo [a,b]
// se generan n números, siendo n en sí mismo un número aleatorio en el intervalo [minCount, maxCount]
// los números se generan tras una espera de delay milisegundos,
// donde [delay] es a su vez un número aleatorio en el intervalo [minDelay, maxDelay]
// ¿algunas verificaciones
List<String> messages = new ArrayList<>();
int erreur = 0;
if (a < 0) {
messages.add("Le nombre a de l'intervalle [a,b] de génération doit être supérieur à 0");
erreur |= 2;
}
if (a >= b) {
messages.add("Dans l'intervalle [a,b] de génération, on doit avoir a< b");
erreur |= 4;
}
if (minCount < 0) {
messages.add("Le nombre min de l'intervalle [min,count] du nombre de valeurs générées doit être supérieur à 0");
erreur |= 16;
}
if (minCount > maxCount) {
messages.add("Dans l'intervalle [min,count] du nombre de valeurs générées, on doit avoir min<= max");
erreur |= 32;
}
if (minDelay < 0) {
messages.add("Le nombre min de l'intervalle [min,count] du délai d'attente doit être supérieur à 0");
erreur |= 64;
}
if (minCount > maxCount) {
messages.add("Dans l'intervalle [min,count] du délai d'attente, on doit avoir min<= max");
erreur |= 128;
}
if (maxDelay > 5000) {
messages.add("L'attente en millisecondes avant la génération des nombres doit être dans l'intervalle [0,5000]");
erreur |= 256;
}
// ¿errores?
if (!messages.isEmpty()) {
throw new AleasException(String.join(" [---] ", messages), erreur);
}
// generador de números aleatorios
Random random = new Random();
// ¿espera?
int delay = minDelay + random.nextInt(maxDelay - minDelay + 1);
if (delay > 0) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new AleasException(String.format("[%s : %s]", e.getClass().getName(), e.getMessage()), 1024);
}
}
// generación del resultado
int count = minCount + random.nextInt(maxCount - minCount + 1);
List<Integer> nombres = new ArrayList<>();
for (int i = 0; i < count; i++) {
nombres.add(a + random.nextInt(b - a + 1));
}
// retorno del resultado
return new ServiceResponse(delay,nombres);
}
}
La clase de excepción [AleasException] utilizada por el servicio es la siguiente:
package dvp.rxswing.service;
public class AleasException extends RuntimeException {
private static final long serialVersionUID = 1L;
// código de error
private int code;
// constructores
public AleasException() {
}
public AleasException(String detailMessage, int code) {
super(detailMessage);
this.code = code;
}
public AleasException(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
public AleasException(String detailMessage, Throwable throwable, int code) {
super(detailMessage, throwable);
this.code = code;
}
// getters y setters
...
}
- línea 3: extiende la clase [RuntimeException]. Por lo tanto, se trata de una excepción no controlada;
- línea 7: agrega un código de error a su clase padre (0 = sin error);
8.5. El servicio asíncrono

![]() |
La capa de servicio asíncrono presenta la siguiente interfaz [IRxService]:
package dvp.rxswing.service;
import dvp.rxswing.ui.UiResponse;
import rx.Observable;
public interface IRxService {
// números aleatorios en el intervalo [a,b]
// Se generan n números, siendo n en sí mismo un número aleatorio en el intervalo [minCount, maxCount]
// los números se generan tras una espera de delay milisegundos,
// donde [delay] es a su vez un número aleatorio en el intervalo [minDelay, maxDelay]
public Observable<UiResponse> getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay, UiResponse uiResponse);
}
- línea 11: ahora es un observable lo que devuelve el método [getAleas] del servicio;
El método [getAleas] devuelve una respuesta de tipo [UiResponse] destinada a la capa [Ui]. Este tipo es el siguiente:
package dvp.rxswing.ui;
import dvp.rxswing.service.ServiceResponse;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class UiResponse {
// ID del cliente
private int idClient;
// respuesta del servicio
private ServiceResponse serviceResponse;
// nombre del hilo de observación
private String observedOn;
// hora de la solicitud
private String requestAt;
// hora de la respuesta
private String responseAt;
// constructores
public UiResponse() {
// hilo de observación
observedOn = Thread.currentThread().getName();
// hora de la solicitud
requestAt = getTimeStamp();
}
// métodos privados
private String getTimeStamp() {
return new SimpleDateFormat("hh:mm:ss:SSS").format(Calendar.getInstance().getTime());
}
// getters y setters
...
}
- los números aleatorios se encuentran en el campo de la línea 13;
- los demás campos sirven para especificar los hilos de ejecución y observación del observable del servicio asíncrono, así como las horas de la solicitud enviada al servicio y de la respuesta obtenida;
La interfaz asíncrona está implementada por la siguiente clase [RxService]:
package dvp.rxswing.service;
import dvp.rxswing.ui.UiResponse;
import rx.Observable;
public class RxService implements IRxService {
// servicio síncrono
private IService service;
// constructor
public RxService(IService service) {
this.service = service;
}
@Override
public Observable<UiResponse> getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay, UiResponse uiResponse) {
// se crea un observable que emite el valor devuelto por el servicio síncrono
return Observable.create(subscriber -> {
try {
// llamada síncrona
uiResponse.setServiceResponse(service.getAleas(a, b, minCount, maxCount, minDelay, maxDelay));
// se pasa el resultado al observador
subscriber.onNext(uiResponse);
} catch (Exception e) {
// se pasa el error al observador
subscriber.onError(e);
} finally {
// se le notifica al observador que las emisiones han finalizado
subscriber.onCompleted();
}
});
}
}
- líneas 12-14: la clase [RxService] del servicio asíncrono se construye a partir de una instancia de la interfaz síncrona [IService];
- líneas 20-33: construcción del observable, resultado del método [getAleas];
- línea 22: se invoca el método síncrono [service.getAleas]. Su resultado, de tipo [ServiceResponse], se incluye en el objeto de tipo [UiResponse] que se proporcionará a la capa [swing]. Este objeto se pasó inicialmente en los parámetros de llamada del método (último parámetro, línea 17);
- línea 24: la respuesta [UiResponse] se envía al observador (la capa [swing]). El objeto [UiResponse] no solo contiene la información generada por el servicio síncrono de la línea 22. También contiene otra información generada por el método que llama al método [getAleas] de la línea 17. Es por esta razón que este método llamante pasó el objeto [UiResponse] como parámetro al método [getAleas] (último parámetro, línea 17);
- línea 30: no hay que olvidar indicar el fin de las transmisiones. Aquí tenemos un observable que solo emite un valor: el devuelto por el servicio síncrono;
- línea 27: se notifica al observador un posible error;
8.6. La interfaz gráfica

![]() |
- La interfaz gráfica se creó con el IDE [Netbeans], que cuenta con un buen editor gráfico. Este editor generó el archivo [AbstractJFrameAleas.form], que solo puede ser utilizado por este IDE;
- la clase [AbstractJFrameAleas] también fue generada por el editor gráfico de NetBeans. Posteriormente, se refactorizó de la siguiente manera: los eventos de la interfaz gráfica que queríamos manejar se procesan en la clase [AbstractJFrameAleas] mediante métodos abstractos implementados en la clase hija [JFrameAleasEvents]. Al final,
- La clase abstracta [AbstractJFrameAleas] se encarga de construir y mostrar la interfaz gráfica;
- la clase hija [JFrameAleasEvents] se encarga de gestionar los eventos de esta;
Los componentes de la interfaz gráfica de la pestaña [Request] son los siguientes:
![]() |
n.º | tipo | nombre | función |
1 | JTabbedPane | jTabbedPane1 | un contenedor de pestañas. Contiene dos pestañas (JPanel) [jPanelRequest] para la solicitud, [jPanelresponse] para la respuesta; |
2 | JTextField | jTextFieldNbValeurs | el número de solicitudes que se deben realizar al servicio de números aleatorios. En el caso del servicio asíncrono ejecutado en el programador [Schedulers.io], estas solicitudes compartirán un procesador; |
3 | JTextField | jTextFieldA | límite a del intervalo [a,b] |
4 | JTextField | jTextFieldB | terminal b del intervalo [a,b] |
5 | JTextField | jTextFieldMinCount | terminal minCount del intervalo [minCount, maxCount] |
6 | JTextField | jTextFieldMaxCount | terminal maxCount del intervalo [minCount, maxCount] |
7 | JTextField | jTextFieldMinDelay | terminal minDelay del intervalo [minDelay, maxDelay] |
8 | JTextField | jTextFieldMaxDelay | terminal maxDelay del intervalo [minDelay, maxDelay] |
9 | JCheckBox | jCheckBoxRxSwing | Si la casilla está marcada, las consultas se realizan a través de la interfaz asíncrona. De lo contrario, se realizan a través de la interfaz síncrona |
10 | JComboBox | jComboBoxSchedulers | En el caso de las solicitudes asincrónicas, estas se ejecutarán con el programador seleccionado aquí |
11 | JButton | jButtonGenerate | inicia la ejecución de las consultas en el servicio síncrono o asíncrono |
Los componentes de la interfaz gráfica de la pestaña [Response] son los siguientes:
![]() |
n.º | tipo | nombre | función |
1 | JLabel | jLabelDuree | el tiempo total de ejecución en milisegundos de las consultas |
2 | JLabel | jLabelNbReponses | el número total de respuestas observadas (puede ser diferente del número de consultas, ya que cada consulta puede proporcionar varios valores a observar) |
3 | JList | jListNumbers | Visualización de los valores observados (recibidos) |
4 | JButton | jButtonAnnuler | Cancela las consultas en ejecución |
8.7. Instanciación de la interfaz gráfica
![]() |
La clase [JFrameAleasEvents] gestiona los eventos de la interfaz gráfica de usuario, en particular el clic en el botón [Générer]. Es una clase ejecutable que se inicia en el siguiente contexto:
public class JFrameAleasEvents extends AbstractJFrameAleas {
private static final long serialVersionUID = 1L;
// servicio de generación síncrona
private IService service;
// servicio de generación asíncrona
private IRxService rxService;
// los registros
private int nbRequests;
private int a;
private int b;
private int minDelay;
private int maxDelay;
private int minCount;
private int maxCount;
// mensajes de error
private final String jLabelNbValuesErrorText = "Tapez un nombre entier >=1";
private final String jLabelCountErrorText = "minCount doit être >=0 et maxCount>=minCount ";
private final String jLabelDelayErrorText = "minDelay doit être >=0 et maxDelay>=minDelay et maxDelay<=5000";
private final String jLabelIntervalErrorText = "a doit être >=0 et b>=a ";
// suscripciones a observables
protected List<Subscription> subscriptions = new ArrayList<Subscription>();
// Inicio y fin de la ejecución
private long debut;
// mapeador jSON
private ObjectMapper jsonMapper;
// modelo de respuestas
private DefaultListModel<String> model;
// constructor
public JFrameAleasEvents() {
// padre
super();
// local
initJFrame();
// servicios
service = new Service();
rxService = new RxService(service);
// mapeador jSON
jsonMapper = new ObjectMapper();
}
private void initJFrame() {
// se ocultan los mensajes de error
jLabelCountError.setText("");
jLabelDelayError.setText("");
jLabelIntervalError.setText("");
jLabelNbValuesError.setText("");
// se ocultan los textos predeterminados
jTextFieldA.setText("100");
jTextFieldB.setText("200");
jTextFieldMinCount.setText("5");
jTextFieldMaxCount.setText("10");
jTextFieldMinDelay.setText("100");
jTextFieldMaxDelay.setText("500");
jTextFieldNbValeurs.setText("10");
jLabelDuree.setText("");
// modelo de respuestas
model = new DefaultListModel<>();
jListNumbers.setModel(model);
// número de núcleos
System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
}
public static void main(String args[]) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException
| IllegalAccessException e) {
System.out.println(e);
System.exit(0);
}
/* Crear y mostrar el formulario */
java.awt.EventQueue.invokeLater(() -> {
new JFrameAleasEvents().setVisible(true);
});
}
- línea 1: la clase [JFrameAleasEvents] extiende la clase [AbstractJFrameAleas], la cual a su vez extiende la clase Swing [JFrame]. Por lo tanto, la clase [JFrameAleasEvents] es una ventana Swing;
- líneas 68-75: el método [main] que se ejecutará;
- línea 70: establece el «look and feel» de la interfaz gráfica;
- línea 79: se invoca al constructor de la clase [JFrameAleasEvents]: la interfaz gráfica se construirá e inicializará. Una vez hecho esto, se hace visible;
- líneas 34-44: el constructor;
- línea 36: la llamada al constructor padre inicializará la interfaz gráfica de usuario. En ese momento, se ve tal como la diseñó el desarrollador. Aún no es visible;
- línea 38: se inicializan algunos componentes de la interfaz gráfica;
- línea 40: instanciación del servicio síncrono;
- línea 41: instanciación del servicio asíncrono;
8.8. Ejecución de consultas sincrónicas
Al hacer clic en el botón [Générer], se ejecuta el siguiente método [doGenerate]:
@Override
protected void doGenerate() {
// ¿Datos válidos?
if (!isPageValid()) {
return;
}
// ¿rx o no?
if (jCheckBoxRxSwing.isSelected()) {
// solicitudes asincrónicas
doGenerateWithRxService();
} else {
// solicitudes sincrónicas
doGenerateWithService();
}
}
- líneas 4-6: se verifica que las entradas del usuario sean válidas. No comentaremos el método [isPageValid]. Es básico;
- línea 8: se comprueba el estado de la casilla de selección RxSwing;
- línea 13: se ejecutan las consultas de manera sincrónica;
El método [doGenerateWithService] es el siguiente:
// generación sincrónica
private void doGenerateWithService() {
// Inicio de espera
beginWaiting();
try {
for (int i = 0; i < nbRequests; i++) {
// preparación de la respuesta
UiResponse uiResponse = new UiResponse();
// n.º de cliente
uiResponse.setIdClient(i);
// llamada sincrónica
uiResponse.setServiceResponse(service.getAleas(a, b, minCount, maxCount, minDelay, maxDelay));
// hora de respuesta
uiResponse.setResponseAt();
// actualización del modelo de JList con las respuestas recibidas
model.add(0, jsonMapper.writeValueAsString(uiResponse));
// actualización del número de respuestas
jLabelNbReponses.setText(String.valueOf(Integer.parseInt(jLabelNbReponses.getText()) + 1));
}
} catch (JsonProcessingException | RuntimeException e) {
JOptionPane.showMessageDialog(this, getInfoForThrowable("L'erreur suivante s'est produite", e), "Informations",
JOptionPane.PLAIN_MESSAGE);
}
// fin de espera
endWaiting();
}
- línea 12: llamada sincrónica al servicio de generación de números aleatorios;
- la ejecución del método [doGenerateWithService] se realiza íntegramente en el hilo del bucle de eventos de Swing. Mientras el método no haya finalizado, la interfaz gráfica no procesa ningún evento nuevo. Queda congelada (frozen). Así, por ejemplo, las actualizaciones de la interfaz gráfica de las líneas 16 y 18 nunca se verán. Solo serán visibles con sus valores finales, y esto ocurrirá al finalizar la ejecución de todas las consultas;
El método [beginWaiting] (línea 4) es el siguiente:
private void beginWaiting() {
// botones
jButtonGenerate.setVisible(false);
jButtonCancel.setVisible(true);
// cursor de espera
jTabbedPane1.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
jButtonCancel.setCursor(Cursor.getDefaultCursor());
// borrar respuestas
model.clear();
// suscripciones Rx
subscriptions.clear();
// se muestra la vista de respuestas
jTabbedPane1.setSelectedIndex(1);
jLabelNbReponses.setText("0");
jLabelDuree.setText("");
// inicio de ejecución
debut = new Date().getTime();
}
- línea 3: el botón [Générer] está oculto. Esto genera un evento que tampoco podrá ejecutarse hasta que finalice la ejecución de todas las consultas. Por lo tanto, nunca se ve oculto porque el método [endWaiting] de la línea 25 del método [doGenerateWithService] lo vuelve a mostrar;
- línea 13: se selecciona la pestaña [Response] para ver cómo llegan las respuestas. Una vez más, este evento solo se ejecutará al finalizar la ejecución de todas las consultas, momento en el que se verán todas las respuestas juntas, cuando lo que se deseaba era verlas llegar una tras otra;
La interfaz síncrona presenta claras deficiencias. Estas se superan gracias a la interfaz asíncrona.
8.9. Ejecución de consultas asincrónicas
El código para ejecutar las consultas asincrónicas es el siguiente:
private void doGenerateWithRxService() {
// inicio de la espera
beginWaiting();
// vamos a obtener los números aleatorios en forma de un observable
Observable<UiResponse> observable = Observable.empty();
// Programador de ejecución de los distintos observables
Scheduler[] schedulers = { Schedulers.io(), Schedulers.computation(), Schedulers.newThread(),
Schedulers.trampoline(), Schedulers.immediate() };
Scheduler scheduler = schedulers[jComboBoxSchedulers.getSelectedIndex()];
// Configuración de los observables
for (int i = 0; i < nbRequests; i++) {
// Preparación de la respuesta
UiResponse uiResponse = new UiResponse();
uiResponse.setIdClient(i);
// El observable está configurado para ejecutarse en el programador elegido por el usuario
// luego, se suma el observable obtenido al observable general
observable = observable.mergeWith(
rxService.getAleas(a, b, minCount, maxCount, minDelay, maxDelay, uiResponse).subscribeOn(scheduler));
}
// observador
observable = observable.observeOn(SwingScheduler.getInstance());
// Por el momento, solo se ha realizado la configuración
// aún no se ha realizado ninguna solicitud al servicio síncrono de generación de números aleatorios
// nos suscribimos al observable; esto es lo que provocará la llamada al servicio síncrono de generación de números aleatorios
try {
// aquí solo hay una suscripción; el resultado es una suscripción
subscriptions.add(observable.subscribe(
// notificación de emisión
uiResponse -> {
// se actualiza la interfaz de usuario con la respuesta
// Esto es posible porque la observación se realiza en el hilo de la interfaz de usuario
updateUi(uiResponse);
} ,
// notificación de error
th -> {
// caso de error: se muestra
String message = getInfoForThrowable("L'erreur suivante s'est produite", th);
JOptionPane.showMessageDialog(this, message, "Informations", JOptionPane.PLAIN_MESSAGE);
// cancelación de solicitudes
doCancel();
} ,
// notificación [onCompleted]
// fin de la espera
this::endWaiting));
} catch (Throwable th) {
// caso de excepción + general: se muestra
String message = getInfoForThrowable("L'erreur suivante s'est produite", th);
JOptionPane.showMessageDialog(this, message, "Informations", JOptionPane.PLAIN_MESSAGE);
// se cancelan las solicitudes
doCancel();
}
}
- línea 3: se modifica la interfaz gráfica para indicar que se está realizando una operación que podría tardar mucho tiempo;
- línea 5: se crea un observable vacío. Este observable será el que observe la capa [swing];
- línea 7: la tabla de programadores posibles;
- línea 9: le hemos dado al usuario la posibilidad de elegir el programador en el que se ejecutarán las consultas. Recuperamos el programador que haya elegido;
- líneas 11-19: cada una de las consultas devuelve un observable cuyos elementos se acumulan (mergeWith) (línea 17) en el observable de la línea 5;
- líneas 13-14: se crea el objeto [UiResponse]. Recordemos que este objeto es tanto el parámetro de entrada del método [RxService.getAleas] como su resultado (líneas 17-18);
- línea 14: cada solicitud se identifica con su número, denominado aquí [idClient]. Esto es necesario porque, en un entorno asíncrono, el orden de recepción de las respuestas puede diferir del orden de envío de las solicitudes. [idClient] permite saber a qué solicitud pertenece la respuesta;
- líneas 17-18: se realiza la solicitud asíncrona [rxService.getAleas]. Se ejecuta en el programador elegido por el usuario. Su resultado, de tipo Observable<UiResponse>, se acumula con el observable de la línea 5. Hay que tener muy en cuenta que el método [rxService.getAleas] se ejecuta aquí y devuelve un observable. Sin embargo, esto no significa que se hayan obtenido números aleatorios. De hecho, un observable solo se ejecuta cuando uno se suscribe a él. Este aún no es el caso;
- línea 21: esta es la instrucción importante: se solicita que la observación de los elementos emitidos por el observable de la línea 5 se realice en el hilo de la interfaz de usuario. Aquí se utiliza un programador propio de la biblioteca RxSwing;
- líneas 25-51: nos suscribimos al observable de la línea 5. Solo ahora se solicitarán los números aleatorios al servicio síncrono de generación de dichos números. Lo esencial se encuentra en las instrucciones de las líneas 29-33. El resto se dedica principalmente a manejar los casos de error y la notificación [onCompleted] del observable;
- líneas 28-44: hay que recordar que se solicitó observar el proceso de la línea 5 en el hilo de la interfaz de usuario. Por lo tanto, el código de las líneas 28-44 se ejecuta en el hilo de la interfaz de usuario;
- líneas 29-33: se procesa la notificación [onNext] del observable. Se recibe un tipo [UiResponse] emitido por el proceso observado. Este es el resultado de una de las solicitudes asincrónicas. Se actualiza la interfaz gráfica con esta respuesta;
- líneas 34-41: se procesa la notificación [onError] del observable. Se muestra un cuadro de diálogo con el error (líneas 37-38) y luego se cancelan las solicitudes (línea 40);
- líneas 42-44: se procesa la notificación [onCompleted] del observable. Se actualiza la interfaz gráfica para indicar que el servicio solicitado ha finalizado. La línea 44 también podría haberse escrito de la siguiente manera
Aquí se prefirió utilizar una referencia de método;
- líneas 45-51: algunas excepciones no pasan por las líneas 34-41. Este es el caso cuando se envían demasiadas solicitudes. Una vez superado cierto límite, que depende del entorno de trabajo en el momento de la ejecución, se genera un [StackOverflowError] que es interceptado por las líneas 45-51;
- línea 27: la suscripción genera un tipo [Subscription] que se agrega a una lista de suscripciones. Esta lista tendrá aquí un solo elemento;
Línea 32: se actualiza la interfaz gráfica con el siguiente método [updateUi]:
private void updateUi(UiResponse uiResponse) {
// tiempo de respuesta
uiResponse.setResponseAt();
// hilo de observación
uiResponse.setObservedOn();
// número de respuestas
jLabelNbReponses.setText(String.valueOf(Integer.parseInt(jLabelNbReponses.getText()) + 1));
// tiempo de ejecución
jLabelDuree.setText(String.valueOf(new Date().getTime() - debut));
// Agregación de la cadena jSON de la respuesta al modelo de respuestas JList
try {
model.add(0, jsonMapper.writeValueAsString(uiResponse));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
Aquí vemos que se actualizan componentes de la interfaz gráfica (líneas 7, 9, 12). Para que esto sea posible, es obligatorio estar en el hilo de la interfaz gráfica (bucle de eventos).
El método [endWaiting] es el siguiente:
private void endWaiting() {
// botón [Générer] visible
jButtonGenerate.setVisible(true);
// botón [Annuler] oculto
jButtonCancel.setVisible(false);
// cursor de espera oculto
jTabbedPane1.setCursor(Cursor.getDefaultCursor());
// pestaña de respuestas seleccionada
jTabbedPane1.setSelectedIndex(1);
// hora de la última actualización
jLabelDuree.setText(String.valueOf(new Date().getTime() - debut));
}
El método [doCancel] se invoca cuando se produce un error en la ejecución de las consultas asincrónicas o cuando el usuario hace clic en el botón [Annuler]. Su código es el siguiente:
// suscripciones a los observables
private List<Subscription> subscriptions = new ArrayList<Subscription>();
....
@Override
protected void doCancel() {
// fin de la espera
endWaiting();
// en el caso de suscripciones
if (jCheckBoxRxSwing.isSelected() && subscriptions != null) {
subscriptions.forEach(Subscription::unsubscribe);
//subscriptions.forEach(s -> s.unsubscribe());
}
}
- línea 2: [subscriptions] es una lista de una suscripción;
- línea 11: se cancelan todas las suscripciones;
- línea 12: otra versión de la línea 11. El método [forEach] espera aquí una instancia de tipo Consumer<Subscription> (véase el párrafo 4.4);
Volvamos al código del método [doGenerateWithService]: se puede dividir en dos etapas:
- etapa de configuración de los observables. Esto se realiza en el hilo del llamante del método [doGenerateWithService], es decir, el hilo de la interfaz de usuario;
- la suscripción que provocará la ejecución de los observables;
Si los observables tienen como programador a uno de los programadores [Schedulers.computation(), Scheduler.io(), Schedulers.newThread()], entonces se ejecutarán fuera del hilo de la interfaz de usuario. Estos diferentes hilos competirán por el núcleo o los núcleos de la máquina. Dado que las consultas son operaciones que tardan mucho tiempo (varios cientos de milisegundos), el método [doGenerateWithService] ejecutado en el hilo de la interfaz de usuario terminará antes de que las consultas hayan devuelto sus respuestas. Sin embargo, este método se había ejecutado al hacer clic en el botón [Générer]. Una vez procesado este evento, el hilo de la interfaz de usuario podrá pasar a procesar los siguientes eventos. Hay varios. Así, el método [beginWaiting] había establecido varios:
private void beginWaiting() {
// botones
jButtonGenerate.setVisible(false);
jButtonCancel.setVisible(true);
// cursor de espera
jTabbedPane1.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
jButtonCancel.setCursor(Cursor.getDefaultCursor());
// borrar respuestas
model.clear();
// suscripciones Rx
subscriptions.clear();
// se muestra la vista de respuestas
jTabbedPane1.setSelectedIndex(1);
jLabelNbReponses.setText("0");
jLabelDuree.setText("");
// inicio de ejecución
debut = new Date().getTime();
}
Prácticamente todas las líneas de este código tienen un efecto en la interfaz gráfica. Esta actualización no ocurre de inmediato: los eventos se colocan en la fila de espera del bucle de eventos. Una vez que se procesa el evento de clic en el botón [Générer], estos eventos se ejecutan a su vez y el usuario puede ver cómo cambia la interfaz gráfica:
- se muestra la pestaña [Response] (línea 13) y se le asigna un cursor de espera (línea 6)
- se muestra su botón [Annuler] (línea 4) y el usuario podrá hacer clic en él;
- el campo JList de las respuestas se vacía (línea 9);
- el JLabel del número de respuestas muestra 0;
- el JLabel de la duración de ejecución muestra una cadena vacía;
Durante todo el tiempo de ejecución de las consultas, el hilo del UI tiene acceso regular al procesador. De esta manera, puede procesar los eventos en espera. Entre estos, se encuentran los generados por el método [updateUi]:
private void updateUi(UiResponse uiResponse) {
// hora de respuesta
uiResponse.setResponseAt();
// hilo de observación
uiResponse.setObservedOn();
// número de respuestas
jLabelNbReponses.setText(String.valueOf(Integer.parseInt(jLabelNbReponses.getText()) + 1));
// tiempo de ejecución
jLabelDuree.setText(String.valueOf(new Date().getTime() - debut));
// agregación de la cadena jSON de la respuesta al modelo de JList de las respuestas
try {
model.add(0, jsonMapper.writeValueAsString(uiResponse));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
Cuando el hilo de la interfaz de usuario tiene el control:
- se actualiza el valor de JLabel correspondiente al número de respuestas (línea 7);
- se actualiza el JLabel de la duración de ejecución (línea 9);
- se actualiza el JList de las respuestas a través de su modelo (línea 12);
De esta manera, el usuario puede ver el avance de la ejecución de las consultas. Además, puede cancelarlas mediante el botón [Annuler]. Ahí radica todo el interés de contar con servicios asíncronos frente a la capa [swing], y RxJava es la tecnología ideal para implementarlos.
Para concluir, cabe señalar que si el usuario elige uno de los programadores [Schedulers.immediate(), Schedulers.trampoline()], los observables se ejecutan en el mismo hilo que el que realiza la llamada, es decir, el hilo de la interfaz de usuario. De este modo, se vuelve a un funcionamiento síncrono.
Los resultados obtenidos con los distintos programadores se han mostrado en los párrafos 2.8.1, 2.8.2, 2.8.3 y 2.8.4.








