Skip to content

Commit 41363cc

Browse files
authored
Update README.MD
1 parent a3d2c39 commit 41363cc

File tree

1 file changed

+31
-34
lines changed

1 file changed

+31
-34
lines changed

Spring4-SSE/README.MD

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,21 @@ La nueva versión de Spring 5 trae un mejor y más sencillo soporte para SSE, pe
2222

2323
Entre los métodos más importantes de la clase SseEmitter, podemos encontrar los siguientes:
2424

25-
{% highlight java %}
26-
// Permite enviar un mensaje al cliente, normalmente el navegador.
27-
send(java.lang.Object object)
25+
// Permite enviar un mensaje al cliente, normalmente el navegador.
26+
send(java.lang.Object object)
2827

29-
// Permite registrar el código que se ejecutará una vez que el proceso asíncrono concluya.
30-
onCompletion(java.lang.Runnable callback)
28+
// Permite registrar el código que se ejecutará una vez que el proceso asíncrono concluya.
29+
onCompletion(java.lang.Runnable callback)
3130

32-
// Se debe llamar cuando el proceso haya concluido.
33-
complete()
31+
// Se debe llamar cuando el proceso haya concluido.
32+
complete()
3433

35-
// Permite registrar un error cuando el proceso haya lanzado una excepción.
36-
completeWithError(java.lang.Throwable ex)
37-
{% endhighlight %}
34+
// Permite registrar un error cuando el proceso haya lanzado una excepción.
35+
completeWithError(java.lang.Throwable ex)
3836

3937

4038
Un sencillo ejemplo sería el siguiente controller:
41-
42-
{% highlight java %}
39+
```java
4340
@RestController
4441
public class SseController {
4542
@GetMapping("/sse")
@@ -49,7 +46,7 @@ public class SseController {
4946
return emitter;
5047
}
5148
}
52-
{% endhighlight %}
49+
```
5350

5451
Si ejecutamos el proyecto con bootRun y desde el navegador ingresamos a [http://localhost:8080/sse](http://localhost:8080/sse) podemos ver la siguiente salida:
5552

@@ -64,7 +61,7 @@ Sin embargo, debía crear una sencilla página que permitiera lanzar este y algu
6461

6562
Primero creamos la clase que se va a encargar de crear el archivo de texto. Esta misma clase será la que esté anotada con ``@Scheduled`` y se ejecute automáticamente.
6663

67-
{% highlight java %}
64+
```java
6865
@Component
6966
public class GenerarArchivo {
7067

@@ -81,7 +78,7 @@ public class GenerarArchivo {
8178
}
8279

8380
}
84-
{% endhighlight %}
81+
```
8582

8683
Para escribir en el archivo de texto, usamos [commons io](https://commons.apache.org/proper/commons-io/), así que se agrega la dependencia al build.gradle
8784

@@ -90,7 +87,7 @@ Para escribir en el archivo de texto, usamos [commons io](https://commons.apache
9087

9188
Para habilitar la ejecución de Scheduled, debemos agregar la anotación ``@EnableScheduling``
9289

93-
{% highlight java %}
90+
```java
9491
@SpringBootApplication
9592
@EnableScheduling
9693
public class Spring4SseApplication {
@@ -99,13 +96,13 @@ public class Spring4SseApplication {
9996
SpringApplication.run(Spring4SseApplication.class, args);
10097
}
10198
}
102-
{% endhighlight %}
99+
```
103100

104101
Al correr el proyecto con la tarea bootRun, podemos ver que nuestra tarea inicia su ejecución algunos segundos después y genera un archivo de texto en la ruta indicada.
105102

106103
Sin embargo, como mencioné, la intensión es tener la posibilidad de ejecutar este proceso de forma manual, por lo que debemos crear un controlador REST que podamos invocar desde una página web.
107104

108-
{% highlight java %}
105+
```java
109106
@RestController
110107
public class SseController {
111108

@@ -121,7 +118,7 @@ public class SseController {
121118
}
122119

123120
}
124-
{% endhighlight %}
121+
```
125122

126123
Finalmente para ejecutar la tarea desde el navegador, vamos a cambiar la calendarización de nuestro job para que se ejecute cada 5 minutos y no cada 15 segundos como lo teniamos,
127124

@@ -131,7 +128,7 @@ Si ejecutamos nuevamente el proyecto con bootRun y accedemos desde el navegador
131128

132129
Para solucionar el problema anterior, deberemos agregar la anotación ``@Async`` a nuestro método
133130

134-
{% highlight java %}
131+
```java
135132
@Component
136133
public class GenerarArchivo {
137134

@@ -148,11 +145,11 @@ public class GenerarArchivo {
148145
System.out.println("Finalizando escritura de archivo");
149146
}
150147
}
151-
{% endhighlight %}
148+
```
152149

153150
Finalmente agregar la anotación ``@EnableAsync``
154151

155-
{% highlight java %}
152+
```java
156153
@SpringBootApplication
157154
@EnableScheduling
158155
@EnableAsync
@@ -162,13 +159,13 @@ public class Spring4SseApplication {
162159
SpringApplication.run(Spring4SseApplication.class, args);
163160
}
164161
}
165-
{% endhighlight %}
162+
```
166163

167164
Esto permitirá ejecutar al método ``generar()`` en un hilo separado. Puedes ver un tutorial sobre procesos asíncronos en este otro [tutorial que escribí](https://windoctor7.github.io/Tareas-asincronas-Spring.html).
168165

169166
Si corremos de nueva cuenta el proyecto y accedemos desde el navegador al endpoint **/execute** vemos que la respuesta ahora es inmediata, sin embargo después de algunos segundos obtenremos un error de timeout en el log de la aplicación. Para establecer un timeout a los procesos asíncronos de nuestra aplicación, debemos agregar la siguiente clase,
170167

171-
{% highlight java %}
168+
```java
172169
@Configuration
173170
public class WebMvcConfig extends WebMvcConfigurerAdapter {
174171

@@ -177,13 +174,13 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter {
177174
configurer.setDefaultTimeout(1000000);
178175
}
179176
}
180-
{% endhighlight %}
177+
```
181178

182179
Ya está!... Bueno, claramente el diseño del controller no es bueno, además tenemos el problema que solo podemos ejecutar un método en específico. Que pasaría si queremos diseñar un front con una lista de Jobs a ejecutar? ¿Tendríamos que crear un endpoint para cada job?... La solución es usar el [API reflection de Java](http://java-white-box.blogspot.mx/2016/02/api-reflect-que-es-reflexion-por-donde.html).
183180

184181
Vamos a crear una clase que se encargue de ejecutar cualquier método que tenga la anotación ``@Scheduled``
185182

186-
{% highlight java %}
183+
```java
187184
@Component
188185
public class JobExecutor implements IJobExecutor{
189186

@@ -227,13 +224,13 @@ public class JobExecutor implements IJobExecutor{
227224

228225
}
229226
}
230-
{% endhighlight %}
227+
```
231228

232229
``EMITTERS`` será un [ConcurrentHashMap](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentHashMap.html) donde guardaremos objetos SseEmitter. Dado que nuestro Job escribe en un archivo de texto, es buena idea validar si el Map EMITTERS ya contiene el job ``GenerarArchivo`` y en caso de contenerlo devolver el mensaje.
233230

234231
El código del controller sufre modificaciones y ahora debería quedar más o menos como sigue:
235232

236-
{% highlight java %}
233+
```java
237234
@RestController
238235
public class SseController {
239236

@@ -283,14 +280,14 @@ public class SseController {
283280
}
284281

285282
}
286-
{% endhighlight %}
283+
```
287284

288285
## Publicando el Evento
289286
Estamos casi listos para probar nuestro ejemplo, sin embargo, falta algo muy importante y es notificar al navegador cuando la ejecución del método ``generar()`` concluya.
290287

291288
Para ello, utilizaremos el mecanismo de eventos de Spring. Basta con modificar la clase GenerarArchivo y agregar un publicador de eventos ``ApplicationEventPublisher``
292289

293-
{% highlight java %}
290+
```java
294291
@Component
295292
public class GenerarArchivo {
296293

@@ -311,20 +308,20 @@ public class GenerarArchivo {
311308
System.out.println("Finalizando escritura de archivo");
312309
}
313310
}
314-
{% endhighlight %}
311+
```
315312

316313
A partir de la versión 4.2 de Spring, la publicación de eventos resulta muy sencilla, basta con agregar a nuestra clase ``JobExecutor`` un método anotado con ``@EventListener``
317314

318-
{% highlight java %}
315+
```java
319316
@EventListener
320317
public void eventListener(MensajeEvent mensajeEvent) throws IOException {
321318
String bean = mensajeEvent.getBeanClass();
322319
SseEmitter emitter = JobExecutor.EMITTERS.get(bean);
323320
String msg = String.format("El Job %s ha finalizado correctamente", bean);
324321
emitter.send(new Mensaje(2,msg));
325322
emitter.complete();
326-
}
327-
{% endhighlight %}
323+
   }
324+
```
328325

329326
Con lo anterior, cuando el método ``generar()`` concluya publicaremos el evento con la línea
330327

0 commit comments

Comments
 (0)