17
17
package org .springframework .web .servlet .mvc .method .annotation ;
18
18
19
19
import java .io .IOException ;
20
- import java .util .LinkedHashMap ;
21
- import java .util .Map ;
20
+ import java .util .LinkedHashSet ;
21
+ import java .util .Set ;
22
22
23
23
import org .springframework .http .MediaType ;
24
24
import org .springframework .http .server .ServerHttpResponse ;
54
54
* </pre>
55
55
*
56
56
* @author Rossen Stoyanchev
57
+ * @author Juergen Hoeller
57
58
* @since 4.2
58
59
*/
59
60
public class ResponseBodyEmitter {
60
61
61
62
private final Long timeout ;
62
63
63
- private volatile Handler handler ;
64
+ private final Set < DataWithMediaType > earlySendAttempts = new LinkedHashSet < DataWithMediaType >( 8 ) ;
64
65
65
- /* Cache for objects sent before handler is set. */
66
- private final Map <Object , MediaType > initHandlerCache = new LinkedHashMap <Object , MediaType >(10 );
66
+ private Handler handler ;
67
67
68
- private volatile boolean complete ;
68
+ private boolean complete ;
69
69
70
70
private Throwable failure ;
71
71
@@ -110,32 +110,29 @@ public Long getTimeout() {
110
110
protected void extendResponse (ServerHttpResponse outputMessage ) {
111
111
}
112
112
113
- void initialize (Handler handler ) throws IOException {
114
- synchronized (this ) {
115
- this .handler = handler ;
116
- for (Map .Entry <Object , MediaType > entry : this .initHandlerCache .entrySet ()) {
117
- try {
118
- sendInternal (entry .getKey (), entry .getValue ());
119
- }
120
- catch (Throwable ex ) {
121
- return ;
122
- }
123
- }
124
- if (this .complete ) {
125
- if (this .failure != null ) {
126
- this .handler .completeWithError (this .failure );
127
- }
128
- else {
129
- this .handler .complete ();
130
- }
131
- }
132
- if (this .timeoutCallback != null ) {
133
- this .handler .onTimeout (this .timeoutCallback );
113
+ synchronized void initialize (Handler handler ) throws IOException {
114
+ this .handler = handler ;
115
+
116
+ for (DataWithMediaType sendAttempt : this .earlySendAttempts ) {
117
+ sendInternal (sendAttempt .getData (), sendAttempt .getMediaType ());
118
+ }
119
+ this .earlySendAttempts .clear ();
120
+
121
+ if (this .complete ) {
122
+ if (this .failure != null ) {
123
+ this .handler .completeWithError (this .failure );
134
124
}
135
- if ( this . completionCallback != null ) {
136
- this .handler .onCompletion ( this . completionCallback );
125
+ else {
126
+ this .handler .complete ( );
137
127
}
138
128
}
129
+
130
+ if (this .timeoutCallback != null ) {
131
+ this .handler .onTimeout (this .timeoutCallback );
132
+ }
133
+ if (this .completionCallback != null ) {
134
+ this .handler .onCompletion (this .completionCallback );
135
+ }
139
136
}
140
137
141
138
/**
@@ -159,33 +156,29 @@ public void send(Object object) throws IOException {
159
156
* @throws IOException raised when an I/O error occurs
160
157
* @throws java.lang.IllegalStateException wraps any other errors
161
158
*/
162
- public void send (Object object , MediaType mediaType ) throws IOException {
159
+ public synchronized void send (Object object , MediaType mediaType ) throws IOException {
163
160
Assert .state (!this .complete , "ResponseBodyEmitter is already set complete" );
164
161
sendInternal (object , mediaType );
165
162
}
166
163
167
164
private void sendInternal (Object object , MediaType mediaType ) throws IOException {
168
- if (object == null ) {
169
- return ;
170
- }
171
- if (this .handler == null ) {
172
- synchronized (this ) {
173
- if (this .handler == null ) {
174
- this .initHandlerCache .put (object , mediaType );
175
- return ;
165
+ if (object != null ) {
166
+ if (this .handler != null ) {
167
+ try {
168
+ this .handler .send (object , mediaType );
169
+ }
170
+ catch (IOException ex ) {
171
+ this .handler .completeWithError (ex );
172
+ throw ex ;
173
+ }
174
+ catch (Throwable ex ) {
175
+ this .handler .completeWithError (ex );
176
+ throw new IllegalStateException ("Failed to send " + object , ex );
176
177
}
177
178
}
178
- }
179
- try {
180
- this .handler .send (object , mediaType );
181
- }
182
- catch (IOException ex ){
183
- this .handler .completeWithError (ex );
184
- throw ex ;
185
- }
186
- catch (Throwable ex ){
187
- this .handler .completeWithError (ex );
188
- throw new IllegalStateException ("Failed to send " + object , ex );
179
+ else {
180
+ this .earlySendAttempts .add (new DataWithMediaType (object , mediaType ));
181
+ }
189
182
}
190
183
}
191
184
@@ -194,12 +187,10 @@ private void sendInternal(Object object, MediaType mediaType) throws IOException
194
187
* <p>A dispatch is made into the app server where Spring MVC completes
195
188
* asynchronous request processing.
196
189
*/
197
- public void complete () {
198
- synchronized (this ) {
199
- this .complete = true ;
200
- if (this .handler != null ) {
201
- this .handler .complete ();
202
- }
190
+ public synchronized void complete () {
191
+ this .complete = true ;
192
+ if (this .handler != null ) {
193
+ this .handler .complete ();
203
194
}
204
195
}
205
196
@@ -208,26 +199,22 @@ public void complete() {
208
199
* <p>A dispatch is made into the app server where Spring MVC will pass the
209
200
* exception through its exception handling mechanism.
210
201
*/
211
- public void completeWithError (Throwable ex ) {
212
- synchronized (this ) {
213
- this .complete = true ;
214
- this .failure = ex ;
215
- if (this .handler != null ) {
216
- this .handler .completeWithError (ex );
217
- }
202
+ public synchronized void completeWithError (Throwable ex ) {
203
+ this .complete = true ;
204
+ this .failure = ex ;
205
+ if (this .handler != null ) {
206
+ this .handler .completeWithError (ex );
218
207
}
219
208
}
220
209
221
210
/**
222
211
* Register code to invoke when the async request times out. This method is
223
212
* called from a container thread when an async request times out.
224
213
*/
225
- public void onTimeout (Runnable callback ) {
226
- synchronized (this ) {
227
- this .timeoutCallback = callback ;
228
- if (this .handler != null ) {
229
- this .handler .onTimeout (callback );
230
- }
214
+ public synchronized void onTimeout (Runnable callback ) {
215
+ this .timeoutCallback = callback ;
216
+ if (this .handler != null ) {
217
+ this .handler .onTimeout (callback );
231
218
}
232
219
}
233
220
@@ -237,12 +224,10 @@ public void onTimeout(Runnable callback) {
237
224
* reason including timeout and network error. This method is useful for
238
225
* detecting that a {@code ResponseBodyEmitter} instance is no longer usable.
239
226
*/
240
- public void onCompletion (Runnable callback ) {
241
- synchronized (this ) {
242
- this .completionCallback = callback ;
243
- if (this .handler != null ) {
244
- this .handler .onCompletion (callback );
245
- }
227
+ public synchronized void onCompletion (Runnable callback ) {
228
+ this .completionCallback = callback ;
229
+ if (this .handler != null ) {
230
+ this .handler .onCompletion (callback );
246
231
}
247
232
}
248
233
@@ -263,4 +248,28 @@ interface Handler {
263
248
void onCompletion (Runnable callback );
264
249
}
265
250
251
+
252
+ /**
253
+ * Simple struct for a data entry.
254
+ */
255
+ static class DataWithMediaType {
256
+
257
+ private final Object data ;
258
+
259
+ private final MediaType mediaType ;
260
+
261
+ public DataWithMediaType (Object data , MediaType mediaType ) {
262
+ this .data = data ;
263
+ this .mediaType = mediaType ;
264
+ }
265
+
266
+ public Object getData () {
267
+ return this .data ;
268
+ }
269
+
270
+ public MediaType getMediaType () {
271
+ return this .mediaType ;
272
+ }
273
+ }
274
+
266
275
}
0 commit comments