From d7efc0db80b52ce645d132d117bf64c17e5deaf0 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 10 May 2012 16:42:36 -0400 Subject: [PATCH] Detect controller methods via InitializingBean hook Previously RequestMappingHandlerMapping detected @RequestMapping methods through an initApplicationContext() hook. However, the HandlerMapping may not have been fully set up with all its dependencies at that point including settings like useSuffixPattern and others. This change moves the detection @RequestMapping methods to an InitializingBean.afterPropertiesSet() hook. Issue: SPR-9371 --- .../handler/AbstractHandlerMethodMapping.java | 59 +++++++++++-------- .../WebMvcConfigurationSupportTests.java | 2 + .../handler/HandlerMethodMappingTests.java | 12 ++-- ...HandlerMethodAnnotationDetectionTests.java | 3 +- src/dist/changelog.txt | 1 + 5 files changed, 45 insertions(+), 32 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index be8fd6d6107..ba8348b6752 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -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. @@ -30,6 +30,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContextException; import org.springframework.util.ClassUtils; import org.springframework.util.LinkedMultiValueMap; @@ -42,18 +43,18 @@ import org.springframework.web.servlet.HandlerMapping; /** * Abstract base class for {@link HandlerMapping} implementations that define a * mapping between a request and a {@link HandlerMethod}. - * - *

For each registered handler method, a unique mapping is maintained with - * subclasses defining the details of the mapping type {@code }. - * + * + *

For each registered handler method, a unique mapping is maintained with + * subclasses defining the details of the mapping type {@code }. + * * @param The mapping for a {@link HandlerMethod} containing the conditions - * needed to match the handler method to incoming request. - * + * needed to match the handler method to incoming request. + * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 */ -public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMapping { +public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMapping implements InitializingBean { private boolean detectHandlerMethodsInAncestorContexts = false; @@ -72,7 +73,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap public void setDetectHandlerMethodsInAncestorContexts(boolean detectHandlerMethodsInAncestorContexts) { this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts; } - + /** * Return a map with all handler methods and their mappings. */ @@ -81,11 +82,17 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * ApplicationContext initialization and handler method detection. + * ApplicationContext initialization. */ @Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); + } + + /** + * Detects handler methods at initialization. + */ + public void afterPropertiesSet() { initHandlerMethods(); } @@ -99,7 +106,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } - + String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); @@ -131,17 +138,17 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap * @param handler the bean name of a handler or a handler instance */ protected void detectHandlerMethods(final Object handler) { - Class handlerType = (handler instanceof String) ? + Class handlerType = (handler instanceof String) ? getApplicationContext().getType((String) handler) : handler.getClass(); final Class userType = ClassUtils.getUserClass(handlerType); - + Set methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { public boolean matches(Method method) { return getMappingForMethod(method, userType) != null; } }); - + for (Method method : methods) { T mapping = getMappingForMethod(method, userType); registerHandlerMethod(handler, method, mapping); @@ -149,7 +156,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Provide the mapping for a handler method. A method for which no + * Provide the mapping for a handler method. A method for which no * mapping can be provided is not a handler method. * * @param method the method to provide a mapping for @@ -161,11 +168,11 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap /** * Register a handler method and its unique mapping. - * + * * @param handler the bean name of the handler or the 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 registered + * @throws IllegalStateException if another method was already registered * under the same mapping */ protected void registerHandlerMethod(Object handler, Method method, T mapping) { @@ -177,19 +184,19 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap 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 '" + oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped."); } - + handlerMethods.put(mapping, handlerMethod); if (logger.isInfoEnabled()) { logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod); } - + Set patterns = getMappingPathPatterns(mapping); for (String pattern : patterns) { if (!getPathMatcher().isPattern(pattern)) { @@ -199,7 +206,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Extract and return the URL paths contained in a mapping. + * Extract and return the URL paths contained in a mapping. */ protected abstract Set getMappingPathPatterns(T mapping); @@ -230,11 +237,11 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap /** * Look up the best-matching handler method for the current request. * If multiple matches are found, the best match is selected. - * + * * @param lookupPath mapping lookup path within the current servlet mapping * @param request the current request * @return the best-matching handler method, or {@code null} if no match - * + * * @see #handleMatch(Object, String, HttpServletRequest) * @see #handleNoMatch(Set, String, HttpServletRequest) */ @@ -289,7 +296,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Check if a mapping matches the current request and return a (potentially + * Check if a mapping matches the current request and return a (potentially * new) mapping with conditions relevant to the current request. * * @param mapping the mapping to get a match for @@ -308,7 +315,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap /** * Invoked when a matching mapping is found. - * @param mapping the matching mapping + * @param mapping the matching mapping * @param lookupPath mapping lookup path within the current servlet mapping * @param request the current request */ @@ -360,5 +367,5 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap return comparator.compare(match1.mapping, match2.mapping); } } - + } \ No newline at end of file diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index 8dc615c4af6..8d1c8c8112a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -83,6 +83,7 @@ public class WebMvcConfigurationSupportTests { assertEquals(0, handlerMapping.getOrder()); handlerMapping.setApplicationContext(cxt); + handlerMapping.afterPropertiesSet(); HandlerExecutionChain chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/")); assertNotNull(chain.getInterceptors()); assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[0].getClass()); @@ -204,6 +205,7 @@ public class WebMvcConfigurationSupportTests { RequestMappingHandlerMapping rmHandlerMapping = webConfig.requestMappingHandlerMapping(); rmHandlerMapping.setApplicationContext(appCxt); + rmHandlerMapping.afterPropertiesSet(); HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/")); assertNotNull(chain.getInterceptors()); assertEquals(2, chain.getInterceptors().length); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java index 755eb6a62d0..7524a63dd79 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java @@ -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. @@ -82,7 +82,7 @@ public class HandlerMethodMappingTests { HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo")); assertEquals(method1, result.getMethod()); } - + @Test(expected = IllegalStateException.class) public void ambiguousMatch() throws Exception { mapping.registerHandlerMethod(handler, method1, "/f?o"); @@ -95,24 +95,26 @@ public class HandlerMethodMappingTests { public void testDetectHandlerMethodsInAncestorContexts() { StaticApplicationContext cxt = new StaticApplicationContext(); cxt.registerSingleton("myHandler", MyHandler.class); - + AbstractHandlerMethodMapping mapping1 = new MyHandlerMethodMapping(); mapping1.setApplicationContext(new StaticApplicationContext(cxt)); + mapping1.afterPropertiesSet(); assertEquals(0, mapping1.getHandlerMethods().size()); AbstractHandlerMethodMapping mapping2 = new MyHandlerMethodMapping(); mapping2.setDetectHandlerMethodsInAncestorContexts(true); mapping2.setApplicationContext(new StaticApplicationContext(cxt)); + mapping2.afterPropertiesSet(); assertEquals(2, mapping2.getHandlerMethods().size()); } - + private static class MyHandlerMethodMapping extends AbstractHandlerMethodMapping { private UrlPathHelper pathHelper = new UrlPathHelper(); - + private PathMatcher pathMatcher = new AntPathMatcher(); @Override diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java index 3816f4b5de8..cedaf44cfac 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java @@ -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. @@ -103,6 +103,7 @@ public class HandlerMethodAnnotationDetectionTests { context.refresh(); handlerMapping.setApplicationContext(context); + handlerMapping.afterPropertiesSet(); handlerAdapter.afterPropertiesSet(); exceptionResolver.afterPropertiesSet(); } diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index 1c94b659184..62668e91f37 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -16,6 +16,7 @@ Changes in version 3.2 M1 * add Jackson 2 HttpMessageConverter and View types * add pretty print option to Jackson HttpMessageConverter and View types * fix issue with resolving Errors controller method argument +* detect controller methods via InitializingBean in RequestMappingHandlerMapping Changes in version 3.1.1 (2012-02-16) -------------------------------------