8. RxJava en el entorno Swing
8.1. Introduction
Aquí volveremos a la aplicación Swing presentada en el apartado 2.
![]() |
Para trabajar con RxJava en un entorno Swing, utilizaremos la biblioteca RxSwing, que añade a RxJava clases e interfaces útiles en un entorno Swing. Para ello, el archivo Gradle del ejemplo 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 propio de RxSwing: el programador [SwingScheduler.getInstance()], que ejecuta/observa los observables en el hilo del evento loop de Swing. Lo utilizaremos exclusivamente para observar observables que se ejecutan en hilos distintos al del evento loop. Recordemos la arquitectura de la aplicación de ejemplo:

- la capa de servicio asíncrono presenta métodos que devuelven observables. Ejecutamos estos observables en hilos diferentes al del evento loop. De este modo, la interfaz gráfica no se queda bloqueada. Puede reaccionar a las solicitudes 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 prolongue 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 asíncronas y, a partir de ellos, actualizar la interfaz gráfica. Sin embargo, esto solo puede hacerse en el hilo del evento loop. Para ello, estos resultados se observan en el programador [SwingScheduler.getInstance()];
Así, en el código de gestión de eventos de la interfaz gráfica, la interacción con la capa asíncrona [rxService] se realiza de la siguiente forma:
Observable obs=rxService.doSomething(...).subscribeOn(Schedulers.computation()).observeOn(SwingScheduler.getInstance()) ;
donde el programador [Schedulers.computation()] podrá sustituirse por otro programador según los casos de uso.
Se invita al lector a volver a leer el apartado 2. Ahora dispone de los conocimientos necesarios para comprenderlo por completo.
8.2. La estructura del código
El código implementa la siguiente arquitectura:

El proyecto 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, proceda de la siguiente manera:
![]() |
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 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;
}
// getters y setters
...
}
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 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 comprobaciones
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: añade 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 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 subprocesos de ejecución y observación del observable del servicio asíncrono, así como las horas de la solicitud realizada 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 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 va a 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. Por este motivo, este método llamante ha pasado el objeto [UiResponse] como parámetro al método [getAleas] (último parámetro, línea 17);
- línea 30: no hay que olvidar señalar el final de las emisiones. 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 ha creado con IDE [Netbeans], que cuenta con un buen editor gráfico. Este editor ha generado el archivo [AbstractJFrameAleas.form], que solo puede ser utilizado por este IDE;
- La clase [AbstractJFrameAleas] también se generó mediante el editor gráfico de Netbeans. Posteriormente se refactorizó de la siguiente manera: los eventos de la interfaz gráfica que se deseaba gestionar se tratan en la clase [AbstractJFrameAleas] mediante métodos abstractos implementados en la clase hija [JFrameAleasEvents]. Finalmente,
- 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 solicitudes se realizan a la interfaz asíncrona. De lo contrario, se realizan a la interfaz síncrona |
10 | JComboBox | jComboBoxSchedulers | En el caso de las solicitudes asíncronas, 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 solicitudes 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]. Se trata de 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;
// las entradas
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("");
// plantilla 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], que 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 va a ejecutar;
- línea 70: establece el aspecto de la interfaz gráfica;
- línea 79: se invoca al constructor de la clase [JFrameAleasEvents]: la interfaz gráfica se va a 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. En ese momento, es tal y como la ha diseñado 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 las 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 asíncronas
doGenerateWithRxService();
} else {
// solicitudes sincrónicas
doGenerateWithService();
}
}
- líneas 4-6: se comprueba 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 verificación RxSwing;
- línea 13: se ejecutan las consultas de forma 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));
// tiempo de respuesta
uiResponse.setResponseAt();
// actualización de la plantilla 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 la 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 evento loop 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 al final de 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 crea un evento que tampoco podrá ejecutarse hasta que hayan finalizado todas las consultas. Por lo tanto, nunca se ve oculto, ya que 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 llegar las respuestas. De nuevo, este evento solo se ejecutará al final de la ejecución de todas las consultas, momento en el que se verán todas las respuestas, cuando lo que se quería 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 asíncronas
El código de ejecución de las consultas asíncronas es el siguiente:
private void doGenerateWithRxService() {
// inicio de la espera
beginWaiting();
// se obtendrán los números aleatorios en forma de observable
Observable<UiResponse> observable = Observable.empty();
// Programador de ejecución de los diferentes 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
// y luego se suma el observable obtenido al observable global
observable = observable.mergeWith(
rxService.getAleas(a, b, minCount, maxCount, minDelay, maxDelay, uiResponse).subscribeOn(scheduler));
}
// observador
observable = observable.observeOn(SwingScheduler.getInstance());
// por ahora, 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 el Ui con la respuesta
// esto es posible porque la observación tiene lugar en el hilo del Ui
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 puede tardar bastante tiempo;
- línea 5: se crea un observable vacío. Este observable será el observado por la capa [swing];
- línea 7: la tabla de programadores posibles;
- línea 9: hemos dado al usuario la posibilidad de elegir el programador en el que ejecutar las consultas. Recuperamos el programador de su elección;
- 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 construye el objeto [UiResponse]. Recordemos que este objeto es a la vez parámetro de entrada del método [RxService.getAleas] y su resultado (líneas 17-18);
- línea 14: cada solicitud se identifica mediante su número, denominado aquí [idClient]. Esto es necesario porque, en un entorno asíncrono, el orden de recepción de las respuestas puede ser diferente del orden de emisión 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 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 Ui. 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 gestiona esencialmente los casos de error y la notificación [onCompleted] del observable;
- líneas 28-44: hay que recordar que se ha solicitado observar el proceso de la línea 5 en el hilo de Ui. Por lo tanto, el código de las líneas 28-44 se ejecuta en el hilo de Ui;
- líneas 29-33: se procesa la notificación [onNext] del observable. Se recibe un tipo [UiResponse] emitido por el proceso observado. Es el resultado de una de las solicitudes asíncronas. 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, a continuación, 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 ha preferido 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. Superado un 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 añade a una lista de suscripciones. Esta lista solo tendrá un elemento;
Línea 32: se actualiza la interfaz gráfica con el siguiente 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));
// Añadir 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();
}
}
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 Ui (evento loop).
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 asíncronas o cuando el usuario hace clic en el botón [Annuler]. Su código es el siguiente:
// suscripciones a observables
private List<Subscription> subscriptions = new ArrayList<Subscription>();
....
@Override
protected void doCancel() {
// fin de la espera
endWaiting();
// en 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 escritura de la línea 11. El método [forEach] espera aquí una instancia de tipo Consumer<Subscription> (véase el apartado 4.4);
Volvamos al código del método [doGenerateWithService]: se puede descomponer 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 del Ui;
- la suscripción que provocará la ejecución de los observables;
Si los observables tienen como programador uno de los programadores [Schedulers.computation(), Scheduler.io(), Schedulers.newThread()], se ejecutarán fuera del hilo de Ui. Estos diferentes hilos competirán por el núcleo o los núcleos de la máquina. Dado que las consultas son operaciones largas (varios cientos de milisegundos), el método [doGenerateWithService] ejecutado en el hilo de Ui finalizará antes de que las consultas hayan devuelto sus respuestas. Ahora bien, 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 Ui podrá pasar al procesamiento de 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 se produce de forma inmediata: los eventos se colocan en la cola del evento loop. Una vez procesado 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 asocia 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 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 la ejecución muestra una cadena vacía;
Durante todo el tiempo de ejecución de las consultas, el subproceso del UI tiene acceso regular al procesador. De este modo, puede procesar los eventos en espera. Entre ellos se encuentran los establecidos 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));
// duración de la ejecución
jLabelDuree.setText(String.valueOf(new Date().getTime() - debut));
// adició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 subproceso de Ui tiene la palabra:
- se actualiza el JLabel del número de respuestas (línea 7);
- se actualiza el JLabel del tiempo de ejecución (línea 9);
- el JList de las respuestas se actualiza a través de su modelo (línea 12);
De este modo, el usuario ve el progreso 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 una tecnología ideal para implementarlos.
Para terminar, cabe señalar que si el usuario elige uno de los programadores [Schedulers.immediate(), Schedulers.trampoline()], los observables se ejecutan entonces en el mismo hilo que el llamante, es decir, el hilo del Ui. Se vuelve entonces a un funcionamiento síncrono.
Los resultados obtenidos con los diferentes programadores se han mostrado en los apartados 2.8.1, 2.8.2, 2.8.3 y 2.8.4.








