Browse Source

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
pull/537/merge
Rossen Stoyanchev 12 years ago
parent
commit
9d479feadd
  1. 12
      spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
  2. 25
      spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java
  3. 45
      spring-web/src/main/java/org/springframework/web/method/support/CompositeUriComponentsContributor.java
  4. 64
      spring-web/src/test/java/org/springframework/web/method/support/CompositeUriComponentsContributorTests.java
  5. 51
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
  6. 41
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMethodMappingNamingStrategy.java
  7. 48
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
  8. 5
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
  9. 60
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategy.java
  10. 84
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
  11. 29
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java
  12. 1
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
  13. 13
      spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
  14. 72
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategyTests.java

12
spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java

@ -266,6 +266,18 @@ import java.util.concurrent.Callable; @@ -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").

25
spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java

@ -256,34 +256,45 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod @@ -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();
}
}

45
spring-web/src/main/java/org/springframework/web/method/support/CompositeUriComponentsContributor.java

@ -38,7 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder; @@ -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 @@ -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 @@ -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 @@ -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;
}
}
}
}

64
spring-web/src/test/java/org/springframework/web/method/support/CompositeUriComponentsContributorTests.java

@ -0,0 +1,64 @@ @@ -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) {
}
}

51
spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java

@ -71,10 +71,15 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap @@ -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 @@ -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 @@ -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 @@ -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.");
}
}
}
/**

41
spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMethodMappingNamingStrategy.java

@ -0,0 +1,41 @@ @@ -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);
}

48
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java

@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.method; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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());
}

5
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java

@ -55,6 +55,11 @@ import org.springframework.web.util.WebUtils; @@ -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}.
*/

60
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategy.java

@ -0,0 +1,60 @@ @@ -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();
}
}

84
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java

@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation; @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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;
}
/**

29
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java

@ -123,21 +123,32 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod @@ -123,21 +123,32 @@ 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();
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);
}
if (conversionService != null) {
value = conversionService.convert(value, new TypeDescriptor(parameter), STRING_TYPE_DESCRIPTOR);
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();
}
uriVariables.put(name, value);
}

1
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java

@ -234,6 +234,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi @@ -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()),

13
spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java

@ -47,6 +47,7 @@ import org.springframework.web.context.WebApplicationContext; @@ -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 { @@ -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

72
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategyTests.java

@ -0,0 +1,72 @@ @@ -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() {
}
}
}
Loading…
Cancel
Save