Enhance view controller MVC config
This change adds support for configuring redirect view controllers and also status controllers to the MVC Java config and the MVC namespace. Issue: SPR-11543
This commit is contained in:
@@ -35,6 +35,8 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport {
|
||||
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("tiles", new TilesBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("freemarker", new FreeMarkerBeanDefinitionParser());
|
||||
|
||||
@@ -19,19 +19,32 @@ package org.springframework.web.servlet.config;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.ParameterizableViewController;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
|
||||
* {@code view-controller} element to register a {@link ParameterizableViewController}.
|
||||
* Will also register a {@link SimpleUrlHandlerMapping} for view controllers.
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that
|
||||
* parses the following MVC namespace elements:
|
||||
* <ul>
|
||||
* <li>{@code <view-controller>}
|
||||
* <li>{@code <redirect-view-controller>}
|
||||
* <li>{@code <status-controller>}
|
||||
* </ul>
|
||||
*
|
||||
* <p>All elements result in the registration of a
|
||||
* {@link org.springframework.web.servlet.mvc.ParameterizableViewController
|
||||
* ParameterizableViewController} with all controllers mapped using in a single
|
||||
* {@link org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
|
||||
* SimpleUrlHandlerMapping}.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Christian Dupuis
|
||||
@@ -50,7 +63,7 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
|
||||
Object source = parserContext.extractSource(element);
|
||||
|
||||
// Register SimpleUrlHandlerMapping for view controllers
|
||||
BeanDefinition handlerMapping = registerHandlerMapping(parserContext, source);
|
||||
BeanDefinition hm = registerHandlerMapping(parserContext, source);
|
||||
|
||||
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
|
||||
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
|
||||
@@ -58,16 +71,41 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
|
||||
// Create view controller bean definition
|
||||
RootBeanDefinition controller = new RootBeanDefinition(ParameterizableViewController.class);
|
||||
controller.setSource(source);
|
||||
if (element.hasAttribute("view-name")) {
|
||||
controller.getPropertyValues().add("viewName", element.getAttribute("view-name"));
|
||||
|
||||
HttpStatus statusCode = null;
|
||||
if (element.hasAttribute("status-code")) {
|
||||
int statusValue = Integer.valueOf(element.getAttribute("status-code"));
|
||||
statusCode = HttpStatus.valueOf(statusValue);
|
||||
}
|
||||
|
||||
String name = element.getLocalName();
|
||||
if (name.equals("view-controller")) {
|
||||
if (element.hasAttribute("view-name")) {
|
||||
controller.getPropertyValues().add("viewName", element.getAttribute("view-name"));
|
||||
}
|
||||
if (statusCode != null) {
|
||||
controller.getPropertyValues().add("statusCode", statusCode);
|
||||
}
|
||||
}
|
||||
else if (name.equals("redirect-view-controller")) {
|
||||
controller.getPropertyValues().add("view", getRedirectView(element, statusCode, source));
|
||||
}
|
||||
else if (name.equals("status-controller")) {
|
||||
controller.getPropertyValues().add("statusCode", statusCode);
|
||||
controller.getPropertyValues().add("statusOnly", true);
|
||||
}
|
||||
else {
|
||||
// Should never happen...
|
||||
throw new IllegalStateException("Unexpected tag name: " + name);
|
||||
}
|
||||
|
||||
Map<String, BeanDefinition> urlMap;
|
||||
if (handlerMapping.getPropertyValues().contains("urlMap")) {
|
||||
urlMap = (Map<String, BeanDefinition>) handlerMapping.getPropertyValues().getPropertyValue("urlMap").getValue();
|
||||
if (hm.getPropertyValues().contains("urlMap")) {
|
||||
urlMap = (Map<String, BeanDefinition>) hm.getPropertyValues().getPropertyValue("urlMap").getValue();
|
||||
}
|
||||
else {
|
||||
urlMap = new ManagedMap<String, BeanDefinition>();
|
||||
handlerMapping.getPropertyValues().add("urlMap", urlMap);
|
||||
hm.getPropertyValues().add("urlMap", urlMap);
|
||||
}
|
||||
urlMap.put(element.getAttribute("path"), controller);
|
||||
|
||||
@@ -91,4 +129,21 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
|
||||
return beanDef;
|
||||
}
|
||||
|
||||
private RootBeanDefinition getRedirectView(Element element, HttpStatus status, Object source) {
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, element.getAttribute("redirect-url"));
|
||||
RootBeanDefinition redirectView = new RootBeanDefinition(RedirectView.class, cavs, null);
|
||||
redirectView.setSource(source);
|
||||
if (status != null) {
|
||||
redirectView.getPropertyValues().add("statusCode", status);
|
||||
}
|
||||
if (element.hasAttribute("context-relative")) {
|
||||
redirectView.getPropertyValues().add("contextRelative", element.getAttribute("context-relative"));
|
||||
}
|
||||
if (element.hasAttribute("keep-query-params")) {
|
||||
redirectView.getPropertyValues().add("propagateQueryParams", element.getAttribute("keep-query-params"));
|
||||
}
|
||||
return redirectView;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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.config.annotation;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.servlet.mvc.ParameterizableViewController;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
/**
|
||||
* Assist with the registration of a single redirect view controller.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public class RedirectViewControllerRegistration {
|
||||
|
||||
private final String urlPath;
|
||||
|
||||
private final RedirectView redirectView;
|
||||
|
||||
private final ParameterizableViewController controller = new ParameterizableViewController();
|
||||
|
||||
|
||||
public RedirectViewControllerRegistration(String urlPath, String redirectUrl) {
|
||||
Assert.notNull(urlPath, "'urlPath' is required.");
|
||||
Assert.notNull(redirectUrl, "'redirectUrl' is required.");
|
||||
this.urlPath = urlPath;
|
||||
this.redirectView = new RedirectView(redirectUrl);
|
||||
this.redirectView.setContextRelative(true);
|
||||
this.controller.setView(this.redirectView);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the specific redirect 3xx status code to use.
|
||||
*
|
||||
* <p>If not set, {@link org.springframework.web.servlet.view.RedirectView}
|
||||
* will select {@code HttpStatus.MOVED_TEMPORARILY (302)} by default.
|
||||
*/
|
||||
public RedirectViewControllerRegistration setStatusCode(HttpStatus statusCode) {
|
||||
Assert.isTrue(statusCode.is3xxRedirection(), "Not a redirect status code.");
|
||||
this.redirectView.setStatusCode(statusCode);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to interpret a given redirect URL that starts with a slash ("/")
|
||||
* as relative to the current ServletContext, i.e. as relative to the web
|
||||
* application root.
|
||||
*
|
||||
* <p>Default is {@code true}.
|
||||
*/
|
||||
public RedirectViewControllerRegistration setContextRelative(boolean contextRelative) {
|
||||
this.redirectView.setContextRelative(contextRelative);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to propagate the query parameters of the current request through
|
||||
* to the target redirect URL.
|
||||
*
|
||||
* <p>Default is {@code false}.
|
||||
*/
|
||||
public RedirectViewControllerRegistration setKeepQueryParams(boolean propagate) {
|
||||
this.redirectView.setPropagateQueryParams(propagate);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
protected String getUrlPath() {
|
||||
return this.urlPath;
|
||||
}
|
||||
|
||||
protected ParameterizableViewController getViewController() {
|
||||
return this.controller;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.servlet.RequestToViewNameTranslator;
|
||||
import org.springframework.web.servlet.mvc.ParameterizableViewController;
|
||||
@@ -31,12 +32,9 @@ public class ViewControllerRegistration {
|
||||
|
||||
private final String urlPath;
|
||||
|
||||
private String viewName;
|
||||
private final ParameterizableViewController controller = new ParameterizableViewController();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a registration for the given URL path (or path pattern).
|
||||
*/
|
||||
public ViewControllerRegistration(String urlPath) {
|
||||
Assert.notNull(urlPath, "'urlPath' is required.");
|
||||
this.urlPath = urlPath;
|
||||
@@ -44,17 +42,28 @@ public class ViewControllerRegistration {
|
||||
|
||||
|
||||
/**
|
||||
* Set the view name to return.
|
||||
* Set the status code to set on the response. Optional.
|
||||
*
|
||||
* <p>If not specified, the view controller returns {@code null} as the view
|
||||
* name in which case the configured {@link RequestToViewNameTranslator}
|
||||
* selects the view. In effect {@code DefaultRequestToViewNameTranslator}
|
||||
* translates "/foo/bar" to "foo/bar".
|
||||
* <p>If not set the response status will be 200 (OK).
|
||||
*/
|
||||
public ViewControllerRegistration setStatusCode(HttpStatus statusCode) {
|
||||
this.controller.setStatusCode(statusCode);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the view name to return. Optional.
|
||||
*
|
||||
* <p>If not specified, the view controller will return {@code null} as the
|
||||
* view name in which case the configured {@link RequestToViewNameTranslator}
|
||||
* will select the view name. The {@code DefaultRequestToViewNameTranslator}
|
||||
* for example translates "/foo/bar" to "foo/bar".
|
||||
*
|
||||
* @see org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
|
||||
*/
|
||||
public void setViewName(String viewName) {
|
||||
this.viewName = viewName;
|
||||
public ViewControllerRegistration setViewName(String viewName) {
|
||||
this.controller.setViewName(viewName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,10 +71,8 @@ public class ViewControllerRegistration {
|
||||
return this.urlPath;
|
||||
}
|
||||
|
||||
protected Object getViewController() {
|
||||
ParameterizableViewController controller = new ParameterizableViewController();
|
||||
controller.setViewName(this.viewName);
|
||||
return controller;
|
||||
protected ParameterizableViewController getViewController() {
|
||||
return this.controller;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,14 +21,13 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
|
||||
/**
|
||||
* Enables the registration of view controllers that have no logic other than to
|
||||
* return the view name they're configured with. This is an alternative to
|
||||
* writing a controller manually to do the same.
|
||||
* Assists with the registration of simple automated controllers pre-configured
|
||||
* with status code and/or a view.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Keith Donald
|
||||
@@ -36,13 +35,17 @@ import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
*/
|
||||
public class ViewControllerRegistry {
|
||||
|
||||
private final List<ViewControllerRegistration> registrations = new ArrayList<ViewControllerRegistration>();
|
||||
private final List<ViewControllerRegistration> registrations = new ArrayList<ViewControllerRegistration>(4);
|
||||
|
||||
private final List<RedirectViewControllerRegistration> redirectRegistrations =
|
||||
new ArrayList<RedirectViewControllerRegistration>(10);
|
||||
|
||||
private int order = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Register a view controller mapped to the given URL path or URL path pattern.
|
||||
* Map a view controller to the given URL path (or pattern) in order to render
|
||||
* a response with a pre-configured status code and view.
|
||||
*/
|
||||
public ViewControllerRegistration addViewController(String urlPath) {
|
||||
ViewControllerRegistration registration = new ViewControllerRegistration(urlPath);
|
||||
@@ -50,6 +53,30 @@ public class ViewControllerRegistry {
|
||||
return registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a view controller to the given URL path (or pattern) in order to redirect
|
||||
* to another URL. By default the redirect URL is expected to be relative to
|
||||
* the current ServletContext, i.e. as relative to the web application root.
|
||||
* @since 4.1
|
||||
*/
|
||||
public RedirectViewControllerRegistration addRedirectViewController(String urlPath, String redirectUrl) {
|
||||
RedirectViewControllerRegistration registration = new RedirectViewControllerRegistration(urlPath, redirectUrl);
|
||||
this.redirectRegistrations.add(registration);
|
||||
return registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a simple controller to the given URL path (or pattern) in order to
|
||||
* set the response status to the given code without rendering a body.
|
||||
* @since 4.1
|
||||
*/
|
||||
public void addStatusController(String urlPath, HttpStatus statusCode) {
|
||||
ViewControllerRegistration registration = new ViewControllerRegistration(urlPath);
|
||||
registration.setStatusCode(statusCode);
|
||||
registration.getViewController().setStatusOnly(true);
|
||||
this.registrations.add(registration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the order to use for the {@code HandlerMapping} used to map view
|
||||
* controllers relative to other handler mappings configured in Spring MVC.
|
||||
@@ -66,13 +93,16 @@ public class ViewControllerRegistry {
|
||||
* controller mappings, or {@code null} for no registrations.
|
||||
*/
|
||||
protected AbstractHandlerMapping getHandlerMapping() {
|
||||
if (this.registrations.isEmpty()) {
|
||||
if (this.registrations.isEmpty() && this.redirectRegistrations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> urlMap = new LinkedHashMap<String, Object>();
|
||||
for (ViewControllerRegistration registration : this.registrations) {
|
||||
urlMap.put(registration.getUrlPath(), registration.getViewController());
|
||||
}
|
||||
for (RedirectViewControllerRegistration registration : this.redirectRegistrations) {
|
||||
urlMap.put(registration.getUrlPath(), registration.getViewController());
|
||||
}
|
||||
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
|
||||
handlerMapping.setOrder(this.order);
|
||||
handlerMapping.setUrlMap(urlMap);
|
||||
|
||||
@@ -133,8 +133,11 @@ public interface WebMvcConfigurer {
|
||||
MessageCodesResolver getMessageCodesResolver();
|
||||
|
||||
/**
|
||||
* Add view controllers to create a direct mapping between a URL path and
|
||||
* view name without the need for a controller in between.
|
||||
* Configure simple automated controllers pre-configured with the response
|
||||
* status code and/or a view to render the response body. This is useful in
|
||||
* cases where there is no need for custom controller logic -- e.g. render a
|
||||
* home page, perform simple site URL redirects, return a 404 status with
|
||||
* HTML content, a 204 with no content, and more.
|
||||
*/
|
||||
void addViewControllers(ViewControllerRegistry registry);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user