Browse Source

SPR-8248

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4226 50f2f4bb-b051-0410-bef5-90022cba6387
pull/1/merge
Rossen Stoyanchev 15 years ago
parent
commit
6f8fa24e59
  1. 28
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
  2. 5
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java
  3. 6
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java
  4. 11
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ConsumesRequestCondition.java
  5. 2
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java
  6. 1
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
  7. 40
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodDetectionTests.java
  8. 28
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletHandlerMethodTests.java

28
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java

@ -97,29 +97,30 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
/** /**
* Detect and register handler methods for the specified handler. * Detect and register handler methods for the specified handler.
*/ */
private void detectHandlerMethods(final String handlerName) { private void detectHandlerMethods(final String beanName) {
Class<?> handlerType = getApplicationContext().getType(handlerName); Class<?> handlerType = getApplicationContext().getType(beanName);
Set<Method> methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() { Set<Method> methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() {
public boolean matches(Method method) { public boolean matches(Method method) {
return getKeyForMethod(method) != null; return getKeyForMethod(beanName, method) != null;
} }
}); });
for (Method method : methods) { for (Method method : methods) {
T key = getKeyForMethod(method); T key = getKeyForMethod(beanName, method);
HandlerMethod handlerMethod = new HandlerMethod(handlerName, getApplicationContext(), method); HandlerMethod handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
registerHandlerMethod(key, handlerMethod); registerHandlerMethod(key, handlerMethod);
} }
} }
/** /**
* Provides a lookup key for the given method. A method for which no key can be determined is * Provides a lookup key for the given bean method. A method for which no key can be determined is
* not considered a handler method. * not considered a handler method.
* *
* @param beanName the name of the bean the method belongs to
* @param method the method to create a key for * @param method the method to create a key for
* @return the lookup key, or {@code null} if the method has none * @return the lookup key, or {@code null} if the method has none
*/ */
protected abstract T getKeyForMethod(Method method); protected abstract T getKeyForMethod(String beanName, Method method);
/** /**
* Registers a {@link HandlerMethod} under the given key. * Registers a {@link HandlerMethod} under the given key.
@ -133,12 +134,13 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
Assert.notNull(handlerMethod, "'handlerMethod' must not be null"); Assert.notNull(handlerMethod, "'handlerMethod' must not be null");
HandlerMethod mappedHandlerMethod = handlerMethods.get(key); HandlerMethod mappedHandlerMethod = handlerMethods.get(key);
if (mappedHandlerMethod != null && !mappedHandlerMethod.equals(handlerMethod)) { if (mappedHandlerMethod != null && !mappedHandlerMethod.equals(handlerMethod)) {
throw new IllegalStateException("Cannot map " + handlerMethod + " to \"" + key + "\": There is already " throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean()
+ mappedHandlerMethod + " mapped."); + "' bean method \n" + handlerMethod + "\nto " + key + ": There is already '"
+ mappedHandlerMethod.getBean() + "' bean method\n" + mappedHandlerMethod + " mapped.");
} }
handlerMethods.put(key, handlerMethod); handlerMethods.put(key, handlerMethod);
if (logger.isDebugEnabled()) { if (logger.isInfoEnabled()) {
logger.debug("Mapped \"" + key + "\" onto " + handlerMethod); logger.info("Mapped \"" + key + "\" onto " + handlerMethod);
} }
} }
@ -149,14 +151,14 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
return null; return null;
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for [" + key + "]"); logger.debug("Looking up handler method with key [" + key + "]");
} }
HandlerMethod handlerMethod = lookupHandlerMethod(key, request); HandlerMethod handlerMethod = lookupHandlerMethod(key, request);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
if (handlerMethod != null) { if (handlerMethod != null) {
logger.debug("Returning [" + handlerMethod + "] as best match for [" + key + "]"); logger.debug("Returning handler method [" + handlerMethod + "]");
} }
else { else {
logger.debug("Did not find handler method for [" + key + "]"); logger.debug("Did not find handler method for [" + key + "]");

5
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java

@ -161,16 +161,17 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap
* Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their * Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their
* attributes combined with method-level {@link RequestMapping @RequestMapping} attributes. * attributes combined with method-level {@link RequestMapping @RequestMapping} attributes.
* *
* @param beanName the name of the bean the method belongs to
* @param method the method to create a key for * @param method the method to create a key for
* @return the key, or {@code null} * @return the key, or {@code null}
* @see RequestKey#combine(RequestKey, PathMatcher) * @see RequestKey#combine(RequestKey, PathMatcher)
*/ */
@Override @Override
protected RequestKey getKeyForMethod(Method method) { protected RequestKey getKeyForMethod(String beanName, Method method) {
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (annotation != null) { if (annotation != null) {
RequestKey methodKey = RequestKey.createFromRequestMapping(annotation); RequestKey methodKey = RequestKey.createFromRequestMapping(annotation);
RequestMapping typeAnnot = AnnotationUtils.findAnnotation(method.getDeclaringClass(), RequestMapping.class); RequestMapping typeAnnot = getApplicationContext().findAnnotationOnBean(beanName, RequestMapping.class);
if (typeAnnot != null) { if (typeAnnot != null) {
RequestKey typeKey = RequestKey.createFromRequestMapping(typeAnnot); RequestKey typeKey = RequestKey.createFromRequestMapping(typeAnnot);
return typeKey.combine(methodKey, pathMatcher); return typeKey.combine(methodKey, pathMatcher);

6
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java

@ -25,9 +25,9 @@ import org.springframework.web.servlet.View;
/** /**
* Handles return values that are of type {@link View} or {@link String} (i.e. a logical view name). * Handles return values that are of type {@link View} or {@link String} (i.e. a logical view name).
* <p>Since {@link String} return value can be interpeted in multiple ways, this resolver should be ordered * <p>Since a {@link String} return value may handled in different ways, especially in combination with method
* after return value handlers that recognize annotated return values such as the * annotations, this handler should be registered after return value handlers that look for method annotations
* {@link ModelAttributeMethodProcessor} and the {@link RequestResponseBodyMethodProcessor}. * such as the {@link ModelAttributeMethodProcessor} and the {@link RequestResponseBodyMethodProcessor}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1

11
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ConsumesRequestCondition.java

@ -60,4 +60,13 @@ class ConsumesRequestCondition extends AbstractRequestCondition {
return false; return false;
} }
} @Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (mediaType != null) {
builder.append(mediaType.toString());
}
return builder.toString();
}
}

2
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java

@ -103,7 +103,7 @@ public class HandlerMethodMappingTests {
} }
@Override @Override
protected String getKeyForMethod(Method method) { protected String getKeyForMethod(String beanName, Method method) {
String methodName = method.getName(); String methodName = method.getName();
return methodName.startsWith("handler") ? methodName : null; return methodName.startsWith("handler") ? methodName : null;
} }

1
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java

@ -1157,6 +1157,7 @@ public class ServletAnnotationControllerTests {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
try { try {
servlet.service(request, response); servlet.service(request, response);
fail("Didn't fail with due to ambiguous method mapping");
} }
catch (NestedServletException ex) { catch (NestedServletException ex) {
assertTrue(ex.getCause() instanceof IllegalStateException); assertTrue(ex.getCause() instanceof IllegalStateException);

40
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodDetectionTests.java

@ -63,7 +63,8 @@ public class RequestMappingHandlerMethodDetectionTests {
{ new MappingInterfaceController(), false}, { new MappingInterfaceController(), false},
{ new MappingAbstractClassController(), false}, { new MappingAbstractClassController(), false},
{ new ParameterizedInterfaceController(), false }, { new ParameterizedInterfaceController(), false },
{ new MappingParameterizedInterfaceController(), false }, { new MappingParameterizedInterfaceController(), false },
{ new MappingClassController(), false },
{ new MappingAbstractClassController(), true}, { new MappingAbstractClassController(), true},
{ new PlainController(), true} { new PlainController(), true}
}); });
@ -80,7 +81,7 @@ public class RequestMappingHandlerMethodDetectionTests {
@Test @Test
public void detectAndMapHandlerMethod() throws Exception { public void detectAndMapHandlerMethod() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/handle"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/type/handle");
TestRequestMappingHandlerMethodMapping mapping = createHandlerMapping(handler.getClass(), useAutoProxy); TestRequestMappingHandlerMethodMapping mapping = createHandlerMapping(handler.getClass(), useAutoProxy);
HandlerMethod handlerMethod = mapping.getHandlerInternal(request); HandlerMethod handlerMethod = mapping.getHandlerInternal(request);
@ -110,55 +111,76 @@ public class RequestMappingHandlerMethodDetectionTests {
} }
} }
/* Annotation on interface method */
@Controller @Controller
public interface MappingInterface { public interface MappingInterface {
@RequestMapping(value="/handle", method = RequestMethod.GET) @RequestMapping(value="/handle", method = RequestMethod.GET)
void handle(); void handle();
} }
@RequestMapping(value="/type")
public static class MappingInterfaceController implements MappingInterface { public static class MappingInterfaceController implements MappingInterface {
public void handle() { public void handle() {
} }
} }
/* Annotation on abstract class method */
@Controller @Controller
public static abstract class MappingAbstractClass { public static abstract class MappingAbstractClass {
@RequestMapping(value = "/handle", method = RequestMethod.GET) @RequestMapping(value = "/handle", method = RequestMethod.GET)
public abstract void handle(); public abstract void handle();
} }
@RequestMapping(value="/type")
public static class MappingAbstractClassController extends MappingAbstractClass { public static class MappingAbstractClassController extends MappingAbstractClass {
public void handle() { public void handle() {
} }
} }
/* Annotation on parameterized controller method */
@Controller @Controller
public interface ParameterizedInterface<T> { public interface ParameterizedInterface<T> {
void handle(T object); void handle(T object);
} }
@RequestMapping(value="/type")
public static class ParameterizedInterfaceController implements ParameterizedInterface<TestBean> { public static class ParameterizedInterfaceController implements ParameterizedInterface<TestBean> {
@RequestMapping(value = "/handle", method = RequestMethod.GET) @RequestMapping(value = "/handle", method = RequestMethod.GET)
public void handle(TestBean object) { public void handle(TestBean object) {
} }
} }
/* Annotation on parameterized interface method */
@Controller @Controller
public interface MappingParameterizedInterface<T> { public interface MappingParameterizedInterface<T> {
@RequestMapping(value = "/handle", method = RequestMethod.GET) @RequestMapping(value = "/handle", method = RequestMethod.GET)
void handle(T object); void handle(T object);
} }
@RequestMapping(value="/type")
public static class MappingParameterizedInterfaceController implements MappingParameterizedInterface<TestBean> { public static class MappingParameterizedInterfaceController implements MappingParameterizedInterface<TestBean> {
public void handle(TestBean object) { public void handle(TestBean object) {
} }
} }
/* Type + method annotations, method in parent class only (SPR-8248) */
@Controller @Controller
public static class PlainController { public static class MappingClass {
public PlainController() { @RequestMapping(value = "/handle", method = RequestMethod.GET)
public void handle(TestBean object) {
} }
}
@RequestMapping(value="/type")
public static class MappingClassController extends MappingClass {
// Method in parent class only
}
/* Annotations on controller class */
@Controller
@RequestMapping(value="/type")
public static class PlainController {
@RequestMapping(value = "/handle", method = RequestMethod.GET) @RequestMapping(value = "/handle", method = RequestMethod.GET)
public void handle() { public void handle() {
} }

28
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletHandlerMethodTests.java

@ -16,6 +16,14 @@
package org.springframework.web.servlet.mvc.method.annotation; 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.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.beans.PropertyEditorSupport; import java.beans.PropertyEditorSupport;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
@ -40,6 +48,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -53,7 +62,6 @@ import javax.xml.bind.annotation.XmlRootElement;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor; import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.DefaultPointcutAdvisor;
@ -133,9 +141,6 @@ import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionRes
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter; import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.util.NestedServletException;
import static org.junit.Assert.*;
/** /**
* The origin of this test fixture is {@link ServletHandlerMethodTests} with tests in this class adapted to run * The origin of this test fixture is {@link ServletHandlerMethodTests} with tests in this class adapted to run
@ -771,18 +776,13 @@ public class ServletHandlerMethodTests {
@Test @Test
public void equivalentMappingsWithSameMethodName() throws Exception { public void equivalentMappingsWithSameMethodName() throws Exception {
initDispatcherServlet(ChildController.class, null);
servlet.init(new MockServletConfig());
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/child/test");
request.addParameter("childId", "100");
MockHttpServletResponse response = new MockHttpServletResponse();
try { try {
servlet.service(request, response); initDispatcherServlet(ChildController.class, null);
fail("Expected 'method already mapped' error");
} }
catch (NestedServletException ex) { catch (BeanCreationException e) {
assertTrue(ex.getCause() instanceof IllegalStateException); assertTrue(e.getCause() instanceof IllegalStateException);
assertTrue(ex.getCause().getMessage().contains("doGet")); assertTrue(e.getCause().getMessage().contains("Ambiguous mapping"));
} }
} }

Loading…
Cancel
Save