Browse Source

Merge pull request #73 from rstoyanchev/mvc-async

HanderInterceptor and OSIV async request changes
pull/75/merge
Rossen Stoyanchev 13 years ago
parent
commit
158b3c6af8
  1. 38
      spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.java
  2. 44
      spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java
  3. 38
      spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.java
  4. 45
      spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java
  5. 769
      spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java
  6. 16
      spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java
  7. 73
      spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java
  8. 82
      spring-webmvc/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java
  9. 3
      spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
  10. 50
      spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java
  11. 25
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java
  12. 60
      spring-webmvc/src/test/java/org/springframework/web/servlet/HandlerExecutionChainTests.java

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,6 +31,8 @@ import org.springframework.orm.hibernate3.SessionFactoryUtils; @@ -31,6 +31,8 @@ import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@ -168,6 +170,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { @@ -168,6 +170,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
SessionFactory sessionFactory = lookupSessionFactory(request);
boolean participate = false;
@ -180,7 +184,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { @@ -180,7 +184,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
else {
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
Session session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
chain.addDelegatingCallable(getAsyncCallable(request, sessionFactory, sessionHolder));
}
}
else {
@ -204,6 +211,9 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { @@ -204,6 +211,9 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
// single session mode
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
if (chain.isAsyncStarted()) {
return;
}
logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
closeSession(sessionHolder.getSession(), sessionFactory);
}
@ -279,4 +289,28 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { @@ -279,4 +289,28 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
SessionFactoryUtils.closeSession(session);
}
/**
* Create a Callable to extend the use of the open Hibernate Session to the
* async thread completing the request.
*/
private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request,
final SessionFactory sessionFactory, final SessionHolder sessionHolder) {
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
try {
getNextCallable().call();
}
finally {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
return null;
}
};
}
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package org.springframework.orm.hibernate3.support; @@ -18,7 +18,6 @@ package org.springframework.orm.hibernate3.support;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateAccessor;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
@ -26,7 +25,8 @@ import org.springframework.orm.hibernate3.SessionHolder; @@ -26,7 +25,8 @@ import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
/**
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
@ -89,7 +89,7 @@ import org.springframework.web.context.request.WebRequestInterceptor; @@ -89,7 +89,7 @@ import org.springframework.web.context.request.WebRequestInterceptor;
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
* @see org.springframework.transaction.support.TransactionSynchronizationManager
*/
public class OpenSessionInViewInterceptor extends HibernateAccessor implements WebRequestInterceptor {
public class OpenSessionInViewInterceptor extends HibernateAccessor implements AsyncWebRequestInterceptor {
/**
* Suffix that gets appended to the <code>SessionFactory</code>
@ -155,7 +155,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements W @@ -155,7 +155,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements W
Session session = SessionFactoryUtils.getSession(
getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());
applyFlushMode(session, false);
TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session));
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
}
else {
// deferred close mode
@ -164,6 +165,39 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements W @@ -164,6 +165,39 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements W
}
}
/**
* Create a <code>Callable</code> to bind the <code>Hibernate</code> session
* to the async request thread.
*/
public AbstractDelegatingCallable getAsyncCallable(WebRequest request) {
String attributeName = getParticipateAttributeName();
if ((request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) != null) || !isSingleSession()) {
return null;
}
final SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
getNextCallable().call();
return null;
}
};
}
/**
* Unbind the Hibernate <code>Session</code> from the main thread but leave
* the <code>Session</code> open for further use from the async thread.
*/
public void postHandleAsyncStarted(WebRequest request) {
String attributeName = getParticipateAttributeName();
if ((request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) == null) && isSingleSession()) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
}
/**
* Flush the Hibernate <code>Session</code> before view rendering, if necessary.
* <p>Note that this just applies in {@link #isSingleSession() single session mode}!

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -32,6 +32,8 @@ import org.springframework.orm.hibernate4.SessionFactoryUtils; @@ -32,6 +32,8 @@ import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@ -102,6 +104,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { @@ -102,6 +104,8 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
SessionFactory sessionFactory = lookupSessionFactory(request);
boolean participate = false;
@ -112,7 +116,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { @@ -112,7 +116,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
else {
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
Session session = openSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
chain.addDelegatingCallable(getAsyncCallable(request, sessionFactory, sessionHolder));
}
try {
@ -123,6 +130,9 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { @@ -123,6 +130,9 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
if (!participate) {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
if (chain.isAsyncStarted()) {
return;
}
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
@ -177,4 +187,28 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter { @@ -177,4 +187,28 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
}
}
/**
* Create a Callable to extend the use of the open Hibernate Session to the
* async thread completing the request.
*/
private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request,
final SessionFactory sessionFactory, final SessionHolder sessionHolder) {
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
try {
getNextCallable().call();
}
finally {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
return null;
}
};
}
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,7 +22,6 @@ import org.hibernate.FlushMode; @@ -22,7 +22,6 @@ import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
@ -30,7 +29,9 @@ import org.springframework.orm.hibernate4.SessionHolder; @@ -30,7 +29,9 @@ import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
/**
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
@ -72,7 +73,7 @@ import org.springframework.web.context.request.WebRequestInterceptor; @@ -72,7 +73,7 @@ import org.springframework.web.context.request.WebRequestInterceptor;
* @see org.springframework.orm.hibernate4.HibernateTransactionManager
* @see org.springframework.transaction.support.TransactionSynchronizationManager
*/
public class OpenSessionInViewInterceptor implements WebRequestInterceptor {
public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor {
/**
* Suffix that gets appended to the <code>SessionFactory</code>
@ -112,13 +113,47 @@ public class OpenSessionInViewInterceptor implements WebRequestInterceptor { @@ -112,13 +113,47 @@ public class OpenSessionInViewInterceptor implements WebRequestInterceptor {
else {
logger.debug("Opening Hibernate Session in OpenSessionInViewInterceptor");
Session session = openSession();
TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session));
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
}
}
public void postHandle(WebRequest request, ModelMap model) {
}
/**
* Create a <code>Callable</code> to bind the <code>Hibernate</code> session
* to the async request thread.
*/
public AbstractDelegatingCallable getAsyncCallable(WebRequest request) {
String attributeName = getParticipateAttributeName();
if (request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) != null) {
return null;
}
final SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
getNextCallable().call();
return null;
}
};
}
/**
* Unbind the Hibernate <code>Session</code> from the main thread leaving
* it open for further use from an async thread.
*/
public void postHandleAsyncStarted(WebRequest request) {
String attributeName = getParticipateAttributeName();
if (request.getAttribute(attributeName, WebRequest.SCOPE_REQUEST) == null) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
}
/**
* Unbind the Hibernate <code>Session</code> from the thread and close it).
* @see org.springframework.transaction.support.TransactionSynchronizationManager

769
spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java

File diff suppressed because it is too large Load Diff

16
spring-web/src/main/java/org/springframework/web/context/request/async/AsyncExecutionChain.java

@ -25,6 +25,8 @@ import javax.servlet.ServletRequest; @@ -25,6 +25,8 @@ import javax.servlet.ServletRequest;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler;
/**
@ -77,6 +79,20 @@ public final class AsyncExecutionChain { @@ -77,6 +79,20 @@ public final class AsyncExecutionChain {
return chain;
}
/**
* Obtain the AsyncExecutionChain for the current request.
* Or if not found, create an instance and associate it with the request.
*/
public static AsyncExecutionChain getForCurrentRequest(WebRequest request) {
int scope = RequestAttributes.SCOPE_REQUEST;
AsyncExecutionChain chain = (AsyncExecutionChain) request.getAttribute(CALLABLE_CHAIN_ATTRIBUTE, scope);
if (chain == null) {
chain = new AsyncExecutionChain();
request.setAttribute(CALLABLE_CHAIN_ATTRIBUTE, chain, scope);
}
return chain;
}
/**
* Provide an instance of an AsyncWebRequest.
* This property must be set before async request processing can begin.

73
spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.request.async;
import java.util.concurrent.Callable;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
/**
* Extends {@link WebRequestInterceptor} with lifecycle methods specific to async
* request processing.
*
* <p>This is the sequence of events on the main thread in an async scenario:
* <ol>
* <li>{@link #preHandle(WebRequest)}
* <li>{@link #getAsyncCallable(WebRequest)}
* <li>... <em>handler execution</em>
* <li>{@link #postHandleAsyncStarted(WebRequest)}
* </ol>
*
* <p>This is the sequence of events on the async thread:
* <ol>
* <li>Async {@link Callable#call()} (the {@code Callable} returned by {@code getAsyncCallable})
* <li>... <em>async handler execution</em>
* <li>{@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}
* <li>{@link #afterCompletion(WebRequest, Exception)}
* </ol>
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public interface AsyncWebRequestInterceptor extends WebRequestInterceptor {
/**
* Invoked <em>after</em> {@link #preHandle(WebRequest)} and <em>before</em>
* the handler is executed. The returned {@link Callable} is used only if
* handler execution leads to teh start of async processing. It is invoked
* the async thread before the request is handled fro.
* <p>Implementations can use this <code>Callable</code> to initialize
* ThreadLocal attributes on the async thread.
* @return a {@link Callable} instance or <code>null</code>
*/
AbstractDelegatingCallable getAsyncCallable(WebRequest request);
/**
* Invoked <em>after</em> the execution of a handler if the handler started
* async processing instead of handling the request. Effectively this method
* is invoked on the way out of the main processing thread instead of
* {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}. The
* <code>postHandle</code> method is invoked after the request is handled
* in the async thread.
* <p>Implementations of this method can ensure ThreadLocal attributes bound
* to the main thread are cleared and also prepare for binding them to the
* async thread.
*/
void postHandleAsyncStarted(WebRequest request);
}

82
spring-webmvc/src/main/java/org/springframework/web/servlet/AsyncHandlerInterceptor.java

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
/**
* Extends {@link HanderInterceptor} with lifecycle methods specific to async
* request processing.
*
* <p>This is the sequence of events on the main thread in an async scenario:
* <ol>
* <li>{@link #preHandle(WebRequest)}
* <li>{@link #getAsyncCallable(WebRequest)}
* <li>... <em>handler execution</em>
* <li>{@link #postHandleAsyncStarted(WebRequest)}
* </ol>
*
* <p>This is the sequence of events on the async thread:
* <ol>
* <li>Async {@link Callable#call()} (the {@code Callable} returned by {@code getAsyncCallable})
* <li>... <em>async handler execution</em>
* <li>{@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}
* <li>{@link #afterCompletion(WebRequest, Exception)}
* </ol>
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
/**
* Invoked <em>after</em> {@link #preHandle(WebRequest)} and <em>before</em>
* the handler is executed. The returned {@link Callable} is used only if
* handler execution leads to teh start of async processing. It is invoked
* the async thread before the request is handled fro.
* <p>Implementations can use this <code>Callable</code> to initialize
* ThreadLocal attributes on the async thread.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance examination
* @return a {@link Callable} instance or <code>null</code>
*/
AbstractDelegatingCallable getAsyncCallable(HttpServletRequest request, HttpServletResponse response, Object handler);
/**
* Invoked <em>after</em> the execution of a handler if the handler started
* async processing instead of handling the request. Effectively this method
* is invoked on the way out of the main processing thread instead of
* {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}. The
* <code>postHandle</code> method is invoked after the request is handled
* in the async thread.
* <p>Implementations of this method can ensure ThreadLocal attributes bound
* to the main thread are cleared and also prepare for binding them to the
* async thread.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance examination
*/
void postHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler);
}

3
spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java

@ -941,6 +941,8 @@ public class DispatcherServlet extends FrameworkServlet { @@ -941,6 +941,8 @@ public class DispatcherServlet extends FrameworkServlet {
return;
}
mappedHandler.addDelegatingCallables(processedRequest, response);
asyncChain.addDelegatingCallable(
getDispatchAsyncCallable(mappedHandler, request, response, processedRequest));
@ -948,6 +950,7 @@ public class DispatcherServlet extends FrameworkServlet { @@ -948,6 +950,7 @@ public class DispatcherServlet extends FrameworkServlet {
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncChain.isAsyncStarted()) {
mappedHandler.applyPostHandleAsyncStarted(processedRequest, response);
logger.debug("Exiting request thread and leaving the response open");
return;
}

50
spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java

@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse; @@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.async.AsyncExecutionChain;
/**
* Handler execution chain, consisting of handler object and any handler interceptors.
@ -139,7 +140,7 @@ public class HandlerExecutionChain { @@ -139,7 +140,7 @@ public class HandlerExecutionChain {
}
/**
* Apply preHandle methods of registered interceptors.
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)
throws Exception {
@ -153,6 +154,53 @@ public class HandlerExecutionChain { @@ -153,6 +154,53 @@ public class HandlerExecutionChain {
}
}
/**
* Add delegating, async Callable instances to the {@link AsyncExecutionChain}
* for use in case of asynchronous request processing.
*/
void addDelegatingCallables(HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (getInterceptors() == null) {
return;
}
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
if (interceptor instanceof AsyncHandlerInterceptor) {
try {
AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptor;
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
chain.addDelegatingCallable(asyncInterceptor.getAsyncCallable(request, response, this.handler));
}
catch (Throwable ex) {
logger.error("HandlerInterceptor.addAsyncCallables threw exception", ex);
}
}
}
}
/**
* Trigger postHandleAsyncStarted callbacks on the mapped HandlerInterceptors.
*/
void applyPostHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (getInterceptors() == null) {
return;
}
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
if (interceptor instanceof AsyncHandlerInterceptor) {
try {
((AsyncHandlerInterceptor) interceptor).postHandleAsyncStarted(request, response, this.handler);
}
catch (Throwable ex) {
logger.error("HandlerInterceptor.postHandleAsyncStarted threw exception", ex);
}
}
}
}
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation

25
spring-webmvc/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java

@ -21,7 +21,9 @@ import javax.servlet.http.HttpServletResponse; @@ -21,7 +21,9 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
@ -33,7 +35,7 @@ import org.springframework.web.servlet.ModelAndView; @@ -33,7 +35,7 @@ import org.springframework.web.servlet.ModelAndView;
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
*/
public class WebRequestHandlerInterceptorAdapter implements HandlerInterceptor {
public class WebRequestHandlerInterceptorAdapter implements AsyncHandlerInterceptor {
private final WebRequestInterceptor requestInterceptor;
@ -55,6 +57,25 @@ public class WebRequestHandlerInterceptorAdapter implements HandlerInterceptor { @@ -55,6 +57,25 @@ public class WebRequestHandlerInterceptorAdapter implements HandlerInterceptor {
return true;
}
public AbstractDelegatingCallable getAsyncCallable(HttpServletRequest request,
HttpServletResponse response, Object handler) {
if (this.requestInterceptor instanceof AsyncWebRequestInterceptor) {
AsyncWebRequestInterceptor asyncInterceptor = (AsyncWebRequestInterceptor) this.requestInterceptor;
DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response);
return asyncInterceptor.getAsyncCallable(webRequest);
}
return null;
}
public void postHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (this.requestInterceptor instanceof AsyncWebRequestInterceptor) {
AsyncWebRequestInterceptor asyncInterceptor = (AsyncWebRequestInterceptor) this.requestInterceptor;
DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response);
asyncInterceptor.postHandleAsyncStarted(webRequest);
}
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {

60
spring-webmvc/src/test/java/org/springframework/web/servlet/HandlerExecutionChainTests.java

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
package org.springframework.web.servlet;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
@ -26,6 +26,7 @@ import org.junit.Before; @@ -26,6 +26,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
/**
* A test fixture with HandlerExecutionChain and mock handler interceptors.
@ -42,11 +43,11 @@ public class HandlerExecutionChainTests { @@ -42,11 +43,11 @@ public class HandlerExecutionChainTests {
private MockHttpServletResponse response;
private HandlerInterceptor interceptor1;
private AsyncHandlerInterceptor interceptor1;
private HandlerInterceptor interceptor2;
private AsyncHandlerInterceptor interceptor2;
private HandlerInterceptor interceptor3;
private AsyncHandlerInterceptor interceptor3;
@Before
public void setup() {
@ -56,9 +57,9 @@ public class HandlerExecutionChainTests { @@ -56,9 +57,9 @@ public class HandlerExecutionChainTests {
this.handler = new Object();
this.chain = new HandlerExecutionChain(this.handler);
this.interceptor1 = createMock(HandlerInterceptor.class);
this.interceptor2 = createMock(HandlerInterceptor.class);
this.interceptor3 = createMock(HandlerInterceptor.class);
this.interceptor1 = createStrictMock(AsyncHandlerInterceptor.class);
this.interceptor2 = createStrictMock(AsyncHandlerInterceptor.class);
this.interceptor3 = createStrictMock(AsyncHandlerInterceptor.class);
this.chain.addInterceptor(this.interceptor1);
this.chain.addInterceptor(this.interceptor2);
@ -91,7 +92,42 @@ public class HandlerExecutionChainTests { @@ -91,7 +92,42 @@ public class HandlerExecutionChainTests {
}
@Test
public void earlyExit() throws Exception {
public void successAsyncScenario() throws Exception {
ModelAndView mav = new ModelAndView();
expect(this.interceptor1.preHandle(this.request, this.response, this.handler)).andReturn(true);
expect(this.interceptor2.preHandle(this.request, this.response, this.handler)).andReturn(true);
expect(this.interceptor3.preHandle(this.request, this.response, this.handler)).andReturn(true);
expect(this.interceptor1.getAsyncCallable(request, response, this.handler)).andReturn(new TestAsyncCallable());
expect(this.interceptor2.getAsyncCallable(request, response, this.handler)).andReturn(new TestAsyncCallable());
expect(this.interceptor3.getAsyncCallable(request, response, this.handler)).andReturn(new TestAsyncCallable());
this.interceptor1.postHandleAsyncStarted(request, response, this.handler);
this.interceptor2.postHandleAsyncStarted(request, response, this.handler);
this.interceptor3.postHandleAsyncStarted(request, response, this.handler);
this.interceptor1.postHandle(this.request, this.response, this.handler, mav);
this.interceptor2.postHandle(this.request, this.response, this.handler, mav);
this.interceptor3.postHandle(this.request, this.response, this.handler, mav);
this.interceptor3.afterCompletion(this.request, this.response, this.handler, null);
this.interceptor2.afterCompletion(this.request, this.response, this.handler, null);
this.interceptor1.afterCompletion(this.request, this.response, this.handler, null);
replay(this.interceptor1, this.interceptor2, this.interceptor3);
this.chain.applyPreHandle(request, response);
this.chain.addDelegatingCallables(request, response);
this.chain.applyPostHandleAsyncStarted(request, response);
this.chain.applyPostHandle(request, response, mav);
this.chain.triggerAfterCompletion(this.request, this.response, null);
verify(this.interceptor1, this.interceptor2, this.interceptor3);
}
@Test
public void earlyExitInPreHandle() throws Exception {
expect(this.interceptor1.preHandle(this.request, this.response, this.handler)).andReturn(true);
expect(this.interceptor2.preHandle(this.request, this.response, this.handler)).andReturn(false);
@ -155,4 +191,12 @@ public class HandlerExecutionChainTests { @@ -155,4 +191,12 @@ public class HandlerExecutionChainTests {
verify(this.interceptor1, this.interceptor2, this.interceptor3);
}
private static class TestAsyncCallable extends AbstractDelegatingCallable {
public Object call() throws Exception {
return null;
}
}
}

Loading…
Cancel
Save