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:
parent
4407f6a4c0
commit
cdab04a032
|
|
@ -34,7 +34,7 @@ import org.springframework.util.Assert;
|
|||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.async.AsyncWebUtils;
|
||||
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.filter.OncePerRequestFilter;
|
||||
|
||||
|
|
@ -195,13 +195,13 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||
participate = true;
|
||||
}
|
||||
else {
|
||||
if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) {
|
||||
if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) {
|
||||
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
|
||||
Session session = getSession(sessionFactory);
|
||||
SessionHolder sessionHolder = new SessionHolder(session);
|
||||
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
||||
|
||||
AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
|
||||
WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
|
||||
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) {
|
||||
|
||||
return new AsyncThreadInitializer() {
|
||||
return new WebAsyncThreadInitializer() {
|
||||
public void initialize() {
|
||||
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.AsyncWebUtils;
|
||||
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
|
||||
|
|
@ -147,7 +147,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
|
|||
String participateAttributeName = getParticipateAttributeName();
|
||||
|
||||
if (asyncManager.hasConcurrentResult()) {
|
||||
if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) {
|
||||
if (asyncManager.initializeAsyncThread(participateAttributeName)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
|
|||
SessionHolder sessionHolder = new SessionHolder(session);
|
||||
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
||||
|
||||
AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
|
||||
WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
|
||||
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
|
||||
}
|
||||
else {
|
||||
|
|
@ -261,8 +261,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
|
|||
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
|
||||
}
|
||||
|
||||
private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
|
||||
return new AsyncThreadInitializer() {
|
||||
private WebAsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
|
||||
return new WebAsyncThreadInitializer() {
|
||||
public void initialize() {
|
||||
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.request.async.AsyncWebUtils;
|
||||
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.filter.OncePerRequestFilter;
|
||||
|
||||
|
|
@ -126,13 +126,13 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
|
|||
participate = true;
|
||||
}
|
||||
else {
|
||||
if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) {
|
||||
if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) {
|
||||
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
|
||||
Session session = openSession(sessionFactory);
|
||||
SessionHolder sessionHolder = new SessionHolder(session);
|
||||
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
||||
|
||||
AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
|
||||
WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
|
||||
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) {
|
||||
|
||||
return new AsyncThreadInitializer() {
|
||||
return new WebAsyncThreadInitializer() {
|
||||
public void initialize() {
|
||||
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.AsyncWebUtils;
|
||||
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
|
||||
|
|
@ -109,7 +109,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
|
|||
String participateAttributeName = getParticipateAttributeName();
|
||||
|
||||
if (asyncManager.hasConcurrentResult()) {
|
||||
if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) {
|
||||
if (asyncManager.initializeAsyncThread(participateAttributeName)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
|
|||
SessionHolder sessionHolder = new SessionHolder(session);
|
||||
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
|
||||
|
||||
AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
|
||||
WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
|
||||
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
|
||||
}
|
||||
}
|
||||
|
|
@ -200,8 +200,8 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
|
|||
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
|
||||
}
|
||||
|
||||
private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
|
||||
return new AsyncThreadInitializer() {
|
||||
private WebAsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
|
||||
return new WebAsyncThreadInitializer() {
|
||||
public void initialize() {
|
||||
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.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Provide a Runnable to invoke on timeout.
|
||||
* Set a handler to be invoked if concurrent processing times out.
|
||||
*/
|
||||
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
|
||||
* processing thread exits, the response remains open for further processing
|
||||
|
|
@ -70,6 +68,11 @@ public interface AsyncWebRequest extends NativeWebRequest {
|
|||
*/
|
||||
boolean isDispatched();
|
||||
|
||||
/**
|
||||
* Add a Runnable to be invoked when request processing completes.
|
||||
*/
|
||||
void addCompletionHandler(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Whether asynchronous processing has completed.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,8 +15,14 @@
|
|||
*/
|
||||
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.WebRequest;
|
||||
|
||||
|
|
@ -57,4 +63,30 @@ public abstract class AsyncWebUtils {
|
|||
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;
|
||||
|
||||
/**
|
||||
* {@code DeferredResult} provides an alternative to returning a {@link Callable}
|
||||
* for asynchronous request processing. While with a Callable, a thread is used
|
||||
* to execute it on behalf of the application, with a DeferredResult the application
|
||||
* sets the result whenever it needs to from a thread of its choice.
|
||||
* {@code DeferredResult} provides an alternative to using a {@link Callable}
|
||||
* for asynchronous request processing. While a Callable is executed concurrently
|
||||
* on behalf of the application, with a DeferredResult the application can produce
|
||||
* the result from a thread of its choice.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
|
|
@ -38,32 +38,53 @@ public final class DeferredResult<T> {
|
|||
|
||||
private static final Object RESULT_NONE = new Object();
|
||||
|
||||
private Object result = RESULT_NONE;
|
||||
|
||||
private final Object timeoutResult;
|
||||
|
||||
private final AtomicBoolean expired = new AtomicBoolean(false);
|
||||
private final Long timeout;
|
||||
|
||||
private DeferredResultHandler resultHandler;
|
||||
|
||||
private Object result = RESULT_NONE;
|
||||
|
||||
private final AtomicBoolean expired = new AtomicBoolean(false);
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
|
||||
/**
|
||||
* Create a DeferredResult instance.
|
||||
* Create a DeferredResult.
|
||||
*/
|
||||
public DeferredResult() {
|
||||
this(RESULT_NONE);
|
||||
this(null, RESULT_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DeferredResult with a default result to use in case of a timeout.
|
||||
* @param timeoutResult the result to use
|
||||
* Create a DeferredResult with a timeout.
|
||||
* @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.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.
|
||||
*/
|
||||
public void setTimeout(Long timeout) {
|
||||
Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress");
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
|
|
@ -145,6 +146,9 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a SERVICE_UNAVAILABLE (503).
|
||||
*/
|
||||
private class DefaultTimeoutHandler implements Runnable {
|
||||
|
||||
public void run() {
|
||||
|
|
|
|||
|
|
@ -38,25 +38,16 @@ import org.springframework.web.util.UrlPathHelper;
|
|||
* 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).
|
||||
* 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 #startDeferredResultProcessing(DeferredResult, Object...) startDeferredResultProcessing}
|
||||
* both of which will process in a separate thread (T2).
|
||||
* After the start of concurrent handling {@link #isConcurrentHandlingStarted()}
|
||||
* returns "true" and this can be used by classes involved in processing on the
|
||||
* main thread (T1) quickly and with very minimal processing.
|
||||
* both of which produce a result in a separate thread (T2). The result is saved
|
||||
* and the request dispatched to the container, to resume processing with the saved
|
||||
* result in a third thread (T3). Within the dispatched thread (T3), the saved
|
||||
* 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
|
||||
* {@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
|
||||
* <p>TODO .. Servlet 3 config
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
|
|
@ -73,42 +64,38 @@ public final class WebAsyncManager {
|
|||
|
||||
private static final Log logger = LogFactory.getLog(WebAsyncManager.class);
|
||||
|
||||
|
||||
private AsyncWebRequest asyncWebRequest;
|
||||
|
||||
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[] concurrentResultContext;
|
||||
|
||||
private final Map<Object, WebAsyncThreadInitializer> threadInitializers = new LinkedHashMap<Object, WebAsyncThreadInitializer>();
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
|
||||
/**
|
||||
* Package private constructor
|
||||
* @see AsyncWebUtils
|
||||
* Package private constructor.
|
||||
* @see AsyncWebUtils#getAsyncManager(javax.servlet.ServletRequest)
|
||||
* @see AsyncWebUtils#getAsyncManager(org.springframework.web.context.request.WebRequest)
|
||||
*/
|
||||
WebAsyncManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure an AsyncTaskExecutor for use with {@link #startCallableProcessing(Callable)}.
|
||||
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used. Applications
|
||||
* are advised to provide a TaskExecutor configured for production use.
|
||||
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setAsyncTaskExecutor
|
||||
*/
|
||||
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
|
||||
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
|
||||
* Configure the {@link AsyncWebRequest} to use. This property may be
|
||||
* set more than once during a single request to accurately reflect the
|
||||
* current state of the request (e.g. following a forward, request/response
|
||||
* wrapping, etc). However, it should not be set while concurrent handling is
|
||||
* in progress, i.e. while {@link #isConcurrentHandlingStarted()} is {@code true}.
|
||||
* @param asyncWebRequest the web request to use
|
||||
*/
|
||||
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");
|
||||
this.asyncWebRequest = asyncWebRequest;
|
||||
this.asyncWebRequest.addCompletionHandler(new Runnable() {
|
||||
|
|
@ -119,18 +106,27 @@ public final class WebAsyncManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether the handler for the current request is executed concurrently.
|
||||
* Once concurrent handling is done, the result is saved, and the request
|
||||
* dispatched again to resume processing where the result of concurrent
|
||||
* handling is available via {@link #getConcurrentResult()}.
|
||||
* Configure an AsyncTaskExecutor for use with concurrent processing via
|
||||
* {@link #startCallableProcessing(Callable, Object...)}.
|
||||
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
|
||||
*/
|
||||
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() {
|
||||
return ((this.asyncWebRequest != null) && (this.asyncWebRequest.isAsyncStarted()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current thread was dispatched to continue processing the result
|
||||
* of concurrent handler execution.
|
||||
* Whether the request was dispatched to resume processing the result of
|
||||
* concurrent handling.
|
||||
*/
|
||||
public boolean hasConcurrentResult() {
|
||||
|
||||
|
|
@ -142,84 +138,58 @@ public final class WebAsyncManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the result of concurrent handler execution. This may be an Object
|
||||
* value on successful return or an {@code Exception} or {@code Throwable}.
|
||||
* Provides access to the result from concurrent handling.
|
||||
* @return an Object, possibly an {@code Exception} or {@code Throwable} if
|
||||
* concurrent handling raised one.
|
||||
*/
|
||||
public Object getConcurrentResult() {
|
||||
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() {
|
||||
return this.concurrentResultContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the {@linkplain #getConcurrentResult() concurrentResult} and the
|
||||
* Clear {@linkplain #getConcurrentResult() concurrentResult} and
|
||||
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
|
||||
*/
|
||||
public void resetConcurrentResult() {
|
||||
public void clearConcurrentResult() {
|
||||
this.concurrentResult = RESULT_NONE;
|
||||
this.concurrentResultContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an {@link AsyncThreadInitializer} with the WebAsyncManager instance
|
||||
* for the current request. It may later be accessed and applied via
|
||||
* {@link #applyAsyncThreadInitializer(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, 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".
|
||||
* Start concurrent request processing and execute the given task with an
|
||||
* {@link #setTaskExecutor(AsyncTaskExecutor) AsyncTaskExecutor}. The result
|
||||
* from the task execution is saved and the request dispatched in order to
|
||||
* resume processing of that result. If the task raises an Exception then
|
||||
* the saved result will be the raised Exception.
|
||||
*
|
||||
* @param callable a unit of work to be executed asynchronously
|
||||
* @param processingContext additional context to save for later access via
|
||||
* {@link #getConcurrentResultContext()}
|
||||
* @param processingContext additional context to save that can be accessed
|
||||
* via {@link #getConcurrentResultContext()}
|
||||
*
|
||||
* @see #getConcurrentResult()
|
||||
* @see #getConcurrentResultContext()
|
||||
*/
|
||||
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
|
||||
Assert.notNull(callable, "Callable is required");
|
||||
Assert.notNull(callable, "Callable must not be null");
|
||||
|
||||
startAsyncProcessing(processingContext);
|
||||
|
||||
this.taskExecutor.submit(new Runnable() {
|
||||
|
||||
public void run() {
|
||||
List<AsyncThreadInitializer> initializers =
|
||||
new ArrayList<AsyncThreadInitializer>(threadInitializers.values());
|
||||
List<WebAsyncThreadInitializer> initializers =
|
||||
new ArrayList<WebAsyncThreadInitializer>(threadInitializers.values());
|
||||
|
||||
try {
|
||||
for (AsyncThreadInitializer initializer : initializers) {
|
||||
for (WebAsyncThreadInitializer initializer : initializers) {
|
||||
initializer.initialize();
|
||||
}
|
||||
concurrentResult = callable.call();
|
||||
|
|
@ -229,7 +199,7 @@ public final class WebAsyncManager {
|
|||
}
|
||||
finally {
|
||||
Collections.reverse(initializers);
|
||||
for (AsyncThreadInitializer initializer : initializers) {
|
||||
for (WebAsyncThreadInitializer initializer : initializers) {
|
||||
initializer.reset();
|
||||
}
|
||||
}
|
||||
|
|
@ -250,24 +220,51 @@ public final class WebAsyncManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Initialize the given given {@link DeferredResult} so that whenever the
|
||||
* DeferredResult is set, 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 the result. In the dispatch
|
||||
* thread, the result value can be accessed via {@link #getConcurrentResult()}.
|
||||
* <p>The method returns immediately and it's up to the caller to set the
|
||||
* DeferredResult. Subsequent calls to {@link #isConcurrentHandlingStarted()}
|
||||
* return "true" until after the dispatch when {@link #hasConcurrentResult()}
|
||||
* returns "true" and {@link #isConcurrentHandlingStarted()} is back to "false".
|
||||
* Use the given {@link AsyncTask} to configure the task executor as well as
|
||||
* the timeout value of the {@code AsyncWebRequest} before delegating to
|
||||
* {@link #startCallableProcessing(Callable, Object...)}.
|
||||
* @param asyncTask an asyncTask containing the target {@code Callable}
|
||||
* @param processingContext additional context to save that can be accessed
|
||||
* via {@link #getConcurrentResultContext()}
|
||||
*/
|
||||
public void startCallableProcessing(AsyncTask asyncTask, Object... processingContext) {
|
||||
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 processingContext additional context to save for later access via
|
||||
* {@link #getConcurrentResultContext()}
|
||||
* @param processingContext additional context to save that can be accessed
|
||||
* via {@link #getConcurrentResultContext()}
|
||||
*
|
||||
* @see #getConcurrentResult()
|
||||
* @see #getConcurrentResultContext()
|
||||
*/
|
||||
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() {
|
||||
public void run() {
|
||||
|
|
@ -283,6 +280,8 @@ public final class WebAsyncManager {
|
|||
});
|
||||
}
|
||||
|
||||
startAsyncProcessing(processingContext);
|
||||
|
||||
deferredResult.setResultHandler(new DeferredResultHandler() {
|
||||
|
||||
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.concurrentResult = null;
|
||||
this.concurrentResultContext = context;
|
||||
this.concurrentResultContext = processingContext;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
public void hasTimeout() {
|
||||
assertFalse(new DeferredResult<String>().hasTimeoutResult());
|
||||
assertTrue(new DeferredResult<String>("timed out").hasTimeoutResult());
|
||||
assertTrue(new DeferredResult<String>(null, "timed out").hasTimeoutResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -117,7 +117,7 @@ public class DeferredResultTests {
|
|||
handler.handleResult("timed out");
|
||||
replay(handler);
|
||||
|
||||
DeferredResult<String> result = new DeferredResult<String>("timed out");
|
||||
DeferredResult<String> result = new DeferredResult<String>(null, "timed out");
|
||||
result.setResultHandler(handler);
|
||||
|
||||
assertTrue(result.applyTimeoutResult());
|
||||
|
|
|
|||
|
|
@ -135,4 +135,10 @@ public class StandardServletAsyncWebRequestTests {
|
|||
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;
|
||||
|
||||
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.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.fail;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -47,168 +47,140 @@ public class WebAsyncManagerTests {
|
|||
|
||||
private WebAsyncManager asyncManager;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private StubAsyncWebRequest stubAsyncWebRequest;
|
||||
private AsyncWebRequest asyncWebRequest;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.stubAsyncWebRequest = new StubAsyncWebRequest(this.request, new MockHttpServletResponse());
|
||||
|
||||
this.asyncManager = AsyncWebUtils.getAsyncManager(this.request);
|
||||
this.asyncManager = AsyncWebUtils.getAsyncManager(new MockHttpServletRequest());
|
||||
this.asyncManager.setTaskExecutor(new SyncTaskExecutor());
|
||||
this.asyncManager.setAsyncWebRequest(this.stubAsyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getForCurrentRequest() throws Exception {
|
||||
assertNotNull(this.asyncManager);
|
||||
assertSame(this.asyncManager, AsyncWebUtils.getAsyncManager(this.request));
|
||||
assertSame(this.asyncManager, this.request.getAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE));
|
||||
this.asyncWebRequest = createStrictMock(AsyncWebRequest.class);
|
||||
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
|
||||
replay(this.asyncWebRequest);
|
||||
|
||||
this.asyncManager.setAsyncWebRequest(this.asyncWebRequest);
|
||||
|
||||
verify(this.asyncWebRequest);
|
||||
reset(this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isConcurrentHandlingStarted() {
|
||||
|
||||
expect(this.asyncWebRequest.isAsyncStarted()).andReturn(false);
|
||||
replay(this.asyncWebRequest);
|
||||
|
||||
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());
|
||||
|
||||
verify(this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void setAsyncWebRequestAfterAsyncStarted() {
|
||||
this.stubAsyncWebRequest.startAsync();
|
||||
this.asyncWebRequest.startAsync();
|
||||
this.asyncManager.setAsyncWebRequest(null);
|
||||
}
|
||||
|
||||
@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>() {
|
||||
public Object call() throws Exception {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
|
||||
assertTrue(this.stubAsyncWebRequest.isDispatched());
|
||||
verify(initializer, this.asyncWebRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableChainProcessingStaleRequest() {
|
||||
this.stubAsyncWebRequest.setAsyncComplete(true);
|
||||
this.asyncManager.startCallableProcessing(new Callable<Object>() {
|
||||
public Object call() throws Exception {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
public void startCallableProcessingAsyncTask() {
|
||||
|
||||
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
|
||||
public void startCallableChainProcessingCallableRequired() {
|
||||
public void startCallableProcessingNullCallable() {
|
||||
try {
|
||||
this.asyncManager.startCallableProcessing(null);
|
||||
this.asyncManager.startCallableProcessing((Callable<?>) null);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
assertEquals(ex.getMessage(), "Callable is required");
|
||||
assertEquals(ex.getMessage(), "Callable must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startCallableChainProcessingAsyncWebRequestRequired() {
|
||||
this.request.removeAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE);
|
||||
this.asyncManager = AsyncWebUtils.getAsyncManager(this.request);
|
||||
public void startCallableProcessingNullRequest() {
|
||||
WebAsyncManager manager = AsyncWebUtils.getAsyncManager(new MockHttpServletRequest());
|
||||
try {
|
||||
this.asyncManager.startCallableProcessing(new Callable<Object>() {
|
||||
manager.startCallableProcessing(new Callable<Object>() {
|
||||
public Object call() throws Exception {
|
||||
return null;
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
assertEquals(ex.getMessage(), "AsyncWebRequest was not set");
|
||||
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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")
|
||||
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.async.AsyncWebUtils;
|
||||
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.WebApplicationContextUtils;
|
||||
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() {
|
||||
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;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
|
@ -27,13 +26,11 @@ import java.util.Map.Entry;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.xml.transform.Source;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
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.XmlAwareFormHttpMessageConverter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
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.async.AsyncWebRequest;
|
||||
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.method.HandlerMethod;
|
||||
import org.springframework.web.method.HandlerMethodSelector;
|
||||
|
|
@ -552,7 +547,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
handlers.add(new ModelMethodProcessor());
|
||||
handlers.add(new ViewMethodReturnValueHandler());
|
||||
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
|
||||
handlers.add(new ModelAttributeMethodProcessor(false));
|
||||
|
|
@ -690,7 +687,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
|
||||
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
|
||||
|
||||
AsyncWebRequest asyncWebRequest = createAsyncWebRequest(request, response);
|
||||
AsyncWebRequest asyncWebRequest = AsyncWebUtils.createAsyncWebRequest(request, response);
|
||||
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
|
||||
|
||||
final WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
|
||||
|
|
@ -700,7 +697,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
if (asyncManager.hasConcurrentResult()) {
|
||||
Object result = asyncManager.getConcurrentResult();
|
||||
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
|
||||
asyncManager.resetConcurrentResult();
|
||||
asyncManager.clearConcurrentResult();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Found concurrent result value [" + result + "]");
|
||||
|
|
@ -802,23 +799,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
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,
|
||||
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue