Set timeout value and task executor per async request
Methods returning DeferredResult can now specify a timeout value through constructor arg while methods returning a Callable can wrap it in an AsyncTask that also accepts a timeout and a specific task executor. Issue: SPR-9399
This commit is contained in:
@@ -34,7 +34,7 @@ import org.springframework.util.Assert;
|
|||||||
import org.springframework.web.context.WebApplicationContext;
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
import org.springframework.web.context.request.async.AsyncWebUtils;
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
|
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
|
||||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@@ -195,13 +195,13 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||||||
participate = true;
|
participate = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) {
|
if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) {
|
||||||
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
|
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
|
||||||
Session session = getSession(sessionFactory);
|
Session session = getSession(sessionFactory);
|
||||||
SessionHolder sessionHolder = new SessionHolder(session);
|
SessionHolder sessionHolder = new SessionHolder(session);
|
||||||
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
||||||
|
|
||||||
AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
|
WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
|
||||||
asyncManager.registerAsyncThreadInitializer(key, initializer);
|
asyncManager.registerAsyncThreadInitializer(key, initializer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,10 +240,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
|
private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
|
||||||
final SessionHolder sessionHolder) {
|
final SessionHolder sessionHolder) {
|
||||||
|
|
||||||
return new AsyncThreadInitializer() {
|
return new WebAsyncThreadInitializer() {
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.springframework.web.context.request.WebRequest;
|
|||||||
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
|
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
|
||||||
import org.springframework.web.context.request.async.AsyncWebUtils;
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
|
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
|
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
|
||||||
@@ -147,7 +147,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
|
|||||||
String participateAttributeName = getParticipateAttributeName();
|
String participateAttributeName = getParticipateAttributeName();
|
||||||
|
|
||||||
if (asyncManager.hasConcurrentResult()) {
|
if (asyncManager.hasConcurrentResult()) {
|
||||||
if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) {
|
if (asyncManager.initializeAsyncThread(participateAttributeName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
|
|||||||
SessionHolder sessionHolder = new SessionHolder(session);
|
SessionHolder sessionHolder = new SessionHolder(session);
|
||||||
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
||||||
|
|
||||||
AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
|
WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
|
||||||
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
|
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -261,8 +261,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
|
|||||||
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
|
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
|
private WebAsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
|
||||||
return new AsyncThreadInitializer() {
|
return new WebAsyncThreadInitializer() {
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
|||||||
import org.springframework.web.context.WebApplicationContext;
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
import org.springframework.web.context.request.async.AsyncWebUtils;
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
|
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
|
||||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@@ -126,13 +126,13 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||||||
participate = true;
|
participate = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) {
|
if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) {
|
||||||
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
|
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
|
||||||
Session session = openSession(sessionFactory);
|
Session session = openSession(sessionFactory);
|
||||||
SessionHolder sessionHolder = new SessionHolder(session);
|
SessionHolder sessionHolder = new SessionHolder(session);
|
||||||
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
||||||
|
|
||||||
AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
|
WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
|
||||||
asyncManager.registerAsyncThreadInitializer(key, initializer);
|
asyncManager.registerAsyncThreadInitializer(key, initializer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,10 +153,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
|
private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
|
||||||
final SessionHolder sessionHolder) {
|
final SessionHolder sessionHolder) {
|
||||||
|
|
||||||
return new AsyncThreadInitializer() {
|
return new WebAsyncThreadInitializer() {
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import org.springframework.web.context.request.WebRequest;
|
|||||||
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
|
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
|
||||||
import org.springframework.web.context.request.async.AsyncWebUtils;
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
|
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
|
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
|
||||||
@@ -109,7 +109,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
|
|||||||
String participateAttributeName = getParticipateAttributeName();
|
String participateAttributeName = getParticipateAttributeName();
|
||||||
|
|
||||||
if (asyncManager.hasConcurrentResult()) {
|
if (asyncManager.hasConcurrentResult()) {
|
||||||
if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) {
|
if (asyncManager.initializeAsyncThread(participateAttributeName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
|
|||||||
SessionHolder sessionHolder = new SessionHolder(session);
|
SessionHolder sessionHolder = new SessionHolder(session);
|
||||||
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
||||||
|
|
||||||
AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
|
WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
|
||||||
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
|
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,8 +200,8 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
|
|||||||
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
|
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
|
private WebAsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
|
||||||
return new AsyncThreadInitializer() {
|
return new WebAsyncThreadInitializer() {
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder for a {@link Callable}, a timeout value, and a task executor.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 3.2
|
||||||
|
*/
|
||||||
|
public class AsyncTask {
|
||||||
|
|
||||||
|
private final Callable<?> callable;
|
||||||
|
|
||||||
|
private final Long timeout;
|
||||||
|
|
||||||
|
private final String executorName;
|
||||||
|
|
||||||
|
private final AsyncTaskExecutor executor;
|
||||||
|
|
||||||
|
private BeanFactory beanFactory;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AsyncTask with a timeout value and a Callable.
|
||||||
|
* @param timeout timeout value in milliseconds
|
||||||
|
* @param callable the callable for concurrent handling
|
||||||
|
*/
|
||||||
|
public AsyncTask(long timeout, Callable<?> callable) {
|
||||||
|
this(timeout, null, null, callable);
|
||||||
|
Assert.notNull(timeout, "Timeout must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AsyncTask with a timeout value, an executor name, and a Callable.
|
||||||
|
* @param timeout timeout value in milliseconds; ignored if {@code null}
|
||||||
|
* @param callable the callable for concurrent handling
|
||||||
|
*/
|
||||||
|
public AsyncTask(Long timeout, String executorName, Callable<?> callable) {
|
||||||
|
this(timeout, null, executorName, callable);
|
||||||
|
Assert.notNull(executor, "Executor name must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AsyncTask with a timeout value, an executor instance, and a Callable.
|
||||||
|
* @param timeout timeout value in milliseconds; ignored if {@code null}
|
||||||
|
* @param callable the callable for concurrent handling
|
||||||
|
*/
|
||||||
|
public AsyncTask(Long timeout, AsyncTaskExecutor executor, Callable<?> callable) {
|
||||||
|
this(timeout, executor, null, callable);
|
||||||
|
Assert.notNull(executor, "Executor must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
private AsyncTask(Long timeout, AsyncTaskExecutor executor, String executorName, Callable<?> callable) {
|
||||||
|
Assert.notNull(callable, "Callable must not be null");
|
||||||
|
this.callable = callable;
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.executor = executor;
|
||||||
|
this.executorName = executorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link Callable} to use for concurrent handling, never {@code null}.
|
||||||
|
*/
|
||||||
|
public Callable<?> getCallable() {
|
||||||
|
return this.callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the timeout value in milliseconds or {@code null} if not value is set.
|
||||||
|
*/
|
||||||
|
public Long getTimeout() {
|
||||||
|
return this.timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the AsyncTaskExecutor to use for concurrent handling, or {@code null}.
|
||||||
|
*/
|
||||||
|
public AsyncTaskExecutor getExecutor() {
|
||||||
|
if (this.executor != null) {
|
||||||
|
return this.executor;
|
||||||
|
}
|
||||||
|
else if (this.executorName != null) {
|
||||||
|
Assert.state(this.beanFactory != null, "A BeanFactory is required to look up an task executor bean");
|
||||||
|
return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link BeanFactory} to use to resolve an executor name. Applications are
|
||||||
|
* not expected to have to set this property when AsyncTask is used in a
|
||||||
|
* Spring MVC controller.
|
||||||
|
*/
|
||||||
|
public void setBeanFactory(BeanFactory beanFactory) {
|
||||||
|
this.beanFactory = beanFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -29,20 +29,18 @@ public interface AsyncWebRequest extends NativeWebRequest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the time required for concurrent handling to complete.
|
* Set the time required for concurrent handling to complete.
|
||||||
* @param timeout amount of time in milliseconds
|
* This property should not be set when concurrent handling is in progress,
|
||||||
|
* i.e. when {@link #isAsyncStarted()} is {@code true}.
|
||||||
|
* @param timeout amount of time in milliseconds; {@code null} means no
|
||||||
|
* timeout, i.e. rely on the default timeout of the container.
|
||||||
*/
|
*/
|
||||||
void setTimeout(Long timeout);
|
void setTimeout(Long timeout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a Runnable to invoke on timeout.
|
* Set a handler to be invoked if concurrent processing times out.
|
||||||
*/
|
*/
|
||||||
void setTimeoutHandler(Runnable runnable);
|
void setTimeoutHandler(Runnable runnable);
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a Runnable to invoke at the end of asynchronous request processing.
|
|
||||||
*/
|
|
||||||
void addCompletionHandler(Runnable runnable);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the start of asynchronous request processing so that when the main
|
* Mark the start of asynchronous request processing so that when the main
|
||||||
* processing thread exits, the response remains open for further processing
|
* processing thread exits, the response remains open for further processing
|
||||||
@@ -70,6 +68,11 @@ public interface AsyncWebRequest extends NativeWebRequest {
|
|||||||
*/
|
*/
|
||||||
boolean isDispatched();
|
boolean isDispatched();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Runnable to be invoked when request processing completes.
|
||||||
|
*/
|
||||||
|
void addCompletionHandler(Runnable runnable);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether asynchronous processing has completed.
|
* Whether asynchronous processing has completed.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,8 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.web.context.request.async;
|
package org.springframework.web.context.request.async;
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.web.context.request.RequestAttributes;
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
import org.springframework.web.context.request.WebRequest;
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
|
||||||
@@ -57,4 +63,30 @@ public abstract class AsyncWebUtils {
|
|||||||
return asyncManager;
|
return asyncManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AsyncWebRequest instance.
|
||||||
|
* <p>By default an instance of {@link StandardServletAsyncWebRequest} is created
|
||||||
|
* if running in Servlet 3.0 (or higher) environment or as a fallback option an
|
||||||
|
* instance of {@link NoSupportAsyncWebRequest} is returned.
|
||||||
|
* @param request the current request
|
||||||
|
* @param response the current response
|
||||||
|
* @return an AsyncWebRequest instance, never {@code null}
|
||||||
|
*/
|
||||||
|
public static AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
return ClassUtils.hasMethod(ServletRequest.class, "startAsync") ?
|
||||||
|
createStandardServletAsyncWebRequest(request, response) : new NoSupportAsyncWebRequest(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AsyncWebRequest createStandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
try {
|
||||||
|
String className = "org.springframework.web.context.request.async.StandardServletAsyncWebRequest";
|
||||||
|
Class<?> clazz = ClassUtils.forName(className, AsyncWebUtils.class.getClassLoader());
|
||||||
|
Constructor<?> constructor = clazz.getConstructor(HttpServletRequest.class, HttpServletResponse.class);
|
||||||
|
return (AsyncWebRequest) BeanUtils.instantiateClass(constructor, request, response);
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
throw new IllegalStateException("Failed to instantiate StandardServletAsyncWebRequest", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ import org.apache.commons.logging.Log;
|
|||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code DeferredResult} provides an alternative to returning a {@link Callable}
|
* {@code DeferredResult} provides an alternative to using a {@link Callable}
|
||||||
* for asynchronous request processing. While with a Callable, a thread is used
|
* for asynchronous request processing. While a Callable is executed concurrently
|
||||||
* to execute it on behalf of the application, with a DeferredResult the application
|
* on behalf of the application, with a DeferredResult the application can produce
|
||||||
* sets the result whenever it needs to from a thread of its choice.
|
* the result from a thread of its choice.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
@@ -38,32 +38,53 @@ public final class DeferredResult<T> {
|
|||||||
|
|
||||||
private static final Object RESULT_NONE = new Object();
|
private static final Object RESULT_NONE = new Object();
|
||||||
|
|
||||||
private Object result = RESULT_NONE;
|
|
||||||
|
|
||||||
private final Object timeoutResult;
|
private final Object timeoutResult;
|
||||||
|
|
||||||
private final AtomicBoolean expired = new AtomicBoolean(false);
|
private final Long timeout;
|
||||||
|
|
||||||
private DeferredResultHandler resultHandler;
|
private DeferredResultHandler resultHandler;
|
||||||
|
|
||||||
|
private Object result = RESULT_NONE;
|
||||||
|
|
||||||
|
private final AtomicBoolean expired = new AtomicBoolean(false);
|
||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
|
|
||||||
private final CountDownLatch latch = new CountDownLatch(1);
|
private final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a DeferredResult instance.
|
* Create a DeferredResult.
|
||||||
*/
|
*/
|
||||||
public DeferredResult() {
|
public DeferredResult() {
|
||||||
this(RESULT_NONE);
|
this(null, RESULT_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a DeferredResult with a default result to use in case of a timeout.
|
* Create a DeferredResult with a timeout.
|
||||||
* @param timeoutResult the result to use
|
* @param timeout timeout value in milliseconds
|
||||||
*/
|
*/
|
||||||
public DeferredResult(Object timeoutResult) {
|
public DeferredResult(long timeout) {
|
||||||
|
this(timeout, RESULT_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a DeferredResult with a timeout and a default result to use on timeout.
|
||||||
|
* @param timeout timeout value in milliseconds; ignored if {@code null}
|
||||||
|
* @param timeoutResult the result to use, possibly {@code null}
|
||||||
|
*/
|
||||||
|
public DeferredResult(Long timeout, Object timeoutResult) {
|
||||||
this.timeoutResult = timeoutResult;
|
this.timeoutResult = timeoutResult;
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the configured timeout value in milliseconds.
|
||||||
|
*/
|
||||||
|
public Long getTimeoutMilliseconds() {
|
||||||
|
return this.timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
|||||||
* <p>The timeout period begins when the main processing thread has exited.
|
* <p>The timeout period begins when the main processing thread has exited.
|
||||||
*/
|
*/
|
||||||
public void setTimeout(Long timeout) {
|
public void setTimeout(Long timeout) {
|
||||||
|
Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress");
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +146,9 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a SERVICE_UNAVAILABLE (503).
|
||||||
|
*/
|
||||||
private class DefaultTimeoutHandler implements Runnable {
|
private class DefaultTimeoutHandler implements Runnable {
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|||||||
@@ -38,25 +38,16 @@ import org.springframework.web.util.UrlPathHelper;
|
|||||||
* as an SPI and not typically used directly by application classes.
|
* as an SPI and not typically used directly by application classes.
|
||||||
*
|
*
|
||||||
* <p>An async scenario starts with request processing as usual in a thread (T1).
|
* <p>An async scenario starts with request processing as usual in a thread (T1).
|
||||||
* When a handler decides to handle the request concurrently, it calls
|
* Concurrent request handling can be innitiated by calling
|
||||||
* {@linkplain #startCallableProcessing(Callable, Object...) startCallableProcessing} or
|
* {@linkplain #startCallableProcessing(Callable, Object...) startCallableProcessing} or
|
||||||
* {@linkplain #startDeferredResultProcessing(DeferredResult, Object...) startDeferredResultProcessing}
|
* {@linkplain #startDeferredResultProcessing(DeferredResult, Object...) startDeferredResultProcessing}
|
||||||
* both of which will process in a separate thread (T2).
|
* both of which produce a result in a separate thread (T2). The result is saved
|
||||||
* After the start of concurrent handling {@link #isConcurrentHandlingStarted()}
|
* and the request dispatched to the container, to resume processing with the saved
|
||||||
* returns "true" and this can be used by classes involved in processing on the
|
* result in a third thread (T3). Within the dispatched thread (T3), the saved
|
||||||
* main thread (T1) quickly and with very minimal processing.
|
* result can be accessed via {@link #getConcurrentResult()} or its presence
|
||||||
|
* detected via {@link #hasConcurrentResult()}.
|
||||||
*
|
*
|
||||||
* <p>When the concurrent handling completes in a separate thread (T2), both
|
* <p>TODO .. Servlet 3 config
|
||||||
* {@code startCallableProcessing} and {@code startDeferredResultProcessing}
|
|
||||||
* save the results and dispatched to the container, essentially to the
|
|
||||||
* same request URI as the one that started concurrent handling. This allows for
|
|
||||||
* further processing of the concurrent results. Classes in the dispatched
|
|
||||||
* thread (T3), can access the results via {@link #getConcurrentResult()} or
|
|
||||||
* detect their presence via {@link #hasConcurrentResult()}. Also in the
|
|
||||||
* dispatched thread {@link #isConcurrentHandlingStarted()} will return "false"
|
|
||||||
* unless concurrent handling is started once again.
|
|
||||||
*
|
|
||||||
* TODO .. mention Servlet 3 configuration
|
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
@@ -73,42 +64,38 @@ public final class WebAsyncManager {
|
|||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(WebAsyncManager.class);
|
private static final Log logger = LogFactory.getLog(WebAsyncManager.class);
|
||||||
|
|
||||||
|
|
||||||
private AsyncWebRequest asyncWebRequest;
|
private AsyncWebRequest asyncWebRequest;
|
||||||
|
|
||||||
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());
|
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());
|
||||||
|
|
||||||
private final Map<Object, AsyncThreadInitializer> threadInitializers = new LinkedHashMap<Object, AsyncThreadInitializer>();
|
|
||||||
|
|
||||||
private Object concurrentResult = RESULT_NONE;
|
private Object concurrentResult = RESULT_NONE;
|
||||||
|
|
||||||
private Object[] concurrentResultContext;
|
private Object[] concurrentResultContext;
|
||||||
|
|
||||||
|
private final Map<Object, WebAsyncThreadInitializer> threadInitializers = new LinkedHashMap<Object, WebAsyncThreadInitializer>();
|
||||||
|
|
||||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Package private constructor
|
* Package private constructor.
|
||||||
* @see AsyncWebUtils
|
* @see AsyncWebUtils#getAsyncManager(javax.servlet.ServletRequest)
|
||||||
|
* @see AsyncWebUtils#getAsyncManager(org.springframework.web.context.request.WebRequest)
|
||||||
*/
|
*/
|
||||||
WebAsyncManager() {
|
WebAsyncManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure an AsyncTaskExecutor for use with {@link #startCallableProcessing(Callable)}.
|
* Configure the {@link AsyncWebRequest} to use. This property may be
|
||||||
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used. Applications
|
* set more than once during a single request to accurately reflect the
|
||||||
* are advised to provide a TaskExecutor configured for production use.
|
* current state of the request (e.g. following a forward, request/response
|
||||||
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setAsyncTaskExecutor
|
* wrapping, etc). However, it should not be set while concurrent handling is
|
||||||
*/
|
* in progress, i.e. while {@link #isConcurrentHandlingStarted()} is {@code true}.
|
||||||
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
|
* @param asyncWebRequest the web request to use
|
||||||
this.taskExecutor = taskExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide an {@link AsyncWebRequest} to use to start and to dispatch request.
|
|
||||||
* This property must be set before the start of concurrent handling.
|
|
||||||
* @param asyncWebRequest the request to use
|
|
||||||
*/
|
*/
|
||||||
public void setAsyncWebRequest(final AsyncWebRequest asyncWebRequest) {
|
public void setAsyncWebRequest(final AsyncWebRequest asyncWebRequest) {
|
||||||
Assert.notNull(asyncWebRequest, "Expected AsyncWebRequest");
|
Assert.notNull(asyncWebRequest, "AsyncWebRequest must not be null");
|
||||||
Assert.state(!isConcurrentHandlingStarted(), "Can't set AsyncWebRequest with concurrent handling in progress");
|
Assert.state(!isConcurrentHandlingStarted(), "Can't set AsyncWebRequest with concurrent handling in progress");
|
||||||
this.asyncWebRequest = asyncWebRequest;
|
this.asyncWebRequest = asyncWebRequest;
|
||||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||||
@@ -119,18 +106,27 @@ public final class WebAsyncManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the handler for the current request is executed concurrently.
|
* Configure an AsyncTaskExecutor for use with concurrent processing via
|
||||||
* Once concurrent handling is done, the result is saved, and the request
|
* {@link #startCallableProcessing(Callable, Object...)}.
|
||||||
* dispatched again to resume processing where the result of concurrent
|
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
|
||||||
* handling is available via {@link #getConcurrentResult()}.
|
*/
|
||||||
|
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
|
||||||
|
this.taskExecutor = taskExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the target handler chose to handle the request asynchronously.
|
||||||
|
* A return value of "true" indicates concurrent handling is under way and the
|
||||||
|
* response will remain open. A return value of "false" will be returned again after concurrent
|
||||||
|
* handling produces a result and the request is dispatched to resume processing.
|
||||||
*/
|
*/
|
||||||
public boolean isConcurrentHandlingStarted() {
|
public boolean isConcurrentHandlingStarted() {
|
||||||
return ((this.asyncWebRequest != null) && (this.asyncWebRequest.isAsyncStarted()));
|
return ((this.asyncWebRequest != null) && (this.asyncWebRequest.isAsyncStarted()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the current thread was dispatched to continue processing the result
|
* Whether the request was dispatched to resume processing the result of
|
||||||
* of concurrent handler execution.
|
* concurrent handling.
|
||||||
*/
|
*/
|
||||||
public boolean hasConcurrentResult() {
|
public boolean hasConcurrentResult() {
|
||||||
|
|
||||||
@@ -142,84 +138,58 @@ public final class WebAsyncManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the result of concurrent handler execution. This may be an Object
|
* Provides access to the result from concurrent handling.
|
||||||
* value on successful return or an {@code Exception} or {@code Throwable}.
|
* @return an Object, possibly an {@code Exception} or {@code Throwable} if
|
||||||
|
* concurrent handling raised one.
|
||||||
*/
|
*/
|
||||||
public Object getConcurrentResult() {
|
public Object getConcurrentResult() {
|
||||||
return this.concurrentResult;
|
return this.concurrentResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the processing context saved at the start of concurrent handling.
|
* Provides access to additional processing context saved at the start of
|
||||||
|
* concurrent handling.
|
||||||
*/
|
*/
|
||||||
public Object[] getConcurrentResultContext() {
|
public Object[] getConcurrentResultContext() {
|
||||||
return this.concurrentResultContext;
|
return this.concurrentResultContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the {@linkplain #getConcurrentResult() concurrentResult} and the
|
* Clear {@linkplain #getConcurrentResult() concurrentResult} and
|
||||||
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
|
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
|
||||||
*/
|
*/
|
||||||
public void resetConcurrentResult() {
|
public void clearConcurrentResult() {
|
||||||
this.concurrentResult = RESULT_NONE;
|
this.concurrentResult = RESULT_NONE;
|
||||||
this.concurrentResultContext = null;
|
this.concurrentResultContext = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register an {@link AsyncThreadInitializer} with the WebAsyncManager instance
|
* Start concurrent request processing and execute the given task with an
|
||||||
* for the current request. It may later be accessed and applied via
|
* {@link #setTaskExecutor(AsyncTaskExecutor) AsyncTaskExecutor}. The result
|
||||||
* {@link #applyAsyncThreadInitializer(String)} and will also be used to
|
* from the task execution is saved and the request dispatched in order to
|
||||||
* initialize and reset threads for concurrent handler execution.
|
* resume processing of that result. If the task raises an Exception then
|
||||||
* @param key a unique the key under which to keep the initializer
|
* the saved result will be the raised Exception.
|
||||||
* @param initializer the initializer instance
|
|
||||||
*/
|
|
||||||
public void registerAsyncThreadInitializer(Object key, AsyncThreadInitializer initializer) {
|
|
||||||
Assert.notNull(initializer, "An AsyncThreadInitializer instance is required");
|
|
||||||
this.threadInitializers.put(key, initializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke the {@linkplain AsyncThreadInitializer#initialize() initialize()}
|
|
||||||
* method of the named {@link AsyncThreadInitializer}.
|
|
||||||
* @param key the key under which the initializer was registered
|
|
||||||
* @return whether an initializer was found and applied
|
|
||||||
*/
|
|
||||||
public boolean applyAsyncThreadInitializer(Object key) {
|
|
||||||
AsyncThreadInitializer initializer = this.threadInitializers.get(key);
|
|
||||||
if (initializer != null) {
|
|
||||||
initializer.initialize();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submit a request handling task for concurrent execution. Returns immediately
|
|
||||||
* and subsequent calls to {@link #isConcurrentHandlingStarted()} return "true".
|
|
||||||
* <p>When concurrent handling is done, the resulting value, which may be an
|
|
||||||
* Object or a raised {@code Exception} or {@code Throwable}, is saved and the
|
|
||||||
* request is dispatched for further processing of that result. In the dispatched
|
|
||||||
* thread, the result can be accessed via {@link #getConcurrentResult()} while
|
|
||||||
* {@link #hasConcurrentResult()} returns "true" and
|
|
||||||
* {@link #isConcurrentHandlingStarted()} is back to returning "false".
|
|
||||||
*
|
*
|
||||||
* @param callable a unit of work to be executed asynchronously
|
* @param callable a unit of work to be executed asynchronously
|
||||||
* @param processingContext additional context to save for later access via
|
* @param processingContext additional context to save that can be accessed
|
||||||
* {@link #getConcurrentResultContext()}
|
* via {@link #getConcurrentResultContext()}
|
||||||
|
*
|
||||||
|
* @see #getConcurrentResult()
|
||||||
|
* @see #getConcurrentResultContext()
|
||||||
*/
|
*/
|
||||||
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
|
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
|
||||||
Assert.notNull(callable, "Callable is required");
|
Assert.notNull(callable, "Callable must not be null");
|
||||||
|
|
||||||
startAsyncProcessing(processingContext);
|
startAsyncProcessing(processingContext);
|
||||||
|
|
||||||
this.taskExecutor.submit(new Runnable() {
|
this.taskExecutor.submit(new Runnable() {
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
List<AsyncThreadInitializer> initializers =
|
List<WebAsyncThreadInitializer> initializers =
|
||||||
new ArrayList<AsyncThreadInitializer>(threadInitializers.values());
|
new ArrayList<WebAsyncThreadInitializer>(threadInitializers.values());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (AsyncThreadInitializer initializer : initializers) {
|
for (WebAsyncThreadInitializer initializer : initializers) {
|
||||||
initializer.initialize();
|
initializer.initialize();
|
||||||
}
|
}
|
||||||
concurrentResult = callable.call();
|
concurrentResult = callable.call();
|
||||||
@@ -229,7 +199,7 @@ public final class WebAsyncManager {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
Collections.reverse(initializers);
|
Collections.reverse(initializers);
|
||||||
for (AsyncThreadInitializer initializer : initializers) {
|
for (WebAsyncThreadInitializer initializer : initializers) {
|
||||||
initializer.reset();
|
initializer.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,24 +220,51 @@ public final class WebAsyncManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the given given {@link DeferredResult} so that whenever the
|
* Use the given {@link AsyncTask} to configure the task executor as well as
|
||||||
* DeferredResult is set, the resulting value, which may be an Object or a
|
* the timeout value of the {@code AsyncWebRequest} before delegating to
|
||||||
* raised {@code Exception} or {@code Throwable}, is saved and the request
|
* {@link #startCallableProcessing(Callable, Object...)}.
|
||||||
* is dispatched for further processing of the result. In the dispatch
|
* @param asyncTask an asyncTask containing the target {@code Callable}
|
||||||
* thread, the result value can be accessed via {@link #getConcurrentResult()}.
|
* @param processingContext additional context to save that can be accessed
|
||||||
* <p>The method returns immediately and it's up to the caller to set the
|
* via {@link #getConcurrentResultContext()}
|
||||||
* DeferredResult. Subsequent calls to {@link #isConcurrentHandlingStarted()}
|
*/
|
||||||
* return "true" until after the dispatch when {@link #hasConcurrentResult()}
|
public void startCallableProcessing(AsyncTask asyncTask, Object... processingContext) {
|
||||||
* returns "true" and {@link #isConcurrentHandlingStarted()} is back to "false".
|
Assert.notNull(asyncTask, "AsyncTask must not be null");
|
||||||
|
|
||||||
|
Long timeout = asyncTask.getTimeout();
|
||||||
|
if (timeout != null) {
|
||||||
|
this.asyncWebRequest.setTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncTaskExecutor executor = asyncTask.getExecutor();
|
||||||
|
if (executor != null) {
|
||||||
|
this.taskExecutor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
startCallableProcessing(asyncTask.getCallable(), processingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start concurrent request processing and initialize the given {@link DeferredResult}
|
||||||
|
* with a {@link DeferredResultHandler} that saves the result and dispatches
|
||||||
|
* the request to resume processing of that result.
|
||||||
|
* The {@code AsyncWebRequest} is also updated with a completion handler that
|
||||||
|
* expires the {@code DeferredResult} and a timeout handler assuming the
|
||||||
|
* {@code DeferredResult} has a default timeout result.
|
||||||
*
|
*
|
||||||
* @param deferredResult the DeferredResult instance to initialize
|
* @param deferredResult the DeferredResult instance to initialize
|
||||||
* @param processingContext additional context to save for later access via
|
* @param processingContext additional context to save that can be accessed
|
||||||
* {@link #getConcurrentResultContext()}
|
* via {@link #getConcurrentResultContext()}
|
||||||
|
*
|
||||||
|
* @see #getConcurrentResult()
|
||||||
|
* @see #getConcurrentResultContext()
|
||||||
*/
|
*/
|
||||||
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult, Object... processingContext) {
|
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult, Object... processingContext) {
|
||||||
Assert.notNull(deferredResult, "DeferredResult is required");
|
Assert.notNull(deferredResult, "DeferredResult must not be null");
|
||||||
|
|
||||||
startAsyncProcessing(processingContext);
|
Long timeout = deferredResult.getTimeoutMilliseconds();
|
||||||
|
if (timeout != null) {
|
||||||
|
this.asyncWebRequest.setTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -283,6 +280,8 @@ public final class WebAsyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startAsyncProcessing(processingContext);
|
||||||
|
|
||||||
deferredResult.setResultHandler(new DeferredResultHandler() {
|
deferredResult.setResultHandler(new DeferredResultHandler() {
|
||||||
|
|
||||||
public void handleResult(Object result) {
|
public void handleResult(Object result) {
|
||||||
@@ -300,13 +299,13 @@ public final class WebAsyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startAsyncProcessing(Object... context) {
|
private void startAsyncProcessing(Object[] processingContext) {
|
||||||
|
|
||||||
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest was not set");
|
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
|
||||||
this.asyncWebRequest.startAsync();
|
this.asyncWebRequest.startAsync();
|
||||||
|
|
||||||
this.concurrentResult = null;
|
this.concurrentResult = null;
|
||||||
this.concurrentResultContext = context;
|
this.concurrentResultContext = processingContext;
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
|
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
|
||||||
@@ -315,11 +314,38 @@ public final class WebAsyncManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an {@link WebAsyncThreadInitializer} for the current request. It may
|
||||||
|
* later be accessed and applied via {@link #initializeAsyncThread(String)}
|
||||||
|
* and will also be used to initialize and reset threads for concurrent handler execution.
|
||||||
|
* @param key a unique the key under which to keep the initializer
|
||||||
|
* @param initializer the initializer instance
|
||||||
|
*/
|
||||||
|
public void registerAsyncThreadInitializer(Object key, WebAsyncThreadInitializer initializer) {
|
||||||
|
Assert.notNull(initializer, "WebAsyncThreadInitializer must not be null");
|
||||||
|
this.threadInitializers.put(key, initializer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A contract for initializing and resetting a thread.
|
* Invoke the {@linkplain WebAsyncThreadInitializer#initialize() initialize()}
|
||||||
|
* method of the named {@link WebAsyncThreadInitializer}.
|
||||||
|
* @param key the key under which the initializer was registered
|
||||||
|
* @return whether an initializer was found and applied
|
||||||
*/
|
*/
|
||||||
public interface AsyncThreadInitializer {
|
public boolean initializeAsyncThread(Object key) {
|
||||||
|
WebAsyncThreadInitializer initializer = this.threadInitializers.get(key);
|
||||||
|
if (initializer != null) {
|
||||||
|
initializer.initialize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize and reset thread-bound variables.
|
||||||
|
*/
|
||||||
|
public interface WebAsyncThreadInitializer {
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Support for asynchronous request processing.
|
||||||
|
*
|
||||||
|
* @since 3.2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.springframework.web.context.request.async;
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ public class DeferredResultTests {
|
|||||||
@Test
|
@Test
|
||||||
public void hasTimeout() {
|
public void hasTimeout() {
|
||||||
assertFalse(new DeferredResult<String>().hasTimeoutResult());
|
assertFalse(new DeferredResult<String>().hasTimeoutResult());
|
||||||
assertTrue(new DeferredResult<String>("timed out").hasTimeoutResult());
|
assertTrue(new DeferredResult<String>(null, "timed out").hasTimeoutResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -117,7 +117,7 @@ public class DeferredResultTests {
|
|||||||
handler.handleResult("timed out");
|
handler.handleResult("timed out");
|
||||||
replay(handler);
|
replay(handler);
|
||||||
|
|
||||||
DeferredResult<String> result = new DeferredResult<String>("timed out");
|
DeferredResult<String> result = new DeferredResult<String>(null, "timed out");
|
||||||
result.setResultHandler(handler);
|
result.setResultHandler(handler);
|
||||||
|
|
||||||
assertTrue(result.applyTimeoutResult());
|
assertTrue(result.applyTimeoutResult());
|
||||||
|
|||||||
@@ -135,4 +135,10 @@ public class StandardServletAsyncWebRequestTests {
|
|||||||
verify(timeoutHandler);
|
verify(timeoutHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected=IllegalStateException.class)
|
||||||
|
public void setTimeoutDuringConcurrentHandling() {
|
||||||
|
this.asyncRequest.startAsync();
|
||||||
|
this.asyncRequest.setTimeout(25L);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,26 +16,26 @@
|
|||||||
|
|
||||||
package org.springframework.web.context.request.async;
|
package org.springframework.web.context.request.async;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.easymock.EasyMock.createMock;
|
||||||
|
import static org.easymock.EasyMock.createStrictMock;
|
||||||
|
import static org.easymock.EasyMock.expect;
|
||||||
|
import static org.easymock.EasyMock.notNull;
|
||||||
|
import static org.easymock.EasyMock.replay;
|
||||||
|
import static org.easymock.EasyMock.reset;
|
||||||
|
import static org.easymock.EasyMock.verify;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertSame;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
|
||||||
import org.springframework.web.context.request.ServletWebRequest;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,168 +47,140 @@ public class WebAsyncManagerTests {
|
|||||||
|
|
||||||
private WebAsyncManager asyncManager;
|
private WebAsyncManager asyncManager;
|
||||||
|
|
||||||
private MockHttpServletRequest request;
|
private AsyncWebRequest asyncWebRequest;
|
||||||
|
|
||||||
private StubAsyncWebRequest stubAsyncWebRequest;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.request = new MockHttpServletRequest();
|
this.asyncManager = AsyncWebUtils.getAsyncManager(new MockHttpServletRequest());
|
||||||
this.stubAsyncWebRequest = new StubAsyncWebRequest(this.request, new MockHttpServletResponse());
|
|
||||||
|
|
||||||
this.asyncManager = AsyncWebUtils.getAsyncManager(this.request);
|
|
||||||
this.asyncManager.setTaskExecutor(new SyncTaskExecutor());
|
this.asyncManager.setTaskExecutor(new SyncTaskExecutor());
|
||||||
this.asyncManager.setAsyncWebRequest(this.stubAsyncWebRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
this.asyncWebRequest = createStrictMock(AsyncWebRequest.class);
|
||||||
public void getForCurrentRequest() throws Exception {
|
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
|
||||||
assertNotNull(this.asyncManager);
|
replay(this.asyncWebRequest);
|
||||||
assertSame(this.asyncManager, AsyncWebUtils.getAsyncManager(this.request));
|
|
||||||
assertSame(this.asyncManager, this.request.getAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE));
|
this.asyncManager.setAsyncWebRequest(this.asyncWebRequest);
|
||||||
|
|
||||||
|
verify(this.asyncWebRequest);
|
||||||
|
reset(this.asyncWebRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isConcurrentHandlingStarted() {
|
public void isConcurrentHandlingStarted() {
|
||||||
|
|
||||||
|
expect(this.asyncWebRequest.isAsyncStarted()).andReturn(false);
|
||||||
|
replay(this.asyncWebRequest);
|
||||||
|
|
||||||
assertFalse(this.asyncManager.isConcurrentHandlingStarted());
|
assertFalse(this.asyncManager.isConcurrentHandlingStarted());
|
||||||
|
|
||||||
this.stubAsyncWebRequest.startAsync();
|
verify(this.asyncWebRequest);
|
||||||
|
reset(this.asyncWebRequest);
|
||||||
|
|
||||||
|
expect(this.asyncWebRequest.isAsyncStarted()).andReturn(true);
|
||||||
|
replay(this.asyncWebRequest);
|
||||||
|
|
||||||
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
|
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
|
||||||
|
|
||||||
|
verify(this.asyncWebRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=IllegalArgumentException.class)
|
@Test(expected=IllegalArgumentException.class)
|
||||||
public void setAsyncWebRequestAfterAsyncStarted() {
|
public void setAsyncWebRequestAfterAsyncStarted() {
|
||||||
this.stubAsyncWebRequest.startAsync();
|
this.asyncWebRequest.startAsync();
|
||||||
this.asyncManager.setAsyncWebRequest(null);
|
this.asyncManager.setAsyncWebRequest(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startCallableChainProcessing() throws Exception {
|
public void startCallableProcessing() throws Exception {
|
||||||
|
|
||||||
|
WebAsyncThreadInitializer initializer = createStrictMock(WebAsyncThreadInitializer.class);
|
||||||
|
initializer.initialize();
|
||||||
|
initializer.reset();
|
||||||
|
replay(initializer);
|
||||||
|
|
||||||
|
this.asyncWebRequest.startAsync();
|
||||||
|
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
|
||||||
|
this.asyncWebRequest.dispatch();
|
||||||
|
replay(this.asyncWebRequest);
|
||||||
|
|
||||||
|
this.asyncManager.registerAsyncThreadInitializer("testInitializer", initializer);
|
||||||
this.asyncManager.startCallableProcessing(new Callable<Object>() {
|
this.asyncManager.startCallableProcessing(new Callable<Object>() {
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
|
verify(initializer, this.asyncWebRequest);
|
||||||
assertTrue(this.stubAsyncWebRequest.isDispatched());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startCallableChainProcessingStaleRequest() {
|
public void startCallableProcessingAsyncTask() {
|
||||||
this.stubAsyncWebRequest.setAsyncComplete(true);
|
|
||||||
this.asyncManager.startCallableProcessing(new Callable<Object>() {
|
|
||||||
public Object call() throws Exception {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
assertFalse(this.stubAsyncWebRequest.isDispatched());
|
AsyncTaskExecutor executor = createMock(AsyncTaskExecutor.class);
|
||||||
|
expect(executor.submit((Runnable) notNull())).andReturn(null);
|
||||||
|
replay(executor);
|
||||||
|
|
||||||
|
this.asyncWebRequest.setTimeout(1000L);
|
||||||
|
this.asyncWebRequest.startAsync();
|
||||||
|
replay(this.asyncWebRequest);
|
||||||
|
|
||||||
|
AsyncTask asyncTask = new AsyncTask(1000L, executor, createMock(Callable.class));
|
||||||
|
this.asyncManager.startCallableProcessing(asyncTask);
|
||||||
|
|
||||||
|
verify(executor, this.asyncWebRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startCallableChainProcessingCallableRequired() {
|
public void startCallableProcessingNullCallable() {
|
||||||
try {
|
try {
|
||||||
this.asyncManager.startCallableProcessing(null);
|
this.asyncManager.startCallableProcessing((Callable<?>) null);
|
||||||
fail("Expected exception");
|
fail("Expected exception");
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException ex) {
|
catch (IllegalArgumentException ex) {
|
||||||
assertEquals(ex.getMessage(), "Callable is required");
|
assertEquals(ex.getMessage(), "Callable must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startCallableChainProcessingAsyncWebRequestRequired() {
|
public void startCallableProcessingNullRequest() {
|
||||||
this.request.removeAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE);
|
WebAsyncManager manager = AsyncWebUtils.getAsyncManager(new MockHttpServletRequest());
|
||||||
this.asyncManager = AsyncWebUtils.getAsyncManager(this.request);
|
|
||||||
try {
|
try {
|
||||||
this.asyncManager.startCallableProcessing(new Callable<Object>() {
|
manager.startCallableProcessing(new Callable<Object>() {
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
return null;
|
return 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
fail("Expected exception");
|
fail("Expected exception");
|
||||||
}
|
}
|
||||||
catch (IllegalStateException ex) {
|
catch (IllegalStateException ex) {
|
||||||
assertEquals(ex.getMessage(), "AsyncWebRequest was not set");
|
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startDeferredResultProcessing() throws Exception {
|
public void startDeferredResultProcessing() throws Exception {
|
||||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
|
||||||
|
this.asyncWebRequest.setTimeout(1000L);
|
||||||
|
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
|
||||||
|
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull());
|
||||||
|
this.asyncWebRequest.startAsync();
|
||||||
|
replay(this.asyncWebRequest);
|
||||||
|
|
||||||
|
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(1000L, 10);
|
||||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
||||||
|
|
||||||
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
|
verify(this.asyncWebRequest);
|
||||||
|
reset(this.asyncWebRequest);
|
||||||
|
|
||||||
|
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
|
||||||
|
this.asyncWebRequest.dispatch();
|
||||||
|
replay(this.asyncWebRequest);
|
||||||
|
|
||||||
deferredResult.setResult(25);
|
deferredResult.setResult(25);
|
||||||
|
|
||||||
assertEquals(25, this.asyncManager.getConcurrentResult());
|
assertEquals(25, this.asyncManager.getConcurrentResult());
|
||||||
|
verify(this.asyncWebRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void startDeferredResultProcessingStaleRequest() throws Exception {
|
|
||||||
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
|
|
||||||
this.asyncManager.startDeferredResultProcessing(deferredResult);
|
|
||||||
|
|
||||||
this.stubAsyncWebRequest.setAsyncComplete(true);
|
|
||||||
assertFalse(deferredResult.setResult(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void startDeferredResultProcessingDeferredResultRequired() {
|
|
||||||
try {
|
|
||||||
this.asyncManager.startDeferredResultProcessing(null);
|
|
||||||
fail("Expected exception");
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException ex) {
|
|
||||||
assertThat(ex.getMessage(), containsString("DeferredResult is required"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static class StubAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest {
|
|
||||||
|
|
||||||
private boolean asyncStarted;
|
|
||||||
|
|
||||||
private boolean dispatched;
|
|
||||||
|
|
||||||
private boolean asyncComplete;
|
|
||||||
|
|
||||||
public StubAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
super(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimeout(Long timeout) { }
|
|
||||||
|
|
||||||
public void setTimeoutHandler(Runnable runnable) { }
|
|
||||||
|
|
||||||
public void startAsync() {
|
|
||||||
this.asyncStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAsyncStarted() {
|
|
||||||
return this.asyncStarted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispatch() {
|
|
||||||
this.dispatched = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDispatched() {
|
|
||||||
return dispatched;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAsyncComplete(boolean asyncComplete) {
|
|
||||||
this.asyncComplete = asyncComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAsyncComplete() {
|
|
||||||
return this.asyncComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addCompletionHandler(Runnable runnable) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private static class SyncTaskExecutor extends SimpleAsyncTaskExecutor {
|
private static class SyncTaskExecutor extends SimpleAsyncTaskExecutor {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import org.springframework.web.context.request.RequestContextHolder;
|
|||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import org.springframework.web.context.request.async.AsyncWebUtils;
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
|
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
|
||||||
import org.springframework.web.context.support.ServletRequestHandledEvent;
|
import org.springframework.web.context.support.ServletRequestHandledEvent;
|
||||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||||
import org.springframework.web.context.support.XmlWebApplicationContext;
|
import org.springframework.web.context.support.XmlWebApplicationContext;
|
||||||
@@ -991,9 +991,9 @@ public abstract class FrameworkServlet extends HttpServletBean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncThreadInitializer createAsyncThreadInitializer(final HttpServletRequest request) {
|
private WebAsyncThreadInitializer createAsyncThreadInitializer(final HttpServletRequest request) {
|
||||||
|
|
||||||
return new AsyncThreadInitializer() {
|
return new WebAsyncThreadInitializer() {
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request));
|
initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.mvc.method.annotation;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.web.context.request.NativeWebRequest;
|
|
||||||
import org.springframework.web.context.request.async.DeferredResult;
|
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
|
||||||
import org.springframework.web.context.request.async.AsyncWebUtils;
|
|
||||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
|
||||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles return values of type {@link Callable} and {@link DeferredResult}.
|
|
||||||
*
|
|
||||||
* <p>This handler does not have a defined behavior for {@code null} return
|
|
||||||
* values and will raise an {@link IllegalArgumentException}.
|
|
||||||
*
|
|
||||||
* @author Rossen Stoyanchev
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
public class AsyncMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
|
|
||||||
|
|
||||||
public boolean supportsReturnType(MethodParameter returnType) {
|
|
||||||
Class<?> paramType = returnType.getParameterType();
|
|
||||||
return Callable.class.isAssignableFrom(paramType) || DeferredResult.class.isAssignableFrom(paramType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void handleReturnValue(Object returnValue,
|
|
||||||
MethodParameter returnType, ModelAndViewContainer mavContainer,
|
|
||||||
NativeWebRequest webRequest) throws Exception {
|
|
||||||
|
|
||||||
Assert.notNull(returnValue, "A Callable or a DeferredValue is required");
|
|
||||||
|
|
||||||
Class<?> paramType = returnType.getParameterType();
|
|
||||||
ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class);
|
|
||||||
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(servletRequest);
|
|
||||||
|
|
||||||
if (Callable.class.isAssignableFrom(paramType)) {
|
|
||||||
asyncManager.startCallableProcessing((Callable<Object>) returnValue, mavContainer);
|
|
||||||
}
|
|
||||||
else if (DeferredResult.class.isAssignableFrom(paramType)) {
|
|
||||||
asyncManager.startDeferredResultProcessing((DeferredResult<?>) returnValue, mavContainer);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// should never happen..
|
|
||||||
Method method = returnType.getMethod();
|
|
||||||
throw new UnsupportedOperationException("Unknown return value: " + paramType + " in method: " + method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.mvc.method.annotation;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.web.context.request.NativeWebRequest;
|
||||||
|
import org.springframework.web.context.request.async.AsyncTask;
|
||||||
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
|
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||||
|
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles return values of type {@link AsyncTask}.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 3.2
|
||||||
|
*/
|
||||||
|
public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||||
|
|
||||||
|
private final BeanFactory beanFactory;
|
||||||
|
|
||||||
|
|
||||||
|
public AsyncTaskMethodReturnValueHandler(BeanFactory beanFactory) {
|
||||||
|
this.beanFactory = beanFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsReturnType(MethodParameter returnType) {
|
||||||
|
Class<?> paramType = returnType.getParameterType();
|
||||||
|
return AsyncTask.class.isAssignableFrom(paramType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleReturnValue(Object returnValue,
|
||||||
|
MethodParameter returnType, ModelAndViewContainer mavContainer,
|
||||||
|
NativeWebRequest webRequest) throws Exception {
|
||||||
|
|
||||||
|
if (returnValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncTask asyncTask = (AsyncTask) returnValue;
|
||||||
|
asyncTask.setBeanFactory(this.beanFactory);
|
||||||
|
AsyncWebUtils.getAsyncManager(webRequest).startCallableProcessing(asyncTask.getCallable(), mavContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.mvc.method.annotation;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.web.context.request.NativeWebRequest;
|
||||||
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
|
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||||
|
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles return values of type {@link Callable}.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 3.2
|
||||||
|
*/
|
||||||
|
public class CallableMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||||
|
|
||||||
|
public boolean supportsReturnType(MethodParameter returnType) {
|
||||||
|
Class<?> paramType = returnType.getParameterType();
|
||||||
|
return Callable.class.isAssignableFrom(paramType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleReturnValue(Object returnValue,
|
||||||
|
MethodParameter returnType, ModelAndViewContainer mavContainer,
|
||||||
|
NativeWebRequest webRequest) throws Exception {
|
||||||
|
|
||||||
|
if (returnValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Callable<?> callable = (Callable<?>) returnValue;
|
||||||
|
AsyncWebUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.mvc.method.annotation;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.web.context.request.NativeWebRequest;
|
||||||
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
|
import org.springframework.web.context.request.async.DeferredResult;
|
||||||
|
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||||
|
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles return values of type {@link DeferredResult}.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 3.2
|
||||||
|
*/
|
||||||
|
public class DeferredResultMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||||
|
|
||||||
|
public boolean supportsReturnType(MethodParameter returnType) {
|
||||||
|
Class<?> paramType = returnType.getParameterType();
|
||||||
|
return DeferredResult.class.isAssignableFrom(paramType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleReturnValue(Object returnValue,
|
||||||
|
MethodParameter returnType, ModelAndViewContainer mavContainer,
|
||||||
|
NativeWebRequest webRequest) throws Exception {
|
||||||
|
|
||||||
|
if (returnValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
|
||||||
|
AsyncWebUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.web.servlet.mvc.method.annotation;
|
package org.springframework.web.servlet.mvc.method.annotation;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -27,13 +26,11 @@ import java.util.Map.Entry;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import javax.xml.transform.Source;
|
import javax.xml.transform.Source;
|
||||||
|
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
@@ -50,7 +47,6 @@ import org.springframework.http.converter.StringHttpMessageConverter;
|
|||||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||||
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
|
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
|
||||||
import org.springframework.ui.ModelMap;
|
import org.springframework.ui.ModelMap;
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||||
import org.springframework.web.accept.ContentNegotiationManager;
|
import org.springframework.web.accept.ContentNegotiationManager;
|
||||||
@@ -68,7 +64,6 @@ import org.springframework.web.context.request.ServletWebRequest;
|
|||||||
import org.springframework.web.context.request.WebRequest;
|
import org.springframework.web.context.request.WebRequest;
|
||||||
import org.springframework.web.context.request.async.AsyncWebRequest;
|
import org.springframework.web.context.request.async.AsyncWebRequest;
|
||||||
import org.springframework.web.context.request.async.AsyncWebUtils;
|
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||||
import org.springframework.web.context.request.async.NoSupportAsyncWebRequest;
|
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.method.HandlerMethodSelector;
|
import org.springframework.web.method.HandlerMethodSelector;
|
||||||
@@ -552,7 +547,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||||||
handlers.add(new ModelMethodProcessor());
|
handlers.add(new ModelMethodProcessor());
|
||||||
handlers.add(new ViewMethodReturnValueHandler());
|
handlers.add(new ViewMethodReturnValueHandler());
|
||||||
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
|
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
|
||||||
handlers.add(new AsyncMethodReturnValueHandler());
|
handlers.add(new CallableMethodReturnValueHandler());
|
||||||
|
handlers.add(new DeferredResultMethodReturnValueHandler());
|
||||||
|
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
|
||||||
|
|
||||||
// Annotation-based return value types
|
// Annotation-based return value types
|
||||||
handlers.add(new ModelAttributeMethodProcessor(false));
|
handlers.add(new ModelAttributeMethodProcessor(false));
|
||||||
@@ -690,7 +687,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||||||
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
|
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
|
||||||
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
|
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
|
||||||
|
|
||||||
AsyncWebRequest asyncWebRequest = createAsyncWebRequest(request, response);
|
AsyncWebRequest asyncWebRequest = AsyncWebUtils.createAsyncWebRequest(request, response);
|
||||||
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
|
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
|
||||||
|
|
||||||
final WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
|
final WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
|
||||||
@@ -700,7 +697,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||||||
if (asyncManager.hasConcurrentResult()) {
|
if (asyncManager.hasConcurrentResult()) {
|
||||||
Object result = asyncManager.getConcurrentResult();
|
Object result = asyncManager.getConcurrentResult();
|
||||||
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
|
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
|
||||||
asyncManager.resetConcurrentResult();
|
asyncManager.clearConcurrentResult();
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Found concurrent result value [" + result + "]");
|
logger.debug("Found concurrent result value [" + result + "]");
|
||||||
@@ -802,23 +799,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||||||
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
|
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
return ClassUtils.hasMethod(ServletRequest.class, "startAsync") ?
|
|
||||||
createStandardServletAsyncWebRequest(request, response) : new NoSupportAsyncWebRequest(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AsyncWebRequest createStandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
try {
|
|
||||||
String className = "org.springframework.web.context.request.async.StandardServletAsyncWebRequest";
|
|
||||||
Class<?> clazz = ClassUtils.forName(className, this.getClass().getClassLoader());
|
|
||||||
Constructor<?> constructor = clazz.getConstructor(HttpServletRequest.class, HttpServletResponse.class);
|
|
||||||
return (AsyncWebRequest) BeanUtils.instantiateClass(constructor, request, response);
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
throw new IllegalStateException("Failed to instantiate StandardServletAsyncWebRequest", t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
|
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
|
||||||
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
|
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user