From da3ad5623b02545c8095f16716bfa217bef5a8be Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 8 May 2011 19:31:29 +0000 Subject: [PATCH] Refine HandlerMethod registration to allow detection by handler instance as well as by bean name git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4276 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../handler/AbstractHandlerMethodMapping.java | 69 ++++++++++++------- .../RequestMappingHandlerMapping.java | 34 +++++---- .../handler/HandlerMethodMappingTests.java | 34 ++++----- .../annotation/ServletHandlerMethodTests.java | 2 +- .../UriTemplateServletHandlerMethodTests.java | 1 + 5 files changed, 80 insertions(+), 60 deletions(-) diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index c41753cad5f..502d43dce47 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -96,6 +96,14 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap return urlPathHelper; } + /** + * Return the map with all {@link HandlerMethod}s. The key of the map is the generic type + * {@code } containing request mapping conditions. + */ + public Map getHandlerMethods() { + return Collections.unmodifiableMap(handlerMethods); + } + /** * Calls the initialization of the superclass and detects handlers. */ @@ -115,35 +123,36 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } for (String beanName : getApplicationContext().getBeanNamesForType(Object.class)) { - if (isHandler(beanName)){ + if (isHandler(getApplicationContext().getType(beanName))){ detectHandlerMethods(beanName); } } } /** - * Determines if the given bean is a handler that should be introspected for handler methods. - * @param beanName the name of the bean to check - * @return true if the bean is a handler and may contain handler methods, false otherwise. + * Determines if the given type could contain handler methods. + * @param beanType the type to check + * @return true if this a type that could contain handler methods, false otherwise. */ - protected abstract boolean isHandler(String beanName); + protected abstract boolean isHandler(Class beanType); /** * Detect and register handler methods for the specified handler. + * @param handler the bean name of a handler or a handler instance */ - private void detectHandlerMethods(final String beanName) { - Class handlerType = getApplicationContext().getType(beanName); - + protected void detectHandlerMethods(final Object handler) { + final Class handlerType = (handler instanceof String) ? + getApplicationContext().getType((String) handler) : handler.getClass(); + Set methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() { public boolean matches(Method method) { - return getMappingForMethod(beanName, method) != null; + return getMappingForMethod(method, handlerType) != null; } }); + for (Method method : methods) { - HandlerMethod handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method); - T mapping = getMappingForMethod(beanName, method); - Set paths = getMappingPaths(mapping); - registerHandlerMethod(paths, mapping, handlerMethod); + T mapping = getMappingForMethod(method, handlerType); + registerHandlerMethod(handler, method, mapping); } } @@ -151,40 +160,50 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap * Provides a request mapping for the given bean method. A method for which no request mapping can be determined * is not considered a handler method. * - * @param beanName the name of the bean the method belongs to * @param method the method to create a mapping for + * @param handlerType the actual handler type (possibly a subtype of {@code method.getDeclaringClass()}) * @return the mapping, or {@code null} if the method is not mapped */ - protected abstract T getMappingForMethod(String beanName, Method method); + protected abstract T getMappingForMethod(Method method, Class handlerType); /** * Registers a {@link HandlerMethod} with the given mapping. * - * @param paths URL paths mapped to this method - * @param mapping the mapping for the method - * @param handlerMethod the handler method to register + * @param handler the bean name of the handler or the actual handler instance + * @param method the method to register + * @param mapping the mapping conditions associated with the handler method * @throws IllegalStateException if another method was already register under the same mapping */ - protected void registerHandlerMethod(Set paths, T mapping, HandlerMethod handlerMethod) { - Assert.notNull(mapping, "'mapping' must not be null"); - Assert.notNull(handlerMethod, "'handlerMethod' must not be null"); - HandlerMethod mappedHandlerMethod = handlerMethods.get(mapping); - if (mappedHandlerMethod != null && !mappedHandlerMethod.equals(handlerMethod)) { + protected void registerHandlerMethod(Object handler, Method method, T mapping) { + HandlerMethod handlerMethod; + if (handler instanceof String) { + String beanName = (String) handler; + handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method); + } + else { + handlerMethod = new HandlerMethod(handler, method); + } + + HandlerMethod oldHandlerMethod = handlerMethods.get(mapping); + if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) { throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean() + "' bean method \n" + handlerMethod + "\nto " + mapping + ": There is already '" - + mappedHandlerMethod.getBean() + "' bean method\n" + mappedHandlerMethod + " mapped."); + + oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped."); } + handlerMethods.put(mapping, handlerMethod); if (logger.isInfoEnabled()) { logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod); } + + Set paths = getMappingPaths(mapping); for (String path : paths) { urlMap.add(path, mapping); } } /** - * Get the URL paths for the given mapping. + * Get the URL paths associated with the given mapping. */ protected abstract Set getMappingPaths(T mapping); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index afa7c6d0251..4365ea15524 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -46,12 +46,12 @@ import org.springframework.web.servlet.handler.MappedInterceptors; import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory; /** - * An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingInfo}s for the registration and + * An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingInfo}s for the registration and * the lookup of {@link HandlerMethod}s. - * + * * @author Arjen Poutsma * @author Rossen Stoyanchev - * @since 3.1 + * @since 3.1.0 */ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping { @@ -87,32 +87,30 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping beanType) { + return AnnotationUtils.findAnnotation(beanType, Controller.class) != null; } /** - * Provides a {@link RequestMappingInfo} for the given method. - *

Only {@link RequestMapping @RequestMapping}-annotated methods are considered. - * Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their + * Provides a {@link RequestMappingInfo} for the given method. + *

Only {@link RequestMapping @RequestMapping}-annotated methods are considered. + * Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their * 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 mapping for + * @param handlerType the actual handler type, possibly a sub-type of {@code method.getDeclaringClass()} * @return the mapping, or {@code null} * @see RequestMappingInfo#combine(RequestMappingInfo, PathMatcher) */ @Override - protected RequestMappingInfo getMappingForMethod(String beanName, Method method) { + protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); if (annotation != null) { RequestMappingInfo methodMapping = createFromRequestMapping(annotation); - RequestMapping typeAnnot = getApplicationContext().findAnnotationOnBean(beanName, RequestMapping.class); + RequestMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); if (typeAnnot != null) { RequestMappingInfo typeMapping = createFromRequestMapping(typeAnnot); return typeMapping.combine(methodMapping, pathMatcher); @@ -206,12 +204,12 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMappingFurthermore, the following assumptions are made about the input RequestMappings: - *

  • Each RequestMappingInfo has been fully matched to the request
  • The RequestMappingInfo contains + *

    Furthermore, the following assumptions are made about the input RequestMappings: + *

    • Each RequestMappingInfo has been fully matched to the request
    • The RequestMappingInfo contains * matched patterns only
    • Patterns are ordered with the best matching pattern at the top
    * * @see RequestMappingHandlerMapping#getMatchingMapping(RequestMappingInfo, String, HttpServletRequest) diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java index 788e8eb4143..8eb5476b6cd 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java @@ -41,46 +41,48 @@ public class HandlerMethodMappingTests { private AbstractHandlerMethodMapping mapping; - private HandlerMethod handlerMethod1; + private MyHandler handler; - private HandlerMethod handlerMethod2; + private Method method1; + + private Method method2; @Before public void setUp() throws Exception { mapping = new MyHandlerMethodMapping(); - MyHandler handler = new MyHandler(); - handlerMethod1 = new HandlerMethod(handler, "handlerMethod1"); - handlerMethod2 = new HandlerMethod(handler, "handlerMethod2"); + handler = new MyHandler(); + method1 = handler.getClass().getMethod("handlerMethod1"); + method2 = handler.getClass().getMethod("handlerMethod2"); } @Test(expected = IllegalStateException.class) public void registerDuplicates() { - mapping.registerHandlerMethod(new HashSet(), "foo", handlerMethod1); - mapping.registerHandlerMethod(new HashSet(), "foo", handlerMethod2); + mapping.registerHandlerMethod(handler, method1, "foo"); + mapping.registerHandlerMethod(handler, method2, "foo"); } @Test public void directMatch() throws Exception { String key = "foo"; - mapping.registerHandlerMethod(new HashSet(), key, handlerMethod1); + mapping.registerHandlerMethod(handler, method1, key); HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", key)); - assertEquals(handlerMethod1, result); + assertEquals(method1, result.getMethod()); } @Test public void patternMatch() throws Exception { - mapping.registerHandlerMethod(new HashSet(), "/fo*", handlerMethod1); - mapping.registerHandlerMethod(new HashSet(), "/f*", handlerMethod1); + mapping.registerHandlerMethod(handler, method1, "/fo*"); + mapping.registerHandlerMethod(handler, method1, "/f*"); HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo")); - assertEquals(handlerMethod1, result); + assertEquals(method1, result.getMethod()); } @Test(expected = IllegalStateException.class) public void ambiguousMatch() throws Exception { - mapping.registerHandlerMethod(new HashSet(), "/f?o", handlerMethod1); - mapping.registerHandlerMethod(new HashSet(), "/fo?", handlerMethod2); + mapping.registerHandlerMethod(handler, method1, "/f?o"); + mapping.registerHandlerMethod(handler, method2, "/fo?"); mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo")); } @@ -95,7 +97,7 @@ public class HandlerMethodMappingTests { } @Override - protected String getMappingForMethod(String beanName, Method method) { + protected String getMappingForMethod(Method method, Class handlerType) { String methodName = method.getName(); return methodName.startsWith("handler") ? methodName : null; } @@ -106,7 +108,7 @@ public class HandlerMethodMappingTests { } @Override - protected boolean isHandler(String beanName) { + protected boolean isHandler(Class beanType) { return true; } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletHandlerMethodTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletHandlerMethodTests.java index 30c06053c94..39a09143dad 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletHandlerMethodTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletHandlerMethodTests.java @@ -1531,6 +1531,7 @@ public class ServletHandlerMethodTests { @RequestMapping("/myPage") @SessionAttributes({"object1", "object2"}) + @Controller public interface MySessionAttributesControllerIfc { @RequestMapping(method = RequestMethod.GET) @@ -1540,7 +1541,6 @@ public class ServletHandlerMethodTests { String post(@ModelAttribute("object1") Object object1); } - @Controller public static class MySessionAttributesControllerImpl implements MySessionAttributesControllerIfc { public String get(Model model) { diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/UriTemplateServletHandlerMethodTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/UriTemplateServletHandlerMethodTests.java index 5f98d3cbd98..209e41d58cd 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/UriTemplateServletHandlerMethodTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/UriTemplateServletHandlerMethodTests.java @@ -542,6 +542,7 @@ public class UriTemplateServletHandlerMethodTests { } + @Controller @RequestMapping("/*/menu/**") public static class MenuTreeController {