You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: Spring4-SSE/README.MD
+31-34Lines changed: 31 additions & 34 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,24 +22,21 @@ La nueva versión de Spring 5 trae un mejor y más sencillo soporte para SSE, pe
22
22
23
23
Entre los métodos más importantes de la clase SseEmitter, podemos encontrar los siguientes:
24
24
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)
28
27
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)
31
30
32
-
// Se debe llamar cuando el proceso haya concluido.
33
-
complete()
31
+
// Se debe llamar cuando el proceso haya concluido.
32
+
complete()
34
33
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)
38
36
39
37
40
38
Un sencillo ejemplo sería el siguiente controller:
41
-
42
-
{% highlight java %}
39
+
```java
43
40
@RestController
44
41
publicclassSseController {
45
42
@GetMapping("/sse")
@@ -49,7 +46,7 @@ public class SseController {
49
46
return emitter;
50
47
}
51
48
}
52
-
{% endhighlight %}
49
+
```
53
50
54
51
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:
55
52
@@ -64,7 +61,7 @@ Sin embargo, debía crear una sencilla página que permitiera lanzar este y algu
64
61
65
62
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.
66
63
67
-
{% highlight java %}
64
+
```java
68
65
@Component
69
66
publicclassGenerarArchivo {
70
67
@@ -81,7 +78,7 @@ public class GenerarArchivo {
81
78
}
82
79
83
80
}
84
-
{% endhighlight %}
81
+
```
85
82
86
83
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
87
84
@@ -90,7 +87,7 @@ Para escribir en el archivo de texto, usamos [commons io](https://commons.apache
90
87
91
88
Para habilitar la ejecución de Scheduled, debemos agregar la anotación ``@EnableScheduling``
92
89
93
-
{% highlight java %}
90
+
```java
94
91
@SpringBootApplication
95
92
@EnableScheduling
96
93
publicclassSpring4SseApplication {
@@ -99,13 +96,13 @@ public class Spring4SseApplication {
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.
105
102
106
103
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.
107
104
108
-
{% highlight java %}
105
+
```java
109
106
@RestController
110
107
publicclassSseController {
111
108
@@ -121,7 +118,7 @@ public class SseController {
121
118
}
122
119
123
120
}
124
-
{% endhighlight %}
121
+
```
125
122
126
123
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,
127
124
@@ -131,7 +128,7 @@ Si ejecutamos nuevamente el proyecto con bootRun y accedemos desde el navegador
131
128
132
129
Para solucionar el problema anterior, deberemos agregar la anotación ``@Async`` a nuestro método
133
130
134
-
{% highlight java %}
131
+
```java
135
132
@Component
136
133
publicclassGenerarArchivo {
137
134
@@ -148,11 +145,11 @@ public class GenerarArchivo {
148
145
System.out.println("Finalizando escritura de archivo");
149
146
}
150
147
}
151
-
{% endhighlight %}
148
+
```
152
149
153
150
Finalmente agregar la anotación ``@EnableAsync``
154
151
155
-
{% highlight java %}
152
+
```java
156
153
@SpringBootApplication
157
154
@EnableScheduling
158
155
@EnableAsync
@@ -162,13 +159,13 @@ public class Spring4SseApplication {
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).
168
165
169
166
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,
@@ -177,13 +174,13 @@ public class WebMvcConfig extends WebMvcConfigurerAdapter {
177
174
configurer.setDefaultTimeout(1000000);
178
175
}
179
176
}
180
-
{% endhighlight %}
177
+
```
181
178
182
179
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).
183
180
184
181
Vamos a crear una clase que se encargue de ejecutar cualquier método que tenga la anotación ``@Scheduled``
185
182
186
-
{% highlight java %}
183
+
```java
187
184
@Component
188
185
publicclassJobExecutorimplementsIJobExecutor{
189
186
@@ -227,13 +224,13 @@ public class JobExecutor implements IJobExecutor{
227
224
228
225
}
229
226
}
230
-
{% endhighlight %}
227
+
```
231
228
232
229
``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.
233
230
234
231
El código del controller sufre modificaciones y ahora debería quedar más o menos como sigue:
235
232
236
-
{% highlight java %}
233
+
```java
237
234
@RestController
238
235
publicclassSseController {
239
236
@@ -283,14 +280,14 @@ public class SseController {
283
280
}
284
281
285
282
}
286
-
{% endhighlight %}
283
+
```
287
284
288
285
## Publicando el Evento
289
286
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.
290
287
291
288
Para ello, utilizaremos el mecanismo de eventos de Spring. Basta con modificar la clase GenerarArchivo y agregar un publicador de eventos ``ApplicationEventPublisher``
292
289
293
-
{% highlight java %}
290
+
```java
294
291
@Component
295
292
publicclassGenerarArchivo {
296
293
@@ -311,20 +308,20 @@ public class GenerarArchivo {
311
308
System.out.println("Finalizando escritura de archivo");
312
309
}
313
310
}
314
-
{% endhighlight %}
311
+
```
315
312
316
313
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``
0 commit comments