Add naming strategy for @MVC request mappings.
This change adds a strategy for assigning a default name to an @RequestMapping controller method. The @RequestMapping annotation itself now has a name attribute allowing the explicit assignment of a mapping name. This is mainly intended for use in EL expressions in views. The RequestContext class now provides a getMvcUrl method that internally delegates to MvcUriComponents to look up the handler method. See the Javadoc of MvcUriComponents.fromMappingName. Issue: SPR-5779
This commit is contained in:
@@ -266,6 +266,18 @@ import java.util.concurrent.Callable;
|
||||
@Mapping
|
||||
public @interface RequestMapping {
|
||||
|
||||
|
||||
/**
|
||||
* Assign a name to this mapping.
|
||||
* <p><b>Supported at the method and also at type level!</b>
|
||||
* When used on both levels, a combined name is derived by
|
||||
* concatenation with "#" as separator.
|
||||
*
|
||||
* @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
|
||||
* @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* The primary mapping expressed by this annotation.
|
||||
* <p>In a Servlet environment: the path mapping URIs (e.g. "/myPath.do").
|
||||
|
||||
@@ -256,34 +256,45 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contributeMethodArgument(MethodParameter parameter, Object value,
|
||||
public void contributeMethodArgument(MethodParameter param, Object value,
|
||||
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
|
||||
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
Class<?> paramType = param.getParameterType();
|
||||
if (Map.class.isAssignableFrom(paramType) || MultipartFile.class.equals(paramType) ||
|
||||
"javax.servlet.http.Part".equals(paramType.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
RequestParam annot = parameter.getParameterAnnotation(RequestParam.class);
|
||||
String name = StringUtils.isEmpty(annot.value()) ? parameter.getParameterName() : annot.value();
|
||||
RequestParam annot = param.getParameterAnnotation(RequestParam.class);
|
||||
String name = (annot == null || StringUtils.isEmpty(annot.value()) ? param.getParameterName() : annot.value());
|
||||
|
||||
if (value == null) {
|
||||
builder.queryParam(name);
|
||||
}
|
||||
else if (value instanceof Collection) {
|
||||
for (Object element : (Collection<?>) value) {
|
||||
element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element);
|
||||
element = formatUriValue(conversionService, TypeDescriptor.nested(param, 1), element);
|
||||
builder.queryParam(name, element);
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value));
|
||||
builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(param), value));
|
||||
}
|
||||
}
|
||||
|
||||
protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) {
|
||||
return (cs != null ? (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR) : null);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
else if (value instanceof String) {
|
||||
return (String) value;
|
||||
}
|
||||
else if (cs != null) {
|
||||
return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
|
||||
}
|
||||
else {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
*/
|
||||
public class CompositeUriComponentsContributor implements UriComponentsContributor {
|
||||
|
||||
private final List<UriComponentsContributor> contributors = new LinkedList<UriComponentsContributor>();
|
||||
private final List<Object> contributors = new LinkedList<Object>();
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
@@ -81,18 +81,13 @@ public class CompositeUriComponentsContributor implements UriComponentsContribut
|
||||
* will be used by default.
|
||||
* @param contributors a collection of {@link UriComponentsContributor}
|
||||
* or {@link HandlerMethodArgumentResolver}s.
|
||||
* @param conversionService a ConversionService to use when method argument values
|
||||
* @param cs a ConversionService to use when method argument values
|
||||
* need to be formatted as Strings before being added to the URI
|
||||
*/
|
||||
public CompositeUriComponentsContributor(Collection<?> contributors, ConversionService conversionService) {
|
||||
public CompositeUriComponentsContributor(Collection<?> contributors, ConversionService cs) {
|
||||
Assert.notNull(contributors, "'uriComponentsContributors' must not be null");
|
||||
for (Object contributor : contributors) {
|
||||
if (contributor instanceof UriComponentsContributor) {
|
||||
this.contributors.add((UriComponentsContributor) contributor);
|
||||
}
|
||||
}
|
||||
this.conversionService =
|
||||
(conversionService != null ? conversionService : new DefaultFormattingConversionService());
|
||||
this.contributors.addAll(contributors);
|
||||
this.conversionService = (cs != null ? cs : new DefaultFormattingConversionService());
|
||||
}
|
||||
|
||||
|
||||
@@ -102,9 +97,17 @@ public class CompositeUriComponentsContributor implements UriComponentsContribut
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
for (UriComponentsContributor contributor : this.contributors) {
|
||||
if (contributor.supportsParameter(parameter)) {
|
||||
return true;
|
||||
for (Object c : this.contributors) {
|
||||
if (c instanceof UriComponentsContributor) {
|
||||
UriComponentsContributor contributor = (UriComponentsContributor) c;
|
||||
if (contributor.supportsParameter(parameter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (c instanceof HandlerMethodArgumentResolver) {
|
||||
if (((HandlerMethodArgumentResolver) c).supportsParameter(parameter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -114,10 +117,18 @@ public class CompositeUriComponentsContributor implements UriComponentsContribut
|
||||
public void contributeMethodArgument(MethodParameter parameter, Object value,
|
||||
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
|
||||
|
||||
for (UriComponentsContributor contributor : this.contributors) {
|
||||
if (contributor.supportsParameter(parameter)) {
|
||||
contributor.contributeMethodArgument(parameter, value, builder, uriVariables, conversionService);
|
||||
break;
|
||||
for (Object c : this.contributors) {
|
||||
if (c instanceof UriComponentsContributor) {
|
||||
UriComponentsContributor contributor = (UriComponentsContributor) c;
|
||||
if (contributor.supportsParameter(parameter)) {
|
||||
contributor.contributeMethodArgument(parameter, value, builder, uriVariables, conversionService);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (c instanceof HandlerMethodArgumentResolver) {
|
||||
if (((HandlerMethodArgumentResolver) c).supportsParameter(parameter)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.method.support;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Unit tests for
|
||||
* {@link org.springframework.web.method.support.CompositeUriComponentsContributor}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class CompositeUriComponentsContributorTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void supportsParameter() {
|
||||
|
||||
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
|
||||
resolvers.add(new RequestParamMethodArgumentResolver(false));
|
||||
resolvers.add(new RequestHeaderMethodArgumentResolver(null));
|
||||
resolvers.add(new RequestParamMethodArgumentResolver(true));
|
||||
|
||||
Method method = ClassUtils.getMethod(this.getClass(), "handleRequest", String.class, String.class, String.class);
|
||||
|
||||
CompositeUriComponentsContributor contributor = new CompositeUriComponentsContributor(resolvers);
|
||||
assertTrue(contributor.supportsParameter(new MethodParameter(method, 0)));
|
||||
assertTrue(contributor.supportsParameter(new MethodParameter(method, 1)));
|
||||
assertFalse(contributor.supportsParameter(new MethodParameter(method, 2)));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleRequest(@RequestParam String p1, String p2, @RequestHeader String h) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -71,10 +71,15 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
|
||||
private boolean detectHandlerMethodsInAncestorContexts = false;
|
||||
|
||||
private HandlerMethodMappingNamingStrategy<T> namingStrategy;
|
||||
|
||||
|
||||
private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();
|
||||
|
||||
private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();
|
||||
|
||||
private final MultiValueMap<String, HandlerMethod> nameMap = new LinkedMultiValueMap<String, HandlerMethod>();
|
||||
|
||||
|
||||
/**
|
||||
* Whether to detect handler methods in beans in ancestor ApplicationContexts.
|
||||
@@ -88,6 +93,16 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the naming strategy to use for assigning a default name to every
|
||||
* mapped handler method.
|
||||
*
|
||||
* @param namingStrategy strategy to use.
|
||||
*/
|
||||
public void setHandlerMethodMappingNamingStrategy(HandlerMethodMappingNamingStrategy<T> namingStrategy) {
|
||||
this.namingStrategy = namingStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map with all handler methods and their mappings.
|
||||
*/
|
||||
@@ -95,6 +110,14 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
return Collections.unmodifiableMap(this.handlerMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the handler methods mapped to the mapping with the given name.
|
||||
* @param mappingName the mapping name
|
||||
*/
|
||||
public List<HandlerMethod> getHandlerMethodsForMappingName(String mappingName) {
|
||||
return this.nameMap.get(mappingName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects handler methods at initialization.
|
||||
*/
|
||||
@@ -203,6 +226,34 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
||||
this.urlMap.add(pattern, mapping);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.namingStrategy != null) {
|
||||
String name = this.namingStrategy.getName(newHandlerMethod, mapping);
|
||||
updateNameMap(name, newHandlerMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNameMap(String name, HandlerMethod newHandlerMethod) {
|
||||
|
||||
List<HandlerMethod> handlerMethods = this.nameMap.get(name);
|
||||
if (handlerMethods != null) {
|
||||
for (HandlerMethod handlerMethod : handlerMethods) {
|
||||
if (handlerMethod.getMethod().equals(newHandlerMethod.getMethod())) {
|
||||
logger.trace("Mapping name already registered. Multiple controller instances perhaps?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("Mapping name=" + name);
|
||||
this.nameMap.add(name, newHandlerMethod);
|
||||
|
||||
if (this.nameMap.get(name).size() > 1) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Mapping name clash for handlerMethods=" + this.nameMap.get(name) +
|
||||
". Consider assigning explicit names.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.handler;
|
||||
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A strategy for assigning a name to a controller method mapping.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface HandlerMethodMappingNamingStrategy<T> {
|
||||
|
||||
/**
|
||||
* Determine the name for the given HandlerMethod and mapping.
|
||||
*
|
||||
* @param handlerMethod the handler method
|
||||
* @param mapping the mapping
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
String getName(HandlerMethod handlerMethod, T mapping);
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.method;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
|
||||
@@ -45,6 +46,8 @@ import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondit
|
||||
*/
|
||||
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final PatternsRequestCondition patternsCondition;
|
||||
|
||||
private final RequestMethodsRequestCondition methodsCondition;
|
||||
@@ -60,13 +63,11 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
private final RequestConditionHolder customConditionHolder;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given request conditions.
|
||||
*/
|
||||
public RequestMappingInfo(PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
|
||||
public RequestMappingInfo(String name, PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
|
||||
ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
|
||||
ProducesRequestCondition produces, RequestCondition<?> custom) {
|
||||
|
||||
this.name = (StringUtils.hasText(name) ? name : null);
|
||||
this.patternsCondition = (patterns != null ? patterns : new PatternsRequestCondition());
|
||||
this.methodsCondition = (methods != null ? methods : new RequestMethodsRequestCondition());
|
||||
this.paramsCondition = (params != null ? params : new ParamsRequestCondition());
|
||||
@@ -76,15 +77,32 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
this.customConditionHolder = new RequestConditionHolder(custom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given request conditions.
|
||||
*/
|
||||
public RequestMappingInfo(PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
|
||||
ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
|
||||
ProducesRequestCondition produces, RequestCondition<?> custom) {
|
||||
|
||||
this(null, patterns, methods, params, headers, consumes, produces, custom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-create a RequestMappingInfo with the given custom request condition.
|
||||
*/
|
||||
public RequestMappingInfo(RequestMappingInfo info, RequestCondition<?> customRequestCondition) {
|
||||
this(info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
|
||||
this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
|
||||
info.consumesCondition, info.producesCondition, customRequestCondition);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the name for this mapping, or {@code null}.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL patterns of this {@link RequestMappingInfo};
|
||||
* or instance with 0 patterns, never {@code null}.
|
||||
@@ -148,6 +166,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
*/
|
||||
@Override
|
||||
public RequestMappingInfo combine(RequestMappingInfo other) {
|
||||
String name = combineNames(other);
|
||||
PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
|
||||
RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
|
||||
ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
|
||||
@@ -156,7 +175,21 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
|
||||
RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
|
||||
|
||||
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition());
|
||||
return new RequestMappingInfo(name, patterns,
|
||||
methods, params, headers, consumes, produces, custom.getCondition());
|
||||
}
|
||||
|
||||
private String combineNames(RequestMappingInfo other) {
|
||||
if (this.name != null && other.name != null) {
|
||||
String separator = RequestMappingInfoHandlerMethodMappingNamingStrategy.SEPARATOR;
|
||||
return this.name + separator + other.name;
|
||||
}
|
||||
else if (this.name != null) {
|
||||
return this.name;
|
||||
}
|
||||
else {
|
||||
return (other.name != null ? other.name : null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,7 +221,8 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
return null;
|
||||
}
|
||||
|
||||
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition());
|
||||
return new RequestMappingInfo(this.name, patterns,
|
||||
methods, params, headers, consumes, produces, custom.getCondition());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ import org.springframework.web.util.WebUtils;
|
||||
*/
|
||||
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
|
||||
|
||||
|
||||
protected RequestMappingInfoHandlerMapping() {
|
||||
setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL path patterns associated with this {@link RequestMappingInfo}.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.mvc.method;
|
||||
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
|
||||
* HandlerMethodMappingNamingStrategy} for {@code RequestMappingInfo}-based handler
|
||||
* method mappings.
|
||||
*
|
||||
* If the {@code RequestMappingInfo} name attribute is set, its value is used.
|
||||
* Otherwise the name is based on the capital letters of the class name,
|
||||
* followed by "#" as a separator, and the method name. For example "TC#getFoo"
|
||||
* for a class named TestController with method getFoo.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public class RequestMappingInfoHandlerMethodMappingNamingStrategy
|
||||
implements HandlerMethodMappingNamingStrategy<RequestMappingInfo> {
|
||||
|
||||
/** Separator between the type and method-level parts of a HandlerMethod mapping name */
|
||||
public static final String SEPARATOR = "#";
|
||||
|
||||
|
||||
@Override
|
||||
public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
|
||||
if (mapping.getName() != null) {
|
||||
return mapping.getName();
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String simpleTypeName = handlerMethod.getBeanType().getSimpleName();
|
||||
for (int i = 0 ; i < simpleTypeName.length(); i++) {
|
||||
if (Character.isUpperCase(simpleTypeName.charAt(i))) {
|
||||
sb.append(simpleTypeName.charAt(i));
|
||||
}
|
||||
}
|
||||
sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.cglib.core.SpringNamingPolicy;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
@@ -50,9 +52,11 @@ import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.CompositeUriComponentsContributor;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
@@ -195,6 +199,43 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
|
||||
return fromMethod(info.getControllerMethod(), info.getArgumentValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link UriComponentsBuilder} from a request mapping identified
|
||||
* by name. The configured
|
||||
* {@link org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
|
||||
* HandlerMethodMappingNamingStrategy} assigns a default name to every
|
||||
* {@code @RequestMapping} method but an explicit name may also be assigned
|
||||
* through the {@code @RequestMapping} name attribute.
|
||||
*
|
||||
* <p>This is intended for use in EL expressions, typically in JSPs or other
|
||||
* view templates, which can use the convenience method:
|
||||
* {@link org.springframework.web.servlet.support.RequestContext#getMvcUrl(String, Object...)
|
||||
* RequestContext.getMvcUrl(String, Object...)}).
|
||||
*
|
||||
* <p>The default naming convention for mappings is based on the capital
|
||||
* letters of the class name, followed by "#" as a separator, and the method
|
||||
* name. For example "TC#getFoo" for a class named TestController with method
|
||||
* getFoo. Use explicit names where the naming convention does not produce
|
||||
* unique results.
|
||||
*
|
||||
* @param name the mapping name
|
||||
* @param argumentValues argument values for the controller method; those values
|
||||
* are important for {@code @RequestParam} and {@code @PathVariable} arguments
|
||||
* but may be passed as {@code null} otherwise.
|
||||
*
|
||||
* @return the UriComponentsBuilder
|
||||
*
|
||||
* @throws java.lang.IllegalStateException if the mapping name is not found
|
||||
* or there is no unique match
|
||||
*/
|
||||
public static UriComponentsBuilder fromMappingName(String name, Object... argumentValues) {
|
||||
RequestMappingInfoHandlerMapping hm = getRequestMappingInfoHandlerMapping();
|
||||
List<HandlerMethod> handlerMethods = hm.getHandlerMethodsForMappingName(name);
|
||||
Assert.state(handlerMethods != null, "Mapping name not found: " + name);
|
||||
Assert.state(handlerMethods.size() == 1, "No unique match for mapping name " + name + ": " + handlerMethods);
|
||||
return fromMethod(handlerMethods.get(0).getMethod(), argumentValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link UriComponentsBuilder} from the mapping of a controller method
|
||||
* and an array of method argument values. The array of values must match the
|
||||
@@ -263,6 +304,37 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
|
||||
}
|
||||
|
||||
protected static CompositeUriComponentsContributor getConfiguredUriComponentsContributor() {
|
||||
WebApplicationContext wac = getWebApplicationContext();
|
||||
if (wac == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return wac.getBean(MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, CompositeUriComponentsContributor.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No CompositeUriComponentsContributor bean with name '" +
|
||||
MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME + "'");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected static RequestMappingInfoHandlerMapping getRequestMappingInfoHandlerMapping() {
|
||||
WebApplicationContext wac = getWebApplicationContext();
|
||||
Assert.notNull(wac, "Cannot lookup handler method mappings without WebApplicationContext");
|
||||
try {
|
||||
return wac.getBean(RequestMappingInfoHandlerMapping.class);
|
||||
}
|
||||
catch (NoUniqueBeanDefinitionException ex) {
|
||||
throw new IllegalStateException("More than one RequestMappingInfoHandlerMapping beans found", ex);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
throw new IllegalStateException("No RequestMappingInfoHandlerMapping bean", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static WebApplicationContext getWebApplicationContext() {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
if (requestAttributes == null) {
|
||||
logger.debug("No request bound to the current thread: is DispatcherSerlvet used?");
|
||||
@@ -281,17 +353,7 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
|
||||
logger.debug("No WebApplicationContext found: not in a DispatcherServlet request?");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return wac.getBean(MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, CompositeUriComponentsContributor.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No CompositeUriComponentsContributor bean with name '" +
|
||||
MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME + "'");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return wac;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -123,23 +123,34 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contributeMethodArgument(MethodParameter parameter, Object value,
|
||||
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
|
||||
public void contributeMethodArgument(MethodParameter param, Object value,
|
||||
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService cs) {
|
||||
|
||||
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
if (Map.class.isAssignableFrom(param.getParameterType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
PathVariable annot = parameter.getParameterAnnotation(PathVariable.class);
|
||||
String name = StringUtils.isEmpty(annot.value()) ? parameter.getParameterName() : annot.value();
|
||||
|
||||
if (conversionService != null) {
|
||||
value = conversionService.convert(value, new TypeDescriptor(parameter), STRING_TYPE_DESCRIPTOR);
|
||||
}
|
||||
|
||||
PathVariable annot = param.getParameterAnnotation(PathVariable.class);
|
||||
String name = (StringUtils.isEmpty(annot.value()) ? param.getParameterName() : annot.value());
|
||||
value = formatUriValue(cs, new TypeDescriptor(param), value);
|
||||
uriVariables.put(name, value);
|
||||
}
|
||||
|
||||
protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
else if (value instanceof String) {
|
||||
return (String) value;
|
||||
}
|
||||
else if (cs != null) {
|
||||
return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
|
||||
}
|
||||
else {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class PathVariableNamedValueInfo extends NamedValueInfo {
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
|
||||
String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
|
||||
return new RequestMappingInfo(
|
||||
annotation.name(),
|
||||
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
|
||||
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
|
||||
new RequestMethodsRequestCondition(annotation.method()),
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.LocaleContextResolver;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.ThemeResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
@@ -596,6 +597,18 @@ public class RequestContext {
|
||||
return this.urlPathHelper.getOriginatingQueryString(this.request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a URL derived from a controller method's {@code @RequestMapping}.
|
||||
* This method internally uses
|
||||
* {@link org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder#fromMappingName(String, Object...)
|
||||
* MvcUriComponentsBuilder#fromMappingName(String, Object...)}. See its
|
||||
* Javadoc for more details.
|
||||
*/
|
||||
public String getMvcUrl(String mappingName, Object... handlerMethodArguments) {
|
||||
return MvcUriComponentsBuilder.fromMappingName(
|
||||
mappingName, handlerMethodArguments).build().encode().toUriString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
|
||||
* @param code code of the message
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.mvc.method;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for
|
||||
* {@link org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMethodMappingNamingStrategy}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class RequestMappingInfoHandlerMethodMappingNamingStrategyTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void getNameExplicit() {
|
||||
|
||||
Method method = ClassUtils.getMethod(TestController.class, "handle");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new TestController(), method);
|
||||
|
||||
RequestMappingInfo rmi = new RequestMappingInfo("foo", null, null, null, null, null, null, null);
|
||||
|
||||
HandlerMethodMappingNamingStrategy strategy = new RequestMappingInfoHandlerMethodMappingNamingStrategy();
|
||||
|
||||
assertEquals("foo", strategy.getName(handlerMethod, rmi));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNameConvention() {
|
||||
|
||||
Method method = ClassUtils.getMethod(TestController.class, "handle");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new TestController(), method);
|
||||
|
||||
RequestMappingInfo rmi = new RequestMappingInfo(null, null, null, null, null, null, null, null);
|
||||
|
||||
HandlerMethodMappingNamingStrategy strategy = new RequestMappingInfoHandlerMethodMappingNamingStrategy();
|
||||
|
||||
assertEquals("TC#handle", strategy.getName(handlerMethod, rmi));
|
||||
}
|
||||
|
||||
|
||||
private static class TestController {
|
||||
|
||||
@RequestMapping
|
||||
public void handle() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user