提交 936c83b0 编写于 作者: R Rossen Stoyanchev

SPR-6464 Add RedirectModel

上级 84ff2b49
......@@ -60,7 +60,6 @@ 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
......@@ -214,10 +213,10 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod,
Exception ex) {
Exception exception) {
if (handlerMethod != null) {
ExceptionMethodMapping mapping = getExceptionMethodMapping(handlerMethod);
Method method = mapping.getMethod(ex);
Method method = mapping.getMethod(exception);
if (method != null) {
Object handler = handlerMethod.getBean();
......@@ -232,9 +231,8 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
}
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
ResponseContext responseContext = new ResponseContext(webRequest, mavContainer);
exceptionHandler.invokeAndHandle(webRequest, mavContainer, ex, responseContext);
exceptionHandler.invokeAndHandle(webRequest, mavContainer, exception);
if (!mavContainer.isResolveView()) {
return new ModelAndView();
......
......@@ -40,6 +40,7 @@ import org.springframework.http.converter.HttpMessageConverter;
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.ReflectionUtils.MethodFilter;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.WebDataBinder;
......@@ -84,6 +85,7 @@ import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMeth
import org.springframework.web.servlet.mvc.method.annotation.support.HttpEntityMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.support.ModelAndViewMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.support.PathVariableMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.RedirectModelMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.RequestPartMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletCookieValueMethodArgumentResolver;
......@@ -91,7 +93,8 @@ 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.servlet.mvc.support.RedirectModel;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.WebUtils;
/**
......@@ -366,6 +369,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
argumentResolvers.addResolver(new ServletRequestMethodArgumentResolver());
argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
argumentResolvers.addResolver(new HttpEntityMethodProcessor(messageConverters));
argumentResolvers.addResolver(new RedirectModelMethodArgumentResolver());
argumentResolvers.addResolver(new ModelMethodProcessor());
argumentResolvers.addResolver(new ErrorsMethodArgumentResolver());
......@@ -512,27 +516,32 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod);
FlashMap flashMap = (FlashMap) request.getAttribute(FlashMapManager.PREVIOUS_FLASH_MAP_ATTRIBUTE);
FlashMap previousFlashMap = (FlashMap) request.getAttribute(FlashMapManager.PREVIOUS_FLASH_MAP_ATTRIBUTE);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(flashMap);
mavContainer.addAllAttributes(previousFlashMap);
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
SessionStatus sessionStatus = new SimpleSessionStatus();
ResponseContext responseContext = new ResponseContext(webRequest, mavContainer);
requestMappingMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus, responseContext);
requestMappingMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus);
modelFactory.updateModel(webRequest, mavContainer, sessionStatus);
if (!mavContainer.isResolveView()) {
return null;
}
else {
ModelAndView mav = new ModelAndView().addAllObjects(mavContainer.getModel());
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView().addAllObjects(model);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectModel) {
RedirectModel redirectModel = (RedirectModel) model;
FlashMap currentFlashMap = RequestContextUtils.getFlashMap(request);
currentFlashMap.putAll(redirectModel.getFlashAttributes());
}
return mav;
}
}
......
......@@ -14,55 +14,43 @@
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.support;
package org.springframework.web.servlet.mvc.method.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.support.ModelMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.support.RedirectModel;
/**
* Provides annotated controllers with convenience methods for setting up view
* resolution.
* Resolves {@link RedirectModel} method arguments.
*
* @author Keith Donald
* <p>This resolver must be listed ahead of the {@link ModelMethodProcessor},
* which also resolves arguments of type {@link Model}.
*
* @author Rossen Stoyanchev
*
* @since 3.1
*/
public class ResponseContext {
private final NativeWebRequest webRequest;
public class RedirectModelMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final ModelAndViewContainer mavContainer;
public ResponseContext(NativeWebRequest webRequest, ModelAndViewContainer mavContainer) {
this.webRequest = webRequest;
this.mavContainer = mavContainer;
public boolean supportsParameter(MethodParameter parameter) {
return RedirectModel.class.equals(parameter.getParameterType());
}
/**
* 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);
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
DataBinder dataBinder = binderFactory.createBinder(webRequest, null, null);
ModelMap implicitModel = mavContainer.getModel();
RedirectModel redirectModel = new RedirectModel(dataBinder, implicitModel);
mavContainer.setRedirectModel(redirectModel);
return redirectModel;
}
}
......@@ -16,8 +16,6 @@
package org.springframework.web.servlet.mvc.method.annotation.support;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
......@@ -26,18 +24,20 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.RequestToViewNameTranslator;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
/**
* Handles return values that are of type {@code void}, {@code String} (i.e. logical view name), or {@link View}.
* Handles return values that are of type {@code void}, {@code String} (i.e.
* logical view name), or {@link View}.
*
* <p>A {@code null} return value, either due to a void return type or as the actual value returned from a
* method is left unhandled, leaving it to the configured {@link RequestToViewNameTranslator} to resolve the
* request to an actual view name. By default it is the {@link DefaultRequestToViewNameTranslator}.
* <p>A {@code null} return value, either due to a void return type or as the
* actual value returned from a method is left unhandled, leaving it to the
* configured {@link RequestToViewNameTranslator} to resolve the request to
* an actual view name.
*
* <p>Since a {@link String} return value may handled in different ways, especially in combination with method
* annotations such as @{@link ModelAttribute} and @{@link ResponseBody}, this handler should be ordered after
* return value handlers that support method annotations.
* <p>Since a {@link String} return value may be handled in combination with
* method annotations such as @{@link ModelAttribute} or @{@link ResponseBody},
* this handler should be ordered after return value handlers that support
* method annotations.
*
* @author Rossen Stoyanchev
* @since 3.1
......@@ -57,17 +57,44 @@ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHan
return;
}
if (returnValue instanceof String) {
mavContainer.setViewName((String) returnValue);
String viewName = (String) returnValue;
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.useRedirectModel();
}
}
else if (returnValue instanceof View){
mavContainer.setView(returnValue);
View view = (View) returnValue;
mavContainer.setView(view);
if (isRedirectView(view)) {
mavContainer.useRedirectModel();
}
}
else {
// should not happen
Method method = returnType.getMethod();
String returnTypeName = returnType.getParameterType().getName();
throw new UnsupportedOperationException("Unknown return type: " + returnTypeName + " in method: " + method);
throw new UnsupportedOperationException("Unknown return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
/**
* Whether the given view name is a redirect view reference.
* @param viewName the view name to check, never {@code null}
* @return "true" if the given view name is recognized as a redirect view
* reference; "false" otherwise.
*/
protected boolean isRedirectViewName(String viewName) {
return viewName.startsWith("redirect:");
}
/**
* Whether the given View instance is a redirect view.
* @param view a view instance, never {@code null}
* @return "true" if the given view is recognized as a redirect View;
* "false" otherwise.
*/
protected boolean isRedirectView(View view) {
return "RedirectView".equals(view.getClass().getSimpleName());
}
}
/*
* 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 java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.Conventions;
import org.springframework.util.Assert;
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().put(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 attributeValue) {
Assert.notNull(attributeValue, "Model object must not be null");
if (attributeValue instanceof Collection && ((Collection) attributeValue).isEmpty()) {
return this;
}
return flashAttribute(Conventions.getVariableName(attributeValue), attributeValue);
}
private FlashMap getFlashMap() {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return RequestContextUtils.getFlashMap(servletRequest);
}
}
/*
* 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;
}
}
/*
* 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.support;
import java.beans.PropertyEditor;
import java.util.Collection;
import java.util.Map;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.Formatter;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.validation.DataBinder;
/**
* A {@link Model} implementation that controllers can use when they wish to
* redirect. For a redirect a controller needs to use an empty model and
* only add those attributes that will be used in the redirect URL --
* either embedded as URI template variables or appended as query parameters.
* To be used in the URL such attributes need to be formatted as String
* values. Alternatively a controller may choose to keep attributes in
* flash storage instead for the duration of the redirect.
*
* <p>A RedirectModel serves the above needs as follows:
* <ul>
* <li>Formats attribute values as Strings before adding them using a
* registered {@link Converter}, {@link Formatter}, {@link PropertyEditor},
* or the attribute's {@link #toString()} method.
* <li>Wraps the the model of the current request and provides a method to
* copy attributes from it (see {@link #addModelAttributes(String...)}).
* <li>Provides methods to store attributes candidate for flash storage.
* </ul>
*
* <p>Note that a RedirectModel will not be used unless the controller decides
* to redirect.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
@SuppressWarnings("serial")
public class RedirectModel extends ExtendedModelMap {
private final DataBinder dataBinder;
private final ModelMap flashAttributes = new ModelMap();
private final ModelMap implicitModel;
/**
* Create a new instance without a DataBinder. Attribute values will be
* formatted via {@link #toString()}.
*/
public RedirectModel() {
this(null, null);
}
/**
* Create a new instance providing a DataBinder to use for formatting
* attribute values.
* @param dataBinder a DataBinder for converting attribute values to String.
* @param implicitModel the implicit model for the current request;
* used in conjunction with {@link #addModelAttributes(String...)}
* to copy attributes from the implicit model to the redirect model.
*/
public RedirectModel(DataBinder dataBinder, ModelMap implicitModel) {
this.dataBinder = dataBinder;
this.implicitModel = implicitModel;
}
/**
* Return the attributes candidate for flash storage.
*/
public Map<String, ?> getFlashAttributes() {
return flashAttributes;
}
/**
* Add an attribute. Before being added the attribute value is formatted as
* a String in preparation for use in the redirect URL. If the attribute
* value is null it is not be added to the model.
* @param attributeName the attribute name; never null
* @param attributeValue the attribute value; skipped if null
*/
public RedirectModel addAttribute(String attributeName, Object attributeValue) {
if (attributeValue != null) {
super.addAttribute(attributeName, formatValue(attributeValue));
}
return this;
}
private String formatValue(Object value) {
return (dataBinder != null) ? dataBinder.convertIfNecessary(value, String.class) : value.toString();
}
/**
* Add an attribute using a
* {@link org.springframework.core.Conventions#getVariableName generated name}.
* Before being added the attribute value is formatted as a String.
* @param attributeValue the attribute value; never null
*/
public RedirectModel addAttribute(Object attributeValue) {
super.addAttribute(attributeValue);
return this;
}
/**
* Copy all attributes in the supplied <code>Collection</code> into this
* Model using attribute name generation for each element.
* @see #addAttribute(Object)
*/
public RedirectModel addAllAttributes(Collection<?> attributeValues) {
super.addAllAttributes(attributeValues);
return this;
}
/**
* Copy all supplied attributes into this redirect model.
* @see #addAttribute(String, Object)
*/
public RedirectModel addAllAttributes(Map<String, ?> attributes) {
if (attributes != null) {
for (String key : attributes.keySet()) {
addAttribute(key, attributes.get(key));
}
}
return this;
}
/**
* Copy all supplied attributes into this redirect model with with existing
* attributes of the same name taking precedence (i.e. not getting replaced).
* @see #addAttribute(String, Object)
*/
public RedirectModel mergeAttributes(Map<String, ?> attributes) {
if (attributes != null) {
for (String key : attributes.keySet()) {
if (!containsKey(key)) {
addAttribute(key, attributes.get(key));
}
}
}
return this;
}
/**
* Copy the attributes specified by name from the "implicit" model of the
* current request to this redirect model instance.
* @param attributeNames the names of attributes present in the implicit model.
* attribute names are required to be present; if an attribute is present
* but is null, it is skipped
* @see #addAttribute(String, Object)
*/
public RedirectModel addModelAttributes(String... attributeNames) {
Assert.notNull(this.implicitModel, "The implicit model has not been set.");
for (String name : attributeNames) {
Assert.isTrue(this.implicitModel.containsAttribute(name), name + " not found in implicit model");
Object value = this.implicitModel.get(name);
addAttribute(name, value);
}
return this;
}
/**
* Add the given attribute as a candidate for flash storage.
* @param attributeName the flash attribute name; never null
* @param attributeValue the flash attribute value; may be null
*/
public RedirectModel addFlashAttribute(String attributeName, Object attributeValue) {
this.flashAttributes.addAttribute(attributeName, attributeValue);
return this;
}
/**
* Add the given attribute value as a candidate for flash storage using a
* {@link org.springframework.core.Conventions#getVariableName generated name}.
* @param attributeValue the flash attribute value; never null
*/
public RedirectModel addFlashAttribute(Object attributeValue) {
this.flashAttributes.addAttribute(attributeValue);
return this;
}
}
......@@ -138,7 +138,7 @@ import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
import org.springframework.web.servlet.mvc.method.support.ResponseContext;
import org.springframework.web.servlet.mvc.support.RedirectModel;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
......@@ -2816,13 +2816,14 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
}
@RequestMapping(value = "/messages", method = RequestMethod.POST)
public void sendMessage(TestBean testBean, BindingResult result, ResponseContext responseContext) {
public String sendMessage(TestBean testBean, BindingResult result, RedirectModel redirectModel) {
if (result.hasErrors()) {
responseContext.view("messages/new");
return "messages/new";
}
else {
responseContext.redirect("/messages/{id}").uriVariable("id", "1").queryParam("name", "value")
.flashAttribute("successMessage", "yay!");
redirectModel.addAttribute("id", "1").addAttribute("name", "value");
redirectModel.addFlashAttribute("successMessage", "yay!");
return "redirect:/messages/{id}";
}
}
}
......
......@@ -29,7 +29,9 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.support.RedirectModel;
import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.RedirectView;
/**
* Test fixture with {@link DefaultMethodReturnValueHandler}.
......@@ -61,15 +63,38 @@ public class ViewMethodReturnValueHandlerTests {
public void returnView() throws Exception {
InternalResourceView view = new InternalResourceView("testView");
handler.handleReturnValue(view, createMethodParam("view"), mavContainer, webRequest);
assertSame(view, mavContainer.getView());
}
@Test
public void returnViewRedirect() throws Exception {
RedirectView redirectView = new RedirectView("testView");
RedirectModel redirectModel = new RedirectModel();
mavContainer.setRedirectModel(redirectModel);
handler.handleReturnValue(redirectView, createMethodParam("view"), mavContainer, webRequest);
assertSame(redirectView, mavContainer.getView());
assertSame("Should have switched to the RedirectModel", redirectModel, mavContainer.getModel());
}
@Test
public void returnViewName() throws Exception {
handler.handleReturnValue("testView", createMethodParam("viewName"), mavContainer, webRequest);
assertEquals("testView", mavContainer.getViewName());
}
@Test
public void returnViewNameRedirect() throws Exception {
RedirectModel redirectModel = new RedirectModel();
mavContainer.setRedirectModel(redirectModel);
handler.handleReturnValue("redirect:testView", createMethodParam("viewName"), mavContainer, webRequest);
assertEquals("redirect:testView", mavContainer.getViewName());
assertSame("Should have switched to the RedirectModel", redirectModel, mavContainer.getModel());
}
private MethodParameter createMethodParam(String methodName) throws Exception {
Method method = getClass().getDeclaredMethod(methodName);
return new MethodParameter(method, -1);
......
/*
* 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.support;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.TestBean;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.ui.ModelMap;
import org.springframework.validation.DataBinder;
/**
*
* Test fixture for RedirectModel tests.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class RedirectModelTests {
private RedirectModel redirectModel;
private FormattingConversionService conversionService;
private ModelMap implicitModel;
@Before
public void setup() {
this.conversionService = new DefaultFormattingConversionService();
DataBinder dataBinder = new DataBinder(null);
dataBinder.setConversionService(conversionService);
this.implicitModel = new ModelMap();
this.redirectModel = new RedirectModel(dataBinder, implicitModel);
}
@Test
public void addAttributePrimitiveType() {
this.redirectModel.addAttribute("speed", 65);
assertEquals("65", this.redirectModel.get("speed"));
}
@Test
public void addAttributeCustomType() {
String attrName = "person";
this.redirectModel.addAttribute(attrName, new TestBean("Fred"));
assertEquals("ConversionService should have invoked toString()", "Fred", this.redirectModel.get(attrName));
this.conversionService.addConverter(new TestBeanConverter());
this.redirectModel.addAttribute(attrName, new TestBean("Fred"));
assertEquals("Type converter should have been used", "[Fred]", this.redirectModel.get(attrName));
}
@Test
public void addAttributeToString() {
String attrName = "person";
RedirectModel model = new RedirectModel();
model.addAttribute(attrName, new TestBean("Fred"));
assertEquals("toString() should have been used", "Fred", model.get(attrName));
}
@Test
public void addAttributeValue() {
this.redirectModel.addAttribute(new TestBean("Fred"));
assertEquals("Fred", this.redirectModel.get("testBean"));
}
@Test
public void addAllAttributesList() {
this.redirectModel.addAllAttributes(Arrays.asList(new TestBean("Fred"), new Integer(5)));
assertEquals("Fred", this.redirectModel.get("testBean"));
assertEquals("5", this.redirectModel.get("integer"));
}
@Test
public void addAttributesMap() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("person", new TestBean("Fred"));
map.put("age", 33);
this.redirectModel.addAllAttributes(map);
assertEquals("Fred", this.redirectModel.get("person"));
assertEquals("33", this.redirectModel.get("age"));
}
@Test
public void mergeAttributes() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("person", new TestBean("Fred"));
map.put("age", 33);
this.redirectModel.addAttribute("person", new TestBean("Ralph"));
this.redirectModel.mergeAttributes(map);
assertEquals("Ralph", this.redirectModel.get("person"));
assertEquals("33", this.redirectModel.get("age"));
}
@Test
public void addModelAttributes() {
this.implicitModel.addAttribute("person", new TestBean("Ralph"));
this.implicitModel.addAttribute("age", 33);
this.redirectModel.addModelAttributes("person", "age");
assertEquals("Ralph", this.redirectModel.get("person"));
assertEquals("33", this.redirectModel.get("age"));
}
@Test(expected=IllegalArgumentException.class)
public void addModelAttributesInvalidName() {
this.redirectModel.addModelAttributes("person");
}
public static class TestBeanConverter implements Converter<TestBean, String> {
public String convert(TestBean source) {
return "[" + source.getName() + "]";
}
}
}
......@@ -24,7 +24,6 @@ import java.util.Map;
import java.util.Set;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionAttributeStore;
......@@ -35,11 +34,12 @@ import org.springframework.web.context.request.WebRequest;
* Manages handler-specific session attributes declared via @{@link SessionAttributes}.
* Actual storage is performed through an instance of {@link SessionAttributeStore}.
*
* <p>A typical scenario begins with a controller adding attributes to the {@link Model}.
* At the end of the request, model attributes are checked to see if any of them match
* the names and types declared via @{@link SessionAttributes}. Matching model
* attributes are "promoted" to the session and remain there until the controller
* calls {@link SessionStatus#setComplete()} to indicate the session attributes are
* <p>A typical scenario begins with a controller adding attributes to the
* {@link org.springframework.ui.Model Model}. At the end of the request, model
* attributes are checked to see if any of them match the names and types declared
* via @{@link SessionAttributes}. Matching model attributes are "promoted" to
* the session and remain there until the controller calls
* {@link SessionStatus#setComplete()} to indicate the session attributes are
* no longer needed and can be removed.
*
* @author Rossen Stoyanchev
......
......@@ -35,9 +35,13 @@ public class ModelAndViewContainer {
private Object view;
private boolean resolveView = true;
private final ModelMap model = new BindingAwareModelMap();
private final ModelMap model = new BindingAwareModelMap();
private ModelMap redirectModel;
private boolean useRedirectModel = false;
/**
* Create a new instance.
*/
......@@ -104,10 +108,35 @@ public class ModelAndViewContainer {
}
/**
* Return the underlying {@code ModelMap} instance, never {@code null}.
* Return the internal model currently in use. Normally this is the
* implicit model created in the constructor of this class.
* However, when a redirect model is provided via {@link #setRedirectModel}
* and then enabled for use via {@link #useRedirectModel()}, this method
* returns the redirect model instead.
*
* @return the internal model currently in use, never {@code null}.
*/
public ModelMap getModel() {
return this.model;
return this.useRedirectModel ? this.redirectModel : this.model;
}
/**
* Provide a model that may be used in case of a redirect.
* If {@link #useRedirectModel()} is called at any time after this method is
* called, the ModelAndViewContainer will switch to using the redirect model
* for all operations. Until then, the redirect model is not used.
*/
public void setRedirectModel(ModelMap redirectModel) {
this.redirectModel = redirectModel;
}
/**
* Make a switch internally from using the implicit model to using the
* redirect model provided earlier via {@link #setRedirectModel(ModelMap)}.
* If the redirect model was never set, this method has no effect.
*/
public void useRedirectModel() {
this.useRedirectModel = this.redirectModel != null;
}
/**
......@@ -115,7 +144,7 @@ public class ModelAndViewContainer {
* @see ModelMap#addAttribute(String, Object)
*/
public ModelAndViewContainer addAttribute(String name, Object value) {
this.model.addAttribute(name, value);
getModel().addAttribute(name, value);
return this;
}
......@@ -124,7 +153,7 @@ public class ModelAndViewContainer {
* @see Model#addAttribute(Object)
*/
public ModelAndViewContainer addAttribute(Object value) {
this.model.addAttribute(value);
getModel().addAttribute(value);
return this;
}
......@@ -133,7 +162,7 @@ public class ModelAndViewContainer {
* @see ModelMap#addAllAttributes(Map)
*/
public ModelAndViewContainer addAllAttributes(Map<String, ?> attributes) {
this.model.addAllAttributes(attributes);
getModel().addAllAttributes(attributes);
return this;
}
......@@ -143,7 +172,7 @@ public class ModelAndViewContainer {
* @see ModelMap#mergeAttributes(Map)
*/
public ModelAndViewContainer mergeAttributes(Map<String, ?> attributes) {
this.model.mergeAttributes(attributes);
getModel().mergeAttributes(attributes);
return this;
}
......@@ -152,7 +181,7 @@ public class ModelAndViewContainer {
* @see ModelMap#containsAttribute(String)
*/
public boolean containsAttribute(String name) {
return this.model.containsAttribute(name);
return getModel().containsAttribute(name);
}
}
......@@ -74,7 +74,7 @@ public class InitBinderDataBinderFactoryTests {
@Test
public void createBinderWithGlobalInitialization() throws Exception {
ConversionService conversionService = new DefaultFormattingConversionService();
bindingInitializer.setConversionService(conversionService );
bindingInitializer.setConversionService(conversionService);
WebDataBinderFactory factory = createBinderFactory("initBinder", WebDataBinder.class);
WebDataBinder dataBinder = factory.createBinder(webRequest, null, null);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册