Support access to all URI vars via @PathVariable Map
Issue: SPR-9289
This commit is contained in:
parent
698d004260
commit
1d0e484eac
|
|
@ -79,6 +79,9 @@ import java.util.concurrent.Callable;
|
|||
* will match against the regular expression {@code [^\.]*} (i.e. any character
|
||||
* other than period), but this can be changed by specifying another regular
|
||||
* expression, like so: /hotels/{hotel:\d+}.
|
||||
* Additionally, {@code @PathVariable} can be used on a
|
||||
* {@link java.util.Map Map<String, String>} to gain access to all
|
||||
* URI template variables.
|
||||
* <li>{@link RequestParam @RequestParam} annotated parameters for access to
|
||||
* specific Servlet/Portlet request parameters. Parameter values will be
|
||||
* converted to the declared method argument type. Additionally,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -44,24 +44,25 @@ import org.springframework.web.multipart.MultipartResolver;
|
|||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* Resolves method arguments annotated with @{@link RequestParam}, arguments of
|
||||
* type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver}
|
||||
* abstraction, and arguments of type {@code javax.servlet.http.Part} in conjunction
|
||||
* with Servlet 3.0 multipart requests. This resolver can also be created in default
|
||||
* resolution mode in which simple types (int, long, etc.) not annotated
|
||||
* with @{@link RequestParam} are also treated as request parameters with the
|
||||
* Resolves method arguments annotated with @{@link RequestParam}, arguments of
|
||||
* type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver}
|
||||
* abstraction, and arguments of type {@code javax.servlet.http.Part} in conjunction
|
||||
* with Servlet 3.0 multipart requests. This resolver can also be created in default
|
||||
* resolution mode in which simple types (int, long, etc.) not annotated
|
||||
* with @{@link RequestParam} are also treated as request parameters with the
|
||||
* parameter name derived from the argument name.
|
||||
*
|
||||
* <p>If the method parameter type is {@link Map}, the request parameter name is used to
|
||||
* resolve the request parameter String value. The value is then converted to a {@link Map}
|
||||
* via type conversion assuming a suitable {@link Converter} or {@link PropertyEditor} has
|
||||
* been registered. If a request parameter name is not specified with a {@link Map} method
|
||||
* parameter type, the {@link RequestParamMapMethodArgumentResolver} is used instead
|
||||
* providing access to all request parameters in the form of a map.
|
||||
*
|
||||
* <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved request
|
||||
*
|
||||
* <p>If the method parameter type is {@link Map}, the name specified in the
|
||||
* annotation is used to resolve the request parameter String value. The value is
|
||||
* then converted to a {@link Map} via type conversion assuming a suitable
|
||||
* {@link Converter} or {@link PropertyEditor} has been registered.
|
||||
* Or if a request parameter name is not specified the
|
||||
* {@link RequestParamMapMethodArgumentResolver} is used instead to provide
|
||||
* access to all request parameters in the form of a map.
|
||||
*
|
||||
* <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved request
|
||||
* header values that don't yet match the method parameter type.
|
||||
*
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
|
|
@ -72,15 +73,15 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
private final boolean useDefaultResolution;
|
||||
|
||||
/**
|
||||
* @param beanFactory a bean factory used for resolving ${...} placeholder
|
||||
* and #{...} SpEL expressions in default values, or {@code null} if default
|
||||
* @param beanFactory a bean factory used for resolving ${...} placeholder
|
||||
* and #{...} SpEL expressions in default values, or {@code null} if default
|
||||
* values are not expected to contain expressions
|
||||
* @param useDefaultResolution in default resolution mode a method argument
|
||||
* that is a simple type, as defined in {@link BeanUtils#isSimpleProperty},
|
||||
* is treated as a request parameter even if it itsn't annotated, the
|
||||
* @param useDefaultResolution in default resolution mode a method argument
|
||||
* that is a simple type, as defined in {@link BeanUtils#isSimpleProperty},
|
||||
* is treated as a request parameter even if it itsn't annotated, the
|
||||
* request parameter name is derived from the method parameter name.
|
||||
*/
|
||||
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
|
||||
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
|
||||
boolean useDefaultResolution) {
|
||||
super(beanFactory);
|
||||
this.useDefaultResolution = useDefaultResolution;
|
||||
|
|
@ -89,15 +90,15 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
/**
|
||||
* Supports the following:
|
||||
* <ul>
|
||||
* <li>@RequestParam-annotated method arguments.
|
||||
* This excludes {@link Map} params where the annotation doesn't
|
||||
* specify a name. See {@link RequestParamMapMethodArgumentResolver}
|
||||
* <li>@RequestParam-annotated method arguments.
|
||||
* This excludes {@link Map} params where the annotation doesn't
|
||||
* specify a name. See {@link RequestParamMapMethodArgumentResolver}
|
||||
* instead for such params.
|
||||
* <li>Arguments of type {@link MultipartFile}
|
||||
* <li>Arguments of type {@link MultipartFile}
|
||||
* unless annotated with @{@link RequestPart}.
|
||||
* <li>Arguments of type {@code javax.servlet.http.Part}
|
||||
* <li>Arguments of type {@code javax.servlet.http.Part}
|
||||
* unless annotated with @{@link RequestPart}.
|
||||
* <li>In default resolution mode, simple type arguments
|
||||
* <li>In default resolution mode, simple type arguments
|
||||
* even if not with @{@link RequestParam}.
|
||||
* </ul>
|
||||
*/
|
||||
|
|
@ -131,8 +132,8 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
@Override
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
return (annotation != null) ?
|
||||
new RequestParamNamedValueInfo(annotation) :
|
||||
return (annotation != null) ?
|
||||
new RequestParamNamedValueInfo(annotation) :
|
||||
new RequestParamNamedValueInfo();
|
||||
}
|
||||
|
||||
|
|
@ -140,9 +141,9 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
|
||||
|
||||
Object arg;
|
||||
|
||||
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
|
||||
|
||||
if (MultipartFile.class.equals(parameter.getParameterType())) {
|
||||
|
|
@ -174,7 +175,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +185,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
throw new MultipartException("The current request is not a multipart request");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isMultipartFileCollection(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
if (Collection.class.equals(paramType) || List.class.isAssignableFrom(paramType)){
|
||||
|
|
@ -206,7 +207,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
private RequestParamNamedValueInfo() {
|
||||
super("", false, ValueConstants.DEFAULT_NONE);
|
||||
}
|
||||
|
||||
|
||||
private RequestParamNamedValueInfo(RequestParam annotation) {
|
||||
super(annotation.value(), annotation.required(), annotation.defaultValue());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.annotation;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.ServletRequestBindingException;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* Resolves {@link Map} method arguments annotated with an @{@link PathVariable}
|
||||
* where the annotation does not specify a path variable name. The created
|
||||
* {@link Map} contains all URI template name/value pairs.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
* @see PathVariableMethodArgumentResolver
|
||||
*/
|
||||
public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
PathVariable annot = parameter.getParameterAnnotation(PathVariable.class);
|
||||
return ((annot != null) && (Map.class.isAssignableFrom(parameter.getParameterType()))
|
||||
&& (!StringUtils.hasText(annot.value())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map with all URI template variables.
|
||||
* @throws ServletRequestBindingException if no URI vars are found in the
|
||||
* request attribute {@link HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE}
|
||||
*/
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> uriTemplateVars =
|
||||
(Map<String, String>) webRequest.getAttribute(
|
||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||
|
||||
if (CollectionUtils.isEmpty(uriTemplateVars)) {
|
||||
throw new ServletRequestBindingException(
|
||||
"No URI template variables for method parameter type [" + parameter.getParameterType() + "]");
|
||||
}
|
||||
|
||||
return new LinkedHashMap<String, String>(uriTemplateVars);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -16,10 +16,13 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.ServletRequestBindingException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
|
@ -27,6 +30,7 @@ import org.springframework.web.bind.annotation.ValueConstants;
|
|||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.View;
|
||||
|
|
@ -34,13 +38,23 @@ import org.springframework.web.servlet.View;
|
|||
/**
|
||||
* Resolves method arguments annotated with an @{@link PathVariable}.
|
||||
*
|
||||
* <p>An @{@link PathVariable} is a named value that gets resolved from a URI template variable. It is always
|
||||
* required and does not have a default value to fall back on. See the base class
|
||||
* {@link org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver} for more information on how named values are processed.
|
||||
*
|
||||
* <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved path variable values that
|
||||
* <p>An @{@link PathVariable} is a named value that gets resolved from a URI
|
||||
* template variable. It is always required and does not have a default value
|
||||
* to fall back on. See the base class
|
||||
* {@link org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver}
|
||||
* for more information on how named values are processed.
|
||||
*
|
||||
* <p>If the method parameter type is {@link Map}, the name specified in the
|
||||
* annotation is used to resolve the URI variable String value. The value is
|
||||
* then converted to a {@link Map} via type conversion assuming a suitable
|
||||
* {@link Converter} or {@link PropertyEditor} has been registered.
|
||||
* Or if the annotation does not specify name the
|
||||
* {@link RequestParamMapMethodArgumentResolver} is used instead to provide
|
||||
* access to all URI variables in a map.
|
||||
*
|
||||
* <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved path variable values that
|
||||
* don't yet match the method parameter type.
|
||||
*
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
|
|
@ -52,7 +66,14 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
}
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(PathVariable.class);
|
||||
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
|
||||
return false;
|
||||
}
|
||||
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
|
||||
return StringUtils.hasText(paramName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -64,7 +85,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
|
||||
Map<String, String> uriTemplateVars =
|
||||
Map<String, String> uriTemplateVars =
|
||||
(Map<String, String>) request.getAttribute(
|
||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||
return (uriTemplateVars != null) ? uriTemplateVars.get(name) : null;
|
||||
|
|
@ -79,10 +100,10 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
|
|||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void handleResolvedValue(Object arg,
|
||||
String name,
|
||||
protected void handleResolvedValue(Object arg,
|
||||
String name,
|
||||
MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest request) {
|
||||
String key = View.PATH_VARIABLES;
|
||||
int scope = RequestAttributes.SCOPE_REQUEST;
|
||||
|
|
|
|||
|
|
@ -454,6 +454,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
|
||||
resolvers.add(new RequestParamMapMethodArgumentResolver());
|
||||
resolvers.add(new PathVariableMethodArgumentResolver());
|
||||
resolvers.add(new PathVariableMapMethodArgumentResolver());
|
||||
resolvers.add(new ServletModelAttributeMethodProcessor(false));
|
||||
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
|
||||
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.web.bind.ServletRequestBindingException;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* Test fixture with {@link PathVariableMapMethodArgumentResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class PathVariableMapMethodArgumentResolverTests {
|
||||
|
||||
private PathVariableMapMethodArgumentResolver resolver;
|
||||
|
||||
private MethodParameter paramMap;
|
||||
|
||||
private MethodParameter paramNamedMap;
|
||||
|
||||
private MethodParameter paramMapNoAnnot;
|
||||
|
||||
private ModelAndViewContainer mavContainer;
|
||||
|
||||
private ServletWebRequest webRequest;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
resolver = new PathVariableMapMethodArgumentResolver();
|
||||
|
||||
Method method = getClass().getMethod("handle", Map.class, Map.class, Map.class);
|
||||
paramMap = new MethodParameter(method, 0);
|
||||
paramNamedMap = new MethodParameter(method, 1);
|
||||
paramMapNoAnnot = new MethodParameter(method, 2);
|
||||
|
||||
mavContainer = new ModelAndViewContainer();
|
||||
request = new MockHttpServletRequest();
|
||||
webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsParameter() {
|
||||
assertTrue(resolver.supportsParameter(paramMap));
|
||||
assertFalse(resolver.supportsParameter(paramNamedMap));
|
||||
assertFalse(resolver.supportsParameter(paramMapNoAnnot));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgument() throws Exception {
|
||||
Map<String, String> uriTemplateVars = new HashMap<String, String>();
|
||||
uriTemplateVars.put("name1", "value1");
|
||||
uriTemplateVars.put("name2", "value2");
|
||||
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
||||
|
||||
Object result = resolver.resolveArgument(paramMap, mavContainer, webRequest, null);
|
||||
|
||||
assertEquals(uriTemplateVars, result);
|
||||
}
|
||||
|
||||
@Test(expected=ServletRequestBindingException.class)
|
||||
public void resolveArgumentNoUriVars() throws Exception {
|
||||
resolver.resolveArgument(paramMap, mavContainer, webRequest, null);
|
||||
}
|
||||
|
||||
|
||||
public void handle(
|
||||
@PathVariable Map<String, String> map,
|
||||
@PathVariable(value = "name") Map<String, String> namedMap,
|
||||
Map<String, String> mapWithoutAnnotat) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ Changes in version 3.2 M1
|
|||
* fix content negotiation issue when sorting selected media types by quality value
|
||||
* Prevent further writing to the response when @ResponseStatus contains a reason
|
||||
* Deprecate HttpStatus codes 419, 420, 421
|
||||
* support access to all URI vars via @PathVariable Map<String, String>
|
||||
|
||||
Changes in version 3.1.1 (2012-02-16)
|
||||
-------------------------------------
|
||||
|
|
|
|||
|
|
@ -992,6 +992,11 @@ public String findPet(<emphasis role="bold">@PathVariable</emphasis> String owne
|
|||
return "displayPet";
|
||||
}</programlisting>
|
||||
|
||||
<para>When a <interfacename>@PathVariable</interfacename> annotation is
|
||||
used on a <classname>Map<String, String></classname> argument, the
|
||||
map is populated with all URI template variables.
|
||||
</para>
|
||||
|
||||
<para>A URI template can be assembled from type and path level
|
||||
<emphasis>@RequestMapping</emphasis> annotations. As a result the
|
||||
<methodname>findPet()</methodname> method can be invoked with a URL
|
||||
|
|
|
|||
Loading…
Reference in New Issue