Skip to content

Commit d7efc0d

Browse files
committed
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
1 parent f61f4a9 commit d7efc0d

File tree

5 files changed

+45
-32
lines changed

5 files changed

+45
-32
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
import javax.servlet.http.HttpServletRequest;
3131

3232
import org.springframework.beans.factory.BeanFactoryUtils;
33+
import org.springframework.beans.factory.InitializingBean;
3334
import org.springframework.context.ApplicationContextException;
3435
import org.springframework.util.ClassUtils;
3536
import org.springframework.util.LinkedMultiValueMap;
@@ -42,18 +43,18 @@
4243
/**
4344
* Abstract base class for {@link HandlerMapping} implementations that define a
4445
* mapping between a request and a {@link HandlerMethod}.
45-
*
46-
* <p>For each registered handler method, a unique mapping is maintained with
47-
* subclasses defining the details of the mapping type {@code <T>}.
48-
*
46+
*
47+
* <p>For each registered handler method, a unique mapping is maintained with
48+
* subclasses defining the details of the mapping type {@code <T>}.
49+
*
4950
* @param <T> The mapping for a {@link HandlerMethod} containing the conditions
50-
* needed to match the handler method to incoming request.
51-
*
51+
* needed to match the handler method to incoming request.
52+
*
5253
* @author Arjen Poutsma
5354
* @author Rossen Stoyanchev
5455
* @since 3.1
5556
*/
56-
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping {
57+
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
5758

5859
private boolean detectHandlerMethodsInAncestorContexts = false;
5960

@@ -72,7 +73,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
7273
public void setDetectHandlerMethodsInAncestorContexts(boolean detectHandlerMethodsInAncestorContexts) {
7374
this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts;
7475
}
75-
76+
7677
/**
7778
* Return a map with all handler methods and their mappings.
7879
*/
@@ -81,11 +82,17 @@ public Map<T, HandlerMethod> getHandlerMethods() {
8182
}
8283

8384
/**
84-
* ApplicationContext initialization and handler method detection.
85+
* ApplicationContext initialization.
8586
*/
8687
@Override
8788
public void initApplicationContext() throws ApplicationContextException {
8889
super.initApplicationContext();
90+
}
91+
92+
/**
93+
* Detects handler methods at initialization.
94+
*/
95+
public void afterPropertiesSet() {
8996
initHandlerMethods();
9097
}
9198

@@ -99,7 +106,7 @@ protected void initHandlerMethods() {
99106
if (logger.isDebugEnabled()) {
100107
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
101108
}
102-
109+
103110
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
104111
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
105112
getApplicationContext().getBeanNamesForType(Object.class));
@@ -131,25 +138,25 @@ protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
131138
* @param handler the bean name of a handler or a handler instance
132139
*/
133140
protected void detectHandlerMethods(final Object handler) {
134-
Class<?> handlerType = (handler instanceof String) ?
141+
Class<?> handlerType = (handler instanceof String) ?
135142
getApplicationContext().getType((String) handler) : handler.getClass();
136143

137144
final Class<?> userType = ClassUtils.getUserClass(handlerType);
138-
145+
139146
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
140147
public boolean matches(Method method) {
141148
return getMappingForMethod(method, userType) != null;
142149
}
143150
});
144-
151+
145152
for (Method method : methods) {
146153
T mapping = getMappingForMethod(method, userType);
147154
registerHandlerMethod(handler, method, mapping);
148155
}
149156
}
150157

151158
/**
152-
* Provide the mapping for a handler method. A method for which no
159+
* Provide the mapping for a handler method. A method for which no
153160
* mapping can be provided is not a handler method.
154161
*
155162
* @param method the method to provide a mapping for
@@ -161,11 +168,11 @@ public boolean matches(Method method) {
161168

162169
/**
163170
* Register a handler method and its unique mapping.
164-
*
171+
*
165172
* @param handler the bean name of the handler or the handler instance
166173
* @param method the method to register
167174
* @param mapping the mapping conditions associated with the handler method
168-
* @throws IllegalStateException if another method was already registered
175+
* @throws IllegalStateException if another method was already registered
169176
* under the same mapping
170177
*/
171178
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
@@ -177,19 +184,19 @@ protected void registerHandlerMethod(Object handler, Method method, T mapping) {
177184
else {
178185
handlerMethod = new HandlerMethod(handler, method);
179186
}
180-
187+
181188
HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
182189
if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
183190
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean()
184191
+ "' bean method \n" + handlerMethod + "\nto " + mapping + ": There is already '"
185192
+ oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
186193
}
187-
194+
188195
handlerMethods.put(mapping, handlerMethod);
189196
if (logger.isInfoEnabled()) {
190197
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
191198
}
192-
199+
193200
Set<String> patterns = getMappingPathPatterns(mapping);
194201
for (String pattern : patterns) {
195202
if (!getPathMatcher().isPattern(pattern)) {
@@ -199,7 +206,7 @@ protected void registerHandlerMethod(Object handler, Method method, T mapping) {
199206
}
200207

201208
/**
202-
* Extract and return the URL paths contained in a mapping.
209+
* Extract and return the URL paths contained in a mapping.
203210
*/
204211
protected abstract Set<String> getMappingPathPatterns(T mapping);
205212

@@ -230,11 +237,11 @@ protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Ex
230237
/**
231238
* Look up the best-matching handler method for the current request.
232239
* If multiple matches are found, the best match is selected.
233-
*
240+
*
234241
* @param lookupPath mapping lookup path within the current servlet mapping
235242
* @param request the current request
236243
* @return the best-matching handler method, or {@code null} if no match
237-
*
244+
*
238245
* @see #handleMatch(Object, String, HttpServletRequest)
239246
* @see #handleNoMatch(Set, String, HttpServletRequest)
240247
*/
@@ -289,7 +296,7 @@ private void addMatchingMappings(Collection<T> mappings, List<Match> matches, Ht
289296
}
290297

291298
/**
292-
* Check if a mapping matches the current request and return a (potentially
299+
* Check if a mapping matches the current request and return a (potentially
293300
* new) mapping with conditions relevant to the current request.
294301
*
295302
* @param mapping the mapping to get a match for
@@ -308,7 +315,7 @@ private void addMatchingMappings(Collection<T> mappings, List<Match> matches, Ht
308315

309316
/**
310317
* Invoked when a matching mapping is found.
311-
* @param mapping the matching mapping
318+
* @param mapping the matching mapping
312319
* @param lookupPath mapping lookup path within the current servlet mapping
313320
* @param request the current request
314321
*/
@@ -360,5 +367,5 @@ public int compare(Match match1, Match match2) {
360367
return comparator.compare(match1.mapping, match2.mapping);
361368
}
362369
}
363-
370+
364371
}

spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public void requestMappingHandlerMapping() throws Exception {
8383
assertEquals(0, handlerMapping.getOrder());
8484

8585
handlerMapping.setApplicationContext(cxt);
86+
handlerMapping.afterPropertiesSet();
8687
HandlerExecutionChain chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/"));
8788
assertNotNull(chain.getInterceptors());
8889
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[0].getClass());
@@ -204,6 +205,7 @@ public void webMvcConfigurerExtensionHooks() throws Exception {
204205

205206
RequestMappingHandlerMapping rmHandlerMapping = webConfig.requestMappingHandlerMapping();
206207
rmHandlerMapping.setApplicationContext(appCxt);
208+
rmHandlerMapping.afterPropertiesSet();
207209
HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/"));
208210
assertNotNull(chain.getInterceptors());
209211
assertEquals(2, chain.getInterceptors().length);

spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -82,7 +82,7 @@ public void patternMatch() throws Exception {
8282
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo"));
8383
assertEquals(method1, result.getMethod());
8484
}
85-
85+
8686
@Test(expected = IllegalStateException.class)
8787
public void ambiguousMatch() throws Exception {
8888
mapping.registerHandlerMethod(handler, method1, "/f?o");
@@ -95,24 +95,26 @@ public void ambiguousMatch() throws Exception {
9595
public void testDetectHandlerMethodsInAncestorContexts() {
9696
StaticApplicationContext cxt = new StaticApplicationContext();
9797
cxt.registerSingleton("myHandler", MyHandler.class);
98-
98+
9999
AbstractHandlerMethodMapping<String> mapping1 = new MyHandlerMethodMapping();
100100
mapping1.setApplicationContext(new StaticApplicationContext(cxt));
101+
mapping1.afterPropertiesSet();
101102

102103
assertEquals(0, mapping1.getHandlerMethods().size());
103104

104105
AbstractHandlerMethodMapping<String> mapping2 = new MyHandlerMethodMapping();
105106
mapping2.setDetectHandlerMethodsInAncestorContexts(true);
106107
mapping2.setApplicationContext(new StaticApplicationContext(cxt));
108+
mapping2.afterPropertiesSet();
107109

108110
assertEquals(2, mapping2.getHandlerMethods().size());
109111
}
110112

111-
113+
112114
private static class MyHandlerMethodMapping extends AbstractHandlerMethodMapping<String> {
113115

114116
private UrlPathHelper pathHelper = new UrlPathHelper();
115-
117+
116118
private PathMatcher pathMatcher = new AntPathMatcher();
117119

118120
@Override

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -103,6 +103,7 @@ public HandlerMethodAnnotationDetectionTests(Class<?> controllerType, boolean us
103103
context.refresh();
104104

105105
handlerMapping.setApplicationContext(context);
106+
handlerMapping.afterPropertiesSet();
106107
handlerAdapter.afterPropertiesSet();
107108
exceptionResolver.afterPropertiesSet();
108109
}

src/dist/changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Changes in version 3.2 M1
1616
* add Jackson 2 HttpMessageConverter and View types
1717
* add pretty print option to Jackson HttpMessageConverter and View types
1818
* fix issue with resolving Errors controller method argument
19+
* detect controller methods via InitializingBean in RequestMappingHandlerMapping
1920

2021
Changes in version 3.1.1 (2012-02-16)
2122
-------------------------------------

0 commit comments

Comments
 (0)