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
This commit is contained in:
Rossen Stoyanchev 2011-05-08 19:31:29 +00:00
parent 11c31085fe
commit da3ad5623b
5 changed files with 80 additions and 60 deletions

View File

@ -96,6 +96,14 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
return urlPathHelper; return urlPathHelper;
} }
/**
* Return the map with all {@link HandlerMethod}s. The key of the map is the generic type
* <strong>{@code <T>}</strong> containing request mapping conditions.
*/
public Map<T, HandlerMethod> getHandlerMethods() {
return Collections.unmodifiableMap(handlerMethods);
}
/** /**
* Calls the initialization of the superclass and detects handlers. * Calls the initialization of the superclass and detects handlers.
*/ */
@ -115,35 +123,36 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
logger.debug("Looking for request mappings in application context: " + getApplicationContext()); logger.debug("Looking for request mappings in application context: " + getApplicationContext());
} }
for (String beanName : getApplicationContext().getBeanNamesForType(Object.class)) { for (String beanName : getApplicationContext().getBeanNamesForType(Object.class)) {
if (isHandler(beanName)){ if (isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName); detectHandlerMethods(beanName);
} }
} }
} }
/** /**
* Determines if the given bean is a handler that should be introspected for handler methods. * Determines if the given type could contain handler methods.
* @param beanName the name of the bean to check * @param beanType the type to check
* @return true if the bean is a handler and may contain handler methods, false otherwise. * @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. * 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) { protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = getApplicationContext().getType(beanName); final Class<?> handlerType = (handler instanceof String) ?
getApplicationContext().getType((String) handler) : handler.getClass();
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 getMappingForMethod(beanName, method) != null; return getMappingForMethod(method, handlerType) != null;
} }
}); });
for (Method method : methods) { for (Method method : methods) {
HandlerMethod handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method); T mapping = getMappingForMethod(method, handlerType);
T mapping = getMappingForMethod(beanName, method); registerHandlerMethod(handler, method, mapping);
Set<String> paths = getMappingPaths(mapping);
registerHandlerMethod(paths, mapping, handlerMethod);
} }
} }
@ -151,40 +160,50 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
* Provides a request mapping for the given bean method. A method for which no request mapping can be determined * 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. * 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 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 * @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. * Registers a {@link HandlerMethod} with the given mapping.
* *
* @param paths URL paths mapped to this method * @param handler the bean name of the handler or the actual handler instance
* @param mapping the mapping for the method * @param method the method to register
* @param handlerMethod the handler 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 * @throws IllegalStateException if another method was already register under the same mapping
*/ */
protected void registerHandlerMethod(Set<String> paths, T mapping, HandlerMethod handlerMethod) { protected void registerHandlerMethod(Object handler, Method method, T mapping) {
Assert.notNull(mapping, "'mapping' must not be null"); HandlerMethod handlerMethod;
Assert.notNull(handlerMethod, "'handlerMethod' must not be null"); if (handler instanceof String) {
HandlerMethod mappedHandlerMethod = handlerMethods.get(mapping); String beanName = (String) handler;
if (mappedHandlerMethod != null && !mappedHandlerMethod.equals(handlerMethod)) { 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() throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean()
+ "' bean method \n" + handlerMethod + "\nto " + mapping + ": There is already '" + "' 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); handlerMethods.put(mapping, handlerMethod);
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod); logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
} }
Set<String> paths = getMappingPaths(mapping);
for (String path : paths) { for (String path : paths) {
urlMap.add(path, mapping); urlMap.add(path, mapping);
} }
} }
/** /**
* Get the URL paths for the given mapping. * Get the URL paths associated with the given mapping.
*/ */
protected abstract Set<String> getMappingPaths(T mapping); protected abstract Set<String> getMappingPaths(T mapping);

View File

@ -51,7 +51,7 @@ import org.springframework.web.servlet.mvc.method.condition.RequestConditionFact
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1.0
*/ */
public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> { public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
@ -87,13 +87,11 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<R
/** /**
* {@inheritDoc} * {@inheritDoc}
* The handler determination is made based on the presence of a type-level {@link Controller} or * The handler determination in this method is made based on the presence of a type-level {@link Controller} annotation.
* a type-level {@link RequestMapping} annotation.
*/ */
@Override @Override
protected boolean isHandler(String beanName) { protected boolean isHandler(Class<?> beanType) {
return ((getApplicationContext().findAnnotationOnBean(beanName, RequestMapping.class) != null) || return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
(getApplicationContext().findAnnotationOnBean(beanName, Controller.class) != null));
} }
/** /**
@ -102,17 +100,17 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<R
* 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 mapping for * @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} * @return the mapping, or {@code null}
* @see RequestMappingInfo#combine(RequestMappingInfo, PathMatcher) * @see RequestMappingInfo#combine(RequestMappingInfo, PathMatcher)
*/ */
@Override @Override
protected RequestMappingInfo getMappingForMethod(String beanName, Method method) { protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (annotation != null) { if (annotation != null) {
RequestMappingInfo methodMapping = createFromRequestMapping(annotation); RequestMappingInfo methodMapping = createFromRequestMapping(annotation);
RequestMapping typeAnnot = getApplicationContext().findAnnotationOnBean(beanName, RequestMapping.class); RequestMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnot != null) { if (typeAnnot != null) {
RequestMappingInfo typeMapping = createFromRequestMapping(typeAnnot); RequestMappingInfo typeMapping = createFromRequestMapping(typeAnnot);
return typeMapping.combine(methodMapping, pathMatcher); return typeMapping.combine(methodMapping, pathMatcher);

View File

@ -41,46 +41,48 @@ public class HandlerMethodMappingTests {
private AbstractHandlerMethodMapping<String> mapping; private AbstractHandlerMethodMapping<String> mapping;
private HandlerMethod handlerMethod1; private MyHandler handler;
private HandlerMethod handlerMethod2; private Method method1;
private Method method2;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
mapping = new MyHandlerMethodMapping(); mapping = new MyHandlerMethodMapping();
MyHandler handler = new MyHandler(); handler = new MyHandler();
handlerMethod1 = new HandlerMethod(handler, "handlerMethod1"); method1 = handler.getClass().getMethod("handlerMethod1");
handlerMethod2 = new HandlerMethod(handler, "handlerMethod2"); method2 = handler.getClass().getMethod("handlerMethod2");
} }
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void registerDuplicates() { public void registerDuplicates() {
mapping.registerHandlerMethod(new HashSet<String>(), "foo", handlerMethod1); mapping.registerHandlerMethod(handler, method1, "foo");
mapping.registerHandlerMethod(new HashSet<String>(), "foo", handlerMethod2); mapping.registerHandlerMethod(handler, method2, "foo");
} }
@Test @Test
public void directMatch() throws Exception { public void directMatch() throws Exception {
String key = "foo"; String key = "foo";
mapping.registerHandlerMethod(new HashSet<String>(), key, handlerMethod1); mapping.registerHandlerMethod(handler, method1, key);
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", key)); HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", key));
assertEquals(handlerMethod1, result); assertEquals(method1, result.getMethod());
} }
@Test @Test
public void patternMatch() throws Exception { public void patternMatch() throws Exception {
mapping.registerHandlerMethod(new HashSet<String>(), "/fo*", handlerMethod1); mapping.registerHandlerMethod(handler, method1, "/fo*");
mapping.registerHandlerMethod(new HashSet<String>(), "/f*", handlerMethod1); mapping.registerHandlerMethod(handler, method1, "/f*");
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo")); HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo"));
assertEquals(handlerMethod1, result); assertEquals(method1, result.getMethod());
} }
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void ambiguousMatch() throws Exception { public void ambiguousMatch() throws Exception {
mapping.registerHandlerMethod(new HashSet<String>(), "/f?o", handlerMethod1); mapping.registerHandlerMethod(handler, method1, "/f?o");
mapping.registerHandlerMethod(new HashSet<String>(), "/fo?", handlerMethod2); mapping.registerHandlerMethod(handler, method2, "/fo?");
mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo")); mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo"));
} }
@ -95,7 +97,7 @@ public class HandlerMethodMappingTests {
} }
@Override @Override
protected String getMappingForMethod(String beanName, Method method) { protected String getMappingForMethod(Method method, Class<?> handlerType) {
String methodName = method.getName(); String methodName = method.getName();
return methodName.startsWith("handler") ? methodName : null; return methodName.startsWith("handler") ? methodName : null;
} }
@ -106,7 +108,7 @@ public class HandlerMethodMappingTests {
} }
@Override @Override
protected boolean isHandler(String beanName) { protected boolean isHandler(Class<?> beanType) {
return true; return true;
} }

View File

@ -1531,6 +1531,7 @@ public class ServletHandlerMethodTests {
@RequestMapping("/myPage") @RequestMapping("/myPage")
@SessionAttributes({"object1", "object2"}) @SessionAttributes({"object1", "object2"})
@Controller
public interface MySessionAttributesControllerIfc { public interface MySessionAttributesControllerIfc {
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
@ -1540,7 +1541,6 @@ public class ServletHandlerMethodTests {
String post(@ModelAttribute("object1") Object object1); String post(@ModelAttribute("object1") Object object1);
} }
@Controller
public static class MySessionAttributesControllerImpl implements MySessionAttributesControllerIfc { public static class MySessionAttributesControllerImpl implements MySessionAttributesControllerIfc {
public String get(Model model) { public String get(Model model) {

View File

@ -542,6 +542,7 @@ public class UriTemplateServletHandlerMethodTests {
} }
@Controller
@RequestMapping("/*/menu/**") @RequestMapping("/*/menu/**")
public static class MenuTreeController { public static class MenuTreeController {