Skip to content

Commit 529e629

Browse files
committed
Refactor Servlet 3 async support
As a result of the refactoring, the AsyncContext dispatch mechanism is used much more centrally. Effectively every asynchronously processed request involves one initial (container) thread, a second thread to produce the handler return value asynchronously, and a third thread as a result of a dispatch back to the container to resume processing of the asynchronous resuilt. Other updates include the addition of a MockAsyncContext and support of related request method in the test packages of spring-web and spring-webmvc. Also an upgrade of a Jetty test dependency required to make tests pass. Issue: SPR-9433
1 parent 026ee84 commit 529e629

File tree

47 files changed

+1825
-1729
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1825
-1729
lines changed

build.gradle

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,13 @@ project('spring-web') {
366366
compile("org.codehaus.jackson:jackson-mapper-asl:1.4.2", optional)
367367
compile("com.fasterxml.jackson.core:jackson-databind:2.0.1", optional)
368368
compile("taglibs:standard:1.1.2", optional)
369-
compile("org.mortbay.jetty:jetty:6.1.9") { dep ->
369+
compile("org.eclipse.jetty:jetty-servlet:8.1.5.v20120716") { dep ->
370370
optional dep
371-
exclude group: 'org.mortbay.jetty', module: 'servlet-api-2.5'
371+
exclude group: 'org.eclipse.jetty.orbit', module: 'javax.servlet'
372+
}
373+
compile("org.eclipse.jetty:jetty-server:8.1.5.v20120716") { dep ->
374+
optional dep
375+
exclude group: 'org.eclipse.jetty.orbit', module: 'javax.servlet'
372376
}
373377
testCompile project(":spring-context-support") // for JafMediaTypeFactory
374378
testCompile "xmlunit:xmlunit:1.2"

spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.orm.hibernate3.support;
1818

1919
import java.io.IOException;
20+
2021
import javax.servlet.FilterChain;
2122
import javax.servlet.ServletException;
2223
import javax.servlet.http.HttpServletRequest;
@@ -25,14 +26,15 @@
2526
import org.hibernate.FlushMode;
2627
import org.hibernate.Session;
2728
import org.hibernate.SessionFactory;
28-
2929
import org.springframework.dao.DataAccessResourceFailureException;
3030
import org.springframework.orm.hibernate3.SessionFactoryUtils;
3131
import org.springframework.orm.hibernate3.SessionHolder;
3232
import org.springframework.transaction.support.TransactionSynchronizationManager;
33+
import org.springframework.util.Assert;
3334
import org.springframework.web.context.WebApplicationContext;
34-
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
35-
import org.springframework.web.context.request.async.AsyncExecutionChain;
35+
import org.springframework.web.context.request.async.AsyncWebUtils;
36+
import org.springframework.web.context.request.async.WebAsyncManager;
37+
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
3638
import org.springframework.web.context.support.WebApplicationContextUtils;
3739
import org.springframework.web.filter.OncePerRequestFilter;
3840

@@ -165,33 +167,48 @@ protected FlushMode getFlushMode() {
165167
}
166168

167169

170+
/**
171+
* The default value is "true" so that the filter may re-bind the opened
172+
* {@code Session} to each asynchronously dispatched thread and postpone
173+
* closing it until the very last asynchronous dispatch.
174+
*/
175+
@Override
176+
protected boolean shouldFilterAsyncDispatches() {
177+
return true;
178+
}
179+
168180
@Override
169181
protected void doFilterInternal(
170182
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
171183
throws ServletException, IOException {
172184

173-
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
174-
175185
SessionFactory sessionFactory = lookupSessionFactory(request);
176186
boolean participate = false;
177187

188+
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
189+
String key = getAlreadyFilteredAttributeName();
190+
178191
if (isSingleSession()) {
179192
// single session mode
180193
if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
181194
// Do not modify the Session: just set the participate flag.
182195
participate = true;
183196
}
184197
else {
185-
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
186-
Session session = getSession(sessionFactory);
187-
SessionHolder sessionHolder = new SessionHolder(session);
188-
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
198+
if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) {
199+
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
200+
Session session = getSession(sessionFactory);
201+
SessionHolder sessionHolder = new SessionHolder(session);
202+
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
189203

190-
chain.push(getAsyncCallable(request, sessionFactory, sessionHolder));
204+
AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
205+
asyncManager.registerAsyncThreadInitializer(key, initializer);
206+
}
191207
}
192208
}
193209
else {
194210
// deferred close mode
211+
Assert.state(isLastRequestThread(request), "Deferred close mode is not supported on async dispatches");
195212
if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
196213
// Do not modify deferred close: just set the participate flag.
197214
participate = true;
@@ -210,23 +227,32 @@ protected void doFilterInternal(
210227
// single session mode
211228
SessionHolder sessionHolder =
212229
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
213-
if (!chain.pop()) {
214-
return;
230+
if (isLastRequestThread(request)) {
231+
logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
232+
closeSession(sessionHolder.getSession(), sessionFactory);
215233
}
216-
logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
217-
closeSession(sessionHolder.getSession(), sessionFactory);
218234
}
219235
else {
220-
if (chain.isAsyncStarted()) {
221-
throw new IllegalStateException("Deferred close is not supported with async requests.");
222-
}
223236
// deferred close mode
224237
SessionFactoryUtils.processDeferredClose(sessionFactory);
225238
}
226239
}
227240
}
228241
}
229242

243+
private AsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
244+
final SessionHolder sessionHolder) {
245+
246+
return new AsyncThreadInitializer() {
247+
public void initialize() {
248+
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
249+
}
250+
public void reset() {
251+
TransactionSynchronizationManager.unbindResource(sessionFactory);
252+
}
253+
};
254+
}
255+
230256
/**
231257
* Look up the SessionFactory that this filter should use,
232258
* taking the current HTTP request as argument.
@@ -291,28 +317,4 @@ protected void closeSession(Session session, SessionFactory sessionFactory) {
291317
SessionFactoryUtils.closeSession(session);
292318
}
293319

294-
/**
295-
* Create a Callable to extend the use of the open Hibernate Session to the
296-
* async thread completing the request.
297-
*/
298-
private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request,
299-
final SessionFactory sessionFactory, final SessionHolder sessionHolder) {
300-
301-
return new AbstractDelegatingCallable() {
302-
public Object call() throws Exception {
303-
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
304-
try {
305-
getNext().call();
306-
}
307-
finally {
308-
SessionHolder sessionHolder =
309-
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
310-
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
311-
SessionFactoryUtils.closeSession(sessionHolder.getSession());
312-
}
313-
return null;
314-
}
315-
};
316-
}
317-
318320
}

spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import org.springframework.transaction.support.TransactionSynchronizationManager;
2626
import org.springframework.ui.ModelMap;
2727
import org.springframework.web.context.request.WebRequest;
28-
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
2928
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
29+
import org.springframework.web.context.request.async.AsyncWebUtils;
30+
import org.springframework.web.context.request.async.WebAsyncManager;
31+
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
3032

3133
/**
3234
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
@@ -140,10 +142,19 @@ protected boolean isSingleSession() {
140142
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
141143
*/
142144
public void preHandle(WebRequest request) throws DataAccessException {
145+
146+
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
147+
String participateAttributeName = getParticipateAttributeName();
148+
149+
if (asyncManager.hasConcurrentResult()) {
150+
if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) {
151+
return;
152+
}
153+
}
154+
143155
if ((isSingleSession() && TransactionSynchronizationManager.hasResource(getSessionFactory())) ||
144156
SessionFactoryUtils.isDeferredCloseActive(getSessionFactory())) {
145157
// Do not modify the Session: just mark the request accordingly.
146-
String participateAttributeName = getParticipateAttributeName();
147158
Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
148159
int newCount = (count != null ? count + 1 : 1);
149160
request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST);
@@ -157,6 +168,9 @@ public void preHandle(WebRequest request) throws DataAccessException {
157168
applyFlushMode(session, false);
158169
SessionHolder sessionHolder = new SessionHolder(session);
159170
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
171+
172+
AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
173+
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
160174
}
161175
else {
162176
// deferred close mode
@@ -165,44 +179,6 @@ public void preHandle(WebRequest request) throws DataAccessException {
165179
}
166180
}
167181

168-
/**
169-
* Create a <code>Callable</code> to bind the <code>Hibernate</code> session
170-
* to the async request thread.
171-
*/
172-
public AbstractDelegatingCallable getAsyncCallable(WebRequest request) {
173-
String attributeName = getParticipateAttributeName();
174-
if ((request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) != null) || !isSingleSession()) {
175-
return null;
176-
}
177-
178-
final SessionHolder sessionHolder =
179-
(SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
180-
181-
return new AbstractDelegatingCallable() {
182-
public Object call() throws Exception {
183-
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
184-
getNext().call();
185-
return null;
186-
}
187-
};
188-
}
189-
190-
/**
191-
* Unbind the Hibernate <code>Session</code> from the main thread but leave
192-
* the <code>Session</code> open for further use from the async thread.
193-
*/
194-
public void postHandleAsyncStarted(WebRequest request) {
195-
String attributeName = getParticipateAttributeName();
196-
if (request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) == null) {
197-
if (isSingleSession()) {
198-
TransactionSynchronizationManager.unbindResource(getSessionFactory());
199-
}
200-
else {
201-
throw new IllegalStateException("Deferred close is not supported with async requests.");
202-
}
203-
}
204-
}
205-
206182
/**
207183
* Flush the Hibernate <code>Session</code> before view rendering, if necessary.
208184
* <p>Note that this just applies in {@link #isSingleSession() single session mode}!
@@ -232,18 +208,7 @@ public void postHandle(WebRequest request, ModelMap model) throws DataAccessExce
232208
* @see org.springframework.transaction.support.TransactionSynchronizationManager
233209
*/
234210
public void afterCompletion(WebRequest request, Exception ex) throws DataAccessException {
235-
String participateAttributeName = getParticipateAttributeName();
236-
Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
237-
if (count != null) {
238-
// Do not modify the Session: just clear the marker.
239-
if (count > 1) {
240-
request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST);
241-
}
242-
else {
243-
request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
244-
}
245-
}
246-
else {
211+
if (!decrementParticipateCount(request)) {
247212
if (isSingleSession()) {
248213
// single session mode
249214
SessionHolder sessionHolder =
@@ -258,6 +223,34 @@ public void afterCompletion(WebRequest request, Exception ex) throws DataAccessE
258223
}
259224
}
260225

226+
public void afterConcurrentHandlingStarted(WebRequest request) {
227+
if (!decrementParticipateCount(request)) {
228+
if (isSingleSession()) {
229+
TransactionSynchronizationManager.unbindResource(getSessionFactory());
230+
}
231+
else {
232+
throw new IllegalStateException("Deferred close mode is not supported with async requests.");
233+
}
234+
235+
}
236+
}
237+
238+
private boolean decrementParticipateCount(WebRequest request) {
239+
String participateAttributeName = getParticipateAttributeName();
240+
Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
241+
if (count == null) {
242+
return false;
243+
}
244+
// Do not modify the Session: just clear the marker.
245+
if (count > 1) {
246+
request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST);
247+
}
248+
else {
249+
request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
250+
}
251+
return true;
252+
}
253+
261254
/**
262255
* Return the name of the request attribute that identifies that a request is
263256
* already intercepted.
@@ -268,4 +261,15 @@ protected String getParticipateAttributeName() {
268261
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
269262
}
270263

264+
private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
265+
return new AsyncThreadInitializer() {
266+
public void initialize() {
267+
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
268+
}
269+
public void reset() {
270+
TransactionSynchronizationManager.unbindResource(getSessionFactory());
271+
}
272+
};
273+
}
274+
271275
}

0 commit comments

Comments
 (0)