SPR-6464 Drop @FlashAttributes, add ResponseContext, ViewResponse, and RedirectResponse types for annotated controllers to use to prepare a redirect response with flash attributes; Add FlashMap and FlashMapManager and update DispatcherServlet to discover and invoke the FlashMapManager.

This commit is contained in:
Rossen Stoyanchev
2011-08-08 14:00:07 +00:00
parent 11597c906d
commit 1df0cd9f20
28 changed files with 1171 additions and 724 deletions

View File

@@ -29,6 +29,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -52,6 +53,7 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.NestedServletException;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
@@ -178,6 +180,11 @@ public class DispatcherServlet extends FrameworkServlet {
*/
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
/**
* Well-known name for the FlashMapManager object in the bean factory for this namespace.
*/
public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";
/**
* Request attribute to hold the current web application context.
* Otherwise only the global web app context is obtainable by tags etc.
@@ -202,7 +209,7 @@ public class DispatcherServlet extends FrameworkServlet {
* @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource
*/
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
/** Log category to use when no mapped handler is found for a request. */
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
@@ -269,6 +276,9 @@ public class DispatcherServlet extends FrameworkServlet {
/** RequestToViewNameTranslator used by this servlet */
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMapManager used by this servlet */
private FlashMapManager flashMapManager;
/** List of ViewResolvers used by this servlet */
private List<ViewResolver> viewResolvers;
@@ -414,6 +424,7 @@ public class DispatcherServlet extends FrameworkServlet {
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
/**
@@ -659,6 +670,28 @@ public class DispatcherServlet extends FrameworkServlet {
}
}
/**
* Initialize the {@link FlashMapManager} used by this servlet instance.
* <p>If no implementation is configured then we default to DefaultFlashMapManager.
*/
private void initFlashMapManager(ApplicationContext context) {
try {
this.flashMapManager =
context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
if (logger.isDebugEnabled()) {
logger.debug("Using FlashMapManager [" + this.flashMapManager + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate FlashMapManager with name '" +
FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" + this.flashMapManager + "]");
}
}
}
/**
* Return this servlet's ThemeSource, if any; else return <code>null</code>.
* <p>Default is to return the WebApplicationContext as ThemeSource,
@@ -782,6 +815,8 @@ public class DispatcherServlet extends FrameworkServlet {
}
}
boolean flashInitialized = this.flashMapManager.requestStarted(request);
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
@@ -792,6 +827,9 @@ public class DispatcherServlet extends FrameworkServlet {
doDispatch(request, response);
}
finally {
if (flashInitialized) {
this.flashMapManager.requestCompleted(request);
}
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);

View File

@@ -20,3 +20,5 @@ org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.DefaultFlashMapManager

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
/**
* Stores attributes that need to be made available in the next request.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class FlashMap extends ModelMap {
private static final long serialVersionUID = 1L;
private final String key;
private final String keyParameterName;
private long expirationStartTime;
private int timeToLive;
/**
* Create a FlashMap with a unique key.
*/
public FlashMap(String key, String keyParameterName) {
Assert.notNull("The key is required", key);
Assert.notNull("The key parameter name is required", keyParameterName);
this.key = key;
this.keyParameterName = keyParameterName;
}
/**
* Create a FlashMap without a key.
*/
public FlashMap() {
this.key = null;
this.keyParameterName = null;
}
/**
* Return the key assigned to this FlashMap instance;
* or {@code null} if a unique key has not been assigned.
*/
public String getKey() {
return this.key;
}
/**
* Return the name of the request parameter to use when appending the flash
* key to a redirect URL.
*/
public String getKeyParameterName() {
return keyParameterName;
}
/**
* Start the expiration period for this instance. After the given number of
* seconds calls to {@link #isExpired()} will return "true".
* @param timeToLive the number of seconds before flash map expires
*/
public void startExpirationPeriod(int timeToLive) {
this.expirationStartTime = System.currentTimeMillis();
this.timeToLive = timeToLive;
}
/**
* Whether the flash map has expired depending on the number of seconds
* elapsed since the call to {@link #startExpirationPeriod}.
*/
public boolean isExpired() {
if (this.expirationStartTime != 0) {
return (System.currentTimeMillis() - this.expirationStartTime) > this.timeToLive * 1000;
}
else {
return false;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* A strategy interface for maintaining {@link FlashMap} instances in some
* underlying storage between two requests. This is typically used when
* redirecting from one URL to another.
*
* TODO ...
*
* @author Rossen Stoyanchev
* @since 3.1
*
* @see FlashMap
*/
public interface FlashMapManager {
/**
* Request attribute to hold the current request FlashMap.
* @see RequestContextUtils#getFlashMap
*/
public static final String CURRENT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".CURRENT_FLASH_MAP";
/**
* Request attribute to hold the FlashMap from the previous request.
*/
public static final String PREVIOUS_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".PREVIOUS_FLASH_MAP";
/**
* Perform flash storage tasks at the start of a new request:
* <ul>
* <li>Create a new FlashMap and make it available to the current request
* under the request attribute {@link #CURRENT_FLASH_MAP_ATTRIBUTE}.
* <li>Locate the FlashMap saved on the previous request and expose its
* contents as attributes in the current request.
* <li>Remove expired flash map instances.
* </ul>
*
* @param request the current request
*
* @return "true" if flash storage tasks were performed; "false" otherwise
* if the {@link #CURRENT_FLASH_MAP_ATTRIBUTE} request attribute exists.
*/
boolean requestStarted(HttpServletRequest request);
/**
* Access the current FlashMap through the {@link #CURRENT_FLASH_MAP_ATTRIBUTE}
* request attribute and if not empty, save it in the underlying storage. This
* method should be invoked after {@link #requestStarted} and if it returned "true".
*/
void requestCompleted(HttpServletRequest request);
}

View File

@@ -60,6 +60,7 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequ
import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.support.ResponseContext;
/**
* An {@link AbstractHandlerMethodExceptionResolver} that supports using {@link ExceptionHandler}-annotated methods
@@ -231,7 +232,9 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
}
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
exceptionHandler.invokeAndHandle(webRequest, mavContainer, ex);
ResponseContext responseContext = new ResponseContext(webRequest, mavContainer);
exceptionHandler.invokeAndHandle(webRequest, mavContainer, ex, responseContext);
if (!mavContainer.isResolveView()) {
return new ModelAndView();
@@ -239,7 +242,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
else {
ModelAndView mav = new ModelAndView().addAllObjects(mavContainer.getModel());
mav.setViewName(mavContainer.getViewName());
if (mavContainer.getView() != null) {
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
return mav;

View File

@@ -20,7 +20,6 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
@@ -47,13 +46,10 @@ import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.FlashStatus;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleFlashStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebDataBinderFactory;
@@ -61,7 +57,6 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.method.annotation.FlashAttributesHandler;
import org.springframework.web.method.annotation.ModelFactory;
import org.springframework.web.method.annotation.SessionAttributesHandler;
import org.springframework.web.method.annotation.support.ErrorsMethodArgumentResolver;
@@ -78,6 +73,8 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.LastModified;
@@ -94,6 +91,7 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletMode
import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.support.ResponseContext;
import org.springframework.web.util.WebUtils;
/**
@@ -149,18 +147,16 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache =
new ConcurrentHashMap<Class<?>, SessionAttributesHandler>();
private final Map<Class<?>, FlashAttributesHandler> flashAttributesHandlerCache =
new ConcurrentHashMap<Class<?>, FlashAttributesHandler>();
private HandlerMethodArgumentResolverComposite argumentResolvers;
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
private final Map<Class<?>, Set<Method>> initBinderMethodCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
private final Map<Class<?>, WebDataBinderFactory> dataBinderFactoryCache =
new ConcurrentHashMap<Class<?>, WebDataBinderFactory>();
private final Map<Class<?>, Set<Method>> modelAttributeMethodCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
private final Map<Class<?>, ModelFactory> modelFactoryCache = new ConcurrentHashMap<Class<?>, ModelFactory>();
/**
* Create a {@link RequestMappingHandlerAdapter} instance.
@@ -372,7 +368,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
argumentResolvers.addResolver(new HttpEntityMethodProcessor(messageConverters));
argumentResolvers.addResolver(new ModelMethodProcessor());
argumentResolvers.addResolver(new ErrorsMethodArgumentResolver());
// Default-mode resolution
argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, true));
argumentResolvers.addResolver(new ServletModelAttributeMethodProcessor(true));
@@ -462,8 +458,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
protected final ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
if (hasSessionAttributes(handlerMethod.getBeanType())) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
// Always prevent caching in case of session attribute management.
checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
}
@@ -487,118 +483,106 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Whether the given handler type defines any handler-specific session attributes
* via {@link SessionAttributes}.
* Return the {@link SessionAttributesHandler} instance for the given
* handler type, never {@code null}.
*/
private boolean hasSessionAttributes(Class<?> handlerType) {
SessionAttributesHandler sessionAttrsHandler = null;
synchronized(this.sessionAttributesHandlerCache) {
sessionAttrsHandler = this.sessionAttributesHandlerCache.get(handlerType);
if (sessionAttrsHandler == null) {
sessionAttrsHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
this.sessionAttributesHandlerCache.put(handlerType, sessionAttrsHandler);
private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
Class<?> handlerType = handlerMethod.getBeanType();
SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
if (sessionAttrHandler == null) {
synchronized(this.sessionAttributesHandlerCache) {
sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
if (sessionAttrHandler == null) {
sessionAttrHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler);
}
}
}
FlashAttributesHandler flashAttrsHandler = null;
synchronized(this.flashAttributesHandlerCache) {
flashAttrsHandler = this.flashAttributesHandlerCache.get(handlerType);
if (flashAttrsHandler == null) {
flashAttrsHandler = new FlashAttributesHandler(handlerType);
this.flashAttributesHandlerCache.put(handlerType, flashAttrsHandler);
}
}
return sessionAttrsHandler.hasSessionAttributes() || flashAttrsHandler.hasFlashAttributes();
return sessionAttrHandler;
}
/**
* Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView} if view resolution is required.
*/
private ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
WebDataBinderFactory binderFactory = createDataBinderFactory(handlerMethod);
ModelFactory modelFactory = createModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod requestMethod = createRequestMappingMethod(handlerMethod, binderFactory);
ServletWebRequest webRequest = new ServletWebRequest(request, response);
SessionStatus sessionStatus = new SimpleSessionStatus();
FlashStatus flashStatus = new SimpleFlashStatus();
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod);
FlashMap flashMap = (FlashMap) request.getAttribute(FlashMapManager.PREVIOUS_FLASH_MAP_ATTRIBUTE);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
modelFactory.initModel(webRequest, mavContainer, requestMethod);
mavContainer.addAllAttributes(flashMap);
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
requestMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus, flashStatus);
modelFactory.updateModel(webRequest, mavContainer, sessionStatus, flashStatus);
SessionStatus sessionStatus = new SimpleSessionStatus();
ResponseContext responseContext = new ResponseContext(webRequest, mavContainer);
requestMappingMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus, responseContext);
modelFactory.updateModel(webRequest, mavContainer, sessionStatus);
if (!mavContainer.isResolveView()) {
return null;
}
else {
ModelAndView mav = new ModelAndView().addAllObjects(mavContainer.getModel());
mav.setViewName(mavContainer.getViewName());
if (mavContainer.getView() != null) {
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
return mav;
}
}
private WebDataBinderFactory createDataBinderFactory(HandlerMethod handlerMethod) {
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod) {
ServletInvocableHandlerMethod requestMappingMethod =
new ServletInvocableHandlerMethod(handlerMethod.getBean(), handlerMethod.getMethod());
requestMappingMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
requestMappingMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
requestMappingMethod.setDataBinderFactory(getDataBinderFactory(handlerMethod));
requestMappingMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
return requestMappingMethod;
}
private ModelFactory getModelFactory(HandlerMethod handlerMethod) {
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> binderMethods = initBinderMethodCache.get(handlerType);
if (binderMethods == null) {
binderMethods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
initBinderMethodCache.put(handlerType, binderMethods);
ModelFactory modelFactory = this.modelFactoryCache.get(handlerType);
if (modelFactory == null) {
List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
for (Method method : HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS)) {
InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
attrMethod.setDataBinderFactory(binderFactory);
attrMethods.add(attrMethod);
}
modelFactory = new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
this.modelFactoryCache.put(handlerType, modelFactory);
}
for (Method method : binderMethods) {
Object bean = handlerMethod.getBean();
InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
initBinderMethods.add(binderMethod);
}
return new ServletRequestDataBinderFactory(initBinderMethods, this.webBindingInitializer);
return modelFactory;
}
private ModelFactory createModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
List<InvocableHandlerMethod> modelAttrMethods = new ArrayList<InvocableHandlerMethod>();
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) {
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> attributeMethods = modelAttributeMethodCache.get(handlerType);
if (attributeMethods == null) {
attributeMethods = HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
modelAttributeMethodCache.put(handlerType, attributeMethods);
WebDataBinderFactory binderFactory = this.dataBinderFactoryCache.get(handlerType);
if (binderFactory == null) {
List<InvocableHandlerMethod> binderMethods = new ArrayList<InvocableHandlerMethod>();
for (Method method : HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS)) {
InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
binderMethods.add(binderMethod);
}
binderFactory = new ServletRequestDataBinderFactory(binderMethods, this.webBindingInitializer);
this.dataBinderFactoryCache.put(handlerType, binderFactory);
}
for (Method method : attributeMethods) {
InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
attrMethod.setDataBinderFactory(binderFactory);
attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
modelAttrMethods.add(attrMethod);
}
SessionAttributesHandler sessionAttrsHandler = sessionAttributesHandlerCache.get(handlerType);
FlashAttributesHandler flashAttrsHandler = flashAttributesHandlerCache.get(handlerType);
return new ModelFactory(modelAttrMethods, binderFactory, sessionAttrsHandler, flashAttrsHandler);
}
private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod,
WebDataBinderFactory binderFactory) {
Method method = handlerMethod.getMethod();
ServletInvocableHandlerMethod requestMethod = new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
requestMethod.setDataBinderFactory(binderFactory);
requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
return requestMethod;
return binderFactory;
}
/**

View File

@@ -17,7 +17,6 @@
package org.springframework.web.servlet.mvc.method.annotation.support;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.BeanUtils;
@@ -84,9 +83,11 @@ public class DefaultMethodReturnValueHandler implements HandlerMethodReturnValue
ExtendedModelMap model = (ExtendedModelMap) mavContainer.getModel();
ModelAndView mav = resolver.resolveModelAndView(method, handlerType, returnValue, model, request);
if (mav != ModelAndViewResolver.UNRESOLVED) {
mavContainer.setView(mav.getView());
mavContainer.setViewName(mav.getViewName());
mavContainer.addAllAttributes(mav.getModel());
mavContainer.setViewName(mav.getViewName());
if (!mav.isReference()) {
mavContainer.setView(mav.getView());
}
return;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2002-2011 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.support;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* Provides annotated controller methods with convenience methods for setting
* up response with a view name that will result in a redirect.
*
* <p>An instance of this class is obtained via {@link ResponseContext#redirect}.
*
* @author Keith Donald
* @author Rossen Stoyanchev
*
* @since 3.1
*/
public class RedirectResponse {
private final NativeWebRequest webRequest;
private final ModelAndViewContainer mavContainer;
RedirectResponse(NativeWebRequest webRequest, ModelAndViewContainer mavContainer) {
this.webRequest = webRequest;
this.mavContainer = mavContainer;
}
/**
* Add a URI template variable to use to expand the URI template into a URL.
* <p><strong>Note:</strong> URI template variables from the current
* request are automatically used when expanding the redirect URI template.
* They don't need to be added explicitly here.
*/
public RedirectResponse uriVariable(String name, Object value) {
this.mavContainer.addAttribute(name, value);
return this;
}
/**
* Add a URI template variable to use to expand the URI template into a URL.
* The name of the variable is selected using a
* {@link org.springframework.core.Conventions#getVariableName generated name}.
* <p><strong>Note:</strong> URI template variables from the current
* request are automatically used when expanding the redirect URI template.
* They don't need to be added explicitly here.
*/
public RedirectResponse uriVariable(Object value) {
this.mavContainer.addAttribute(value);
return this;
}
/**
* Add a query parameter to append to the redirect URL.
*/
public RedirectResponse queryParam(String name, Object value) {
this.mavContainer.addAttribute(name, value);
return this;
}
/**
* Add a query parameter to append to the redirect URL.
* The name of the parameter is selected using a
* {@link org.springframework.core.Conventions#getVariableName generated name}.
*/
public RedirectResponse queryParam(Object value) {
this.mavContainer.addAttribute(value);
return this;
}
/**
* Add a flash attribute to save and make available in the model of the
* target controller method after the redirect.
*/
public RedirectResponse flashAttribute(String name, Object value) {
getFlashMap().addAttribute(name, value);
return this;
}
/**
* Add a flash attribute to save and make available in the model of the
* target controller method after the redirect.
* The name of the attribute is selected using a
* {@link org.springframework.core.Conventions#getVariableName generated name}.
*/
public RedirectResponse flashAttribute(Object value) {
getFlashMap().addAttribute(value);
return this;
}
private FlashMap getFlashMap() {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return RequestContextUtils.getFlashMap(servletRequest);
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2002-2011 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.support;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Provides annotated controllers with convenience methods for setting up view
* resolution.
*
* @author Keith Donald
* @author Rossen Stoyanchev
*
* @since 3.1
*/
public class ResponseContext {
private final NativeWebRequest webRequest;
private final ModelAndViewContainer mavContainer;
public ResponseContext(NativeWebRequest webRequest, ModelAndViewContainer mavContainer) {
this.webRequest = webRequest;
this.mavContainer = mavContainer;
}
/**
* Set up view resolution based on the given view name and the implicit model
* of the current request.
*/
public ViewResponse view(String viewName) {
this.mavContainer.setViewName(viewName);
return new ViewResponse(this.mavContainer);
}
/**
* Set up view resolution for a redirect. This method clears the implicit
* model. Use convenience methods on the returned {@link RedirectResponse}
* instance to add URI variables, query parameters, and flash attributes
* as necessary.
* @param redirectUri a URI template either relative to the current URL or
* absolute; do not prefix with "redirect:"
*/
public RedirectResponse redirect(String redirectUri) {
if (!redirectUri.startsWith("redirect:")) {
redirectUri = "redirect:" + redirectUri;
}
this.mavContainer.getModel().clear();
this.mavContainer.setViewName(redirectUri);
return new RedirectResponse(this.webRequest, this.mavContainer);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2011 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.support;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Provides annotated controller methods with convenience methods for setting
* up a response with a view name that does not have the "redirect:" prefix .
*
* <p>An instance of this class is obtained via {@link ResponseContext#view}.
*
* @author Keith Donald
* @author Rossen Stoyanchev
*
* @since 3.1
*/
public class ViewResponse {
private final ModelAndViewContainer mavContainer;
ViewResponse(ModelAndViewContainer mavContainer) {
this.mavContainer = mavContainer;
}
public ViewResponse attribute(String attributeName, Object attributeValue) {
this.mavContainer.addAttribute(attributeName, attributeValue);
return this;
}
public ViewResponse attribute(Object attributeValue) {
this.mavContainer.addAttribute(attributeValue);
return this;
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2002-2011 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.support;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
/**
* A default implementation that saves and retrieves FlashMap instances to and
* from the HTTP session.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class DefaultFlashMapManager implements FlashMapManager {
static final String FLASH_MAPS_SESSION_ATTRIBUTE = DefaultFlashMapManager.class + ".FLASH_MAPS";
private boolean useUniqueFlashKey = true;
private String flashKeyParameterName = "_flashKey";
private int flashTimeout = 180;
private static final Random random = new Random();
/**
* Whether each FlashMap instance should be stored with a unique key.
* The unique key needs to be passed as a parameter in the redirect URL
* and then used to look up the FlashMap instance avoiding potential
* issues with concurrent requests.
* <p>The default setting is "true".
* <p>When set to "false" only one FlashMap is maintained making it
* possible for a second concurrent request (e.g. via Ajax) to "consume"
* the FlashMap inadvertently.
*/
public void setUseUniqueFlashKey(boolean useUniqueFlashKey) {
this.useUniqueFlashKey = useUniqueFlashKey;
}
/**
* Customize the name of the request parameter to be appended to the
* redirect URL when using a unique flash key.
* <p>The default value is "_flashKey".
*/
public void setFlashKeyParameterName(String parameterName) {
this.flashKeyParameterName = parameterName;
}
/**
* The amount of time in seconds after a request has completed processing
* and before a FlashMap is considered expired.
* The default value is 180.
*/
public void setFlashMapTimeout(int flashTimeout) {
this.flashTimeout = flashTimeout;
}
/**
* {@inheritDoc}
*
* <p>This method never creates an HTTP session. The current FlashMap is
* exposed as a request attribute only and is not saved in the session
* until {@link #requestCompleted}.
*/
public boolean requestStarted(HttpServletRequest request) {
if (request.getAttribute(CURRENT_FLASH_MAP_ATTRIBUTE) != null) {
return false;
}
FlashMap currentFlashMap =
this.useUniqueFlashKey ?
new FlashMap(createFlashKey(request), this.flashKeyParameterName) : new FlashMap();
request.setAttribute(CURRENT_FLASH_MAP_ATTRIBUTE, currentFlashMap);
FlashMap previousFlashMap = lookupPreviousFlashMap(request);
if (previousFlashMap != null) {
for (String name : previousFlashMap.keySet()) {
if (request.getAttribute(name) == null) {
request.setAttribute(name, previousFlashMap.get(name));
}
}
// For exposing flash attributes in other places (e.g. annotated controllers)
request.setAttribute(PREVIOUS_FLASH_MAP_ATTRIBUTE, previousFlashMap);
}
// Check and remove expired instances
Map<String, FlashMap> allFlashMaps = retrieveAllFlashMaps(request, false);
if (allFlashMaps != null && !allFlashMaps.isEmpty()) {
Iterator<FlashMap> iterator = allFlashMaps.values().iterator();
while (iterator.hasNext()) {
if (iterator.next().isExpired()) {
iterator.remove();
}
}
}
return true;
}
/**
* Create a unique flash key. The default implementation uses {@link Random}.
* @return the unique key; never {@code null}.
*/
protected String createFlashKey(HttpServletRequest request) {
return String.valueOf(random.nextInt());
}
/**
* Return the FlashMap from the previous request, if available.
* If {@link #useUniqueFlashKey} is "true", a flash key parameter is
* expected to be in the request. Otherwise there can be only one
* FlashMap instance to return.
*
* @return the FlashMap from the previous request; or {@code null} if none.
*/
private FlashMap lookupPreviousFlashMap(HttpServletRequest request) {
Map<String, FlashMap> flashMaps = retrieveAllFlashMaps(request, false);
if (flashMaps != null && !flashMaps.isEmpty()) {
if (this.useUniqueFlashKey) {
String key = request.getParameter(this.flashKeyParameterName);
if (key != null) {
return flashMaps.remove(key);
}
}
else {
String key = flashMaps.keySet().iterator().next();
return flashMaps.remove(key);
}
}
return null;
}
/**
* Retrieve all FlashMap instances from the HTTP session in a thread-safe way.
* @param request the current request
* @param allowCreate whether to create and the FlashMap container if not found
* @return a Map with all stored FlashMap instances; or {@code null}
*/
@SuppressWarnings("unchecked")
private Map<String, FlashMap> retrieveAllFlashMaps(HttpServletRequest request, boolean allowCreate) {
HttpSession session = request.getSession(allowCreate);
if (session == null) {
return null;
}
Map<String, FlashMap> result = (Map<String, FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE);
if (result == null && allowCreate) {
synchronized (DefaultFlashMapManager.class) {
result = (Map<String, FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE);
if (result == null) {
result = new ConcurrentHashMap<String, FlashMap>(5);
session.setAttribute(FLASH_MAPS_SESSION_ATTRIBUTE, result);
}
}
}
return result;
}
/**
* {@inheritDoc}
*
* <p>The HTTP session is not created if the current FlashMap instance is empty.
*/
public void requestCompleted(HttpServletRequest request) {
FlashMap flashMap = (FlashMap) request.getAttribute(CURRENT_FLASH_MAP_ATTRIBUTE);
if (flashMap == null) {
throw new IllegalStateException(
"Did not find current FlashMap exposed as request attribute " + CURRENT_FLASH_MAP_ATTRIBUTE);
}
if (!flashMap.isEmpty()) {
Map<String, FlashMap> allFlashMaps = retrieveAllFlashMaps(request, true);
flashMap.startExpirationPeriod(this.flashTimeout);
String key = this.useUniqueFlashKey ? flashMap.getKey() : "flashMap";
allFlashMaps.put(key, flashMap);
}
}
}

View File

@@ -17,6 +17,7 @@
package org.springframework.web.servlet.support;
import java.util.Locale;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
@@ -26,6 +27,8 @@ import org.springframework.ui.context.ThemeSource;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ThemeResolver;
@@ -152,4 +155,13 @@ public abstract class RequestContextUtils {
}
}
/**
* Retrieves the flash map to use for the current request.
* @param request current HTTP request
* @return the flash map for the current request; never {@code null}.
*/
public static FlashMap getFlashMap(HttpServletRequest request) {
return (FlashMap) request.getAttribute(FlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE);
}
}

View File

@@ -30,14 +30,19 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpStatus;
import org.springframework.ui.ModelMap;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.WebUtils;
@@ -76,6 +81,7 @@ import org.springframework.web.util.WebUtils;
* @author Colin Sampaleanu
* @author Sam Brannen
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @see #setContextRelative
* @see #setHttp10Compatible
* @see #setExposeModelAttributes
@@ -262,6 +268,14 @@ public class RedirectView extends AbstractUrlBasedView {
appendQueryProperties(targetUrl, model, enc);
}
FlashMap flashMap = RequestContextUtils.getFlashMap(request);
if (flashMap != null && !flashMap.isEmpty()) {
if (flashMap.getKey() != null) {
ModelMap queryParam = new ModelMap(flashMap.getKeyParameterName(), flashMap.getKey());
appendQueryProperties(targetUrl, queryParam, enc);
}
}
return targetUrl.toString();
}
@@ -323,7 +337,7 @@ public class RedirectView extends AbstractUrlBasedView {
}
// If there aren't already some parameters, we need a "?".
boolean first = (getUrl().indexOf('?') < 0);
boolean first = (targetUrl.toString().indexOf('?') < 0);
for (Map.Entry<String, Object> entry : queryProperties(model).entrySet()) {
Object rawValue = entry.getValue();
Iterator<Object> valueIter;