Skip to content

Commit b5e8269

Browse files
committed
fixed interaction with ControllerClassNameHandlerMapping (as reported by Rossen)
1 parent 5e10658 commit b5e8269

File tree

1 file changed

+72
-45
lines changed

1 file changed

+72
-45
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java

Lines changed: 72 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 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.
@@ -37,57 +37,71 @@
3737
import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping;
3838

3939
/**
40-
* Implementation of the {@link org.springframework.web.servlet.HandlerMapping} interface that maps handlers based on
41-
* HTTP paths expressed through the {@link RequestMapping} annotation at the type or method level.
40+
* Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
41+
* interface that maps handlers based on HTTP paths expressed through the
42+
* {@link RequestMapping} annotation at the type or method level.
4243
*
43-
* <p>Registered by default in {@link org.springframework.web.servlet.DispatcherServlet} on Java 5+. <b>NOTE:</b> If you
44-
* define custom HandlerMapping beans in your DispatcherServlet context, you need to add a
45-
* DefaultAnnotationHandlerMapping bean explicitly, since custom HandlerMapping beans replace the default mapping
46-
* strategies. Defining a DefaultAnnotationHandlerMapping also allows for registering custom interceptors:
44+
* <p>Registered by default in {@link org.springframework.web.servlet.DispatcherServlet}
45+
* on Java 5+. <b>NOTE:</b> If you define custom HandlerMapping beans in your
46+
* DispatcherServlet context, you need to add a DefaultAnnotationHandlerMapping bean
47+
* explicitly, since custom HandlerMapping beans replace the default mapping strategies.
48+
* Defining a DefaultAnnotationHandlerMapping also allows for registering custom
49+
* interceptors:
4750
*
48-
* <pre class="code"> &lt;bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"&gt;
49-
* &lt;property name="interceptors"&gt; ... &lt;/property&gt; &lt;/bean&gt;</pre>
51+
* <pre class="code">
52+
* &lt;bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"&gt;
53+
* &lt;property name="interceptors"&gt;
54+
* ...
55+
* &lt;/property&gt;
56+
* &lt;/bean&gt;</pre>
5057
*
51-
* Annotated controllers are usually marked with the {@link Controller} stereotype at the type level. This is not
52-
* strictly necessary when {@link RequestMapping} is applied at the type level (since such a handler usually implements
53-
* the {@link org.springframework.web.servlet.mvc.Controller} interface). However, {@link Controller} is required for
54-
* detecting {@link RequestMapping} annotations at the method level if {@link RequestMapping} is not present at the type
55-
* level.
58+
* Annotated controllers are usually marked with the {@link Controller} stereotype
59+
* at the type level. This is not strictly necessary when {@link RequestMapping} is
60+
* applied at the type level (since such a handler usually implements the
61+
* {@link org.springframework.web.servlet.mvc.Controller} interface). However,
62+
* {@link Controller} is required for detecting {@link RequestMapping} annotations
63+
* at the method level if {@link RequestMapping} is not present at the type level.
5664
*
57-
* <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping expressed at the class level (if any).
58-
* HTTP paths need to uniquely map onto specific handler beans, with any given HTTP path only allowed to be mapped onto
59-
* one specific handler bean (not spread across multiple handler beans). It is strongly recommended to co-locate related
60-
* handler methods into the same bean.
65+
* <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
66+
* expressed at the class level (if any). HTTP paths need to uniquely map onto
67+
* specific handler beans, with any given HTTP path only allowed to be mapped
68+
* onto one specific handler bean (not spread across multiple handler beans).
69+
* It is strongly recommended to co-locate related handler methods into the same bean.
6170
*
62-
* <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing annotated handler methods, as mapped by
63-
* this HandlerMapping. For {@link RequestMapping} at the type level, specific HandlerAdapters such as {@link
64-
* org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
71+
* <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing
72+
* annotated handler methods, as mapped by this HandlerMapping. For
73+
* {@link RequestMapping} at the type level, specific HandlerAdapters such as
74+
* {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
6575
*
6676
* @author Juergen Hoeller
6777
* @author Arjen Poutsma
78+
* @since 2.5
6879
* @see RequestMapping
6980
* @see AnnotationMethodHandlerAdapter
70-
* @since 2.5
7181
*/
7282
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping {
7383

7484
private boolean useDefaultSuffixPattern = true;
7585

7686
private final Map<Class, RequestMapping> cachedMappings = new HashMap<Class, RequestMapping>();
7787

88+
7889
/**
79-
* Set whether to register paths using the default suffix pattern as well: i.e. whether "/users" should be registered
80-
* as "/users.*" and "/users/" too. <p>Default is "true". Turn this convention off if you intend to interpret your
81-
* <code>@RequestMapping</code> paths strictly. <p>Note that paths which include a ".xxx" suffix or end with "/"
82-
* already will not be transformed using the default suffix pattern in any case.
90+
* Set whether to register paths using the default suffix pattern as well:
91+
* i.e. whether "/users" should be registered as "/users.*" and "/users/" too.
92+
* <p>Default is "true". Turn this convention off if you intend to interpret
93+
* your <code>@RequestMapping</code> paths strictly.
94+
* <p>Note that paths which include a ".xxx" suffix or end with "/" already will not be
95+
* transformed using the default suffix pattern in any case.
8396
*/
8497
public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) {
8598
this.useDefaultSuffixPattern = useDefaultSuffixPattern;
8699
}
87100

101+
88102
/**
89-
* Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping} annotation on the handler
90-
* class and on any of its methods.
103+
* Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping}
104+
* annotation on the handler class and on any of its methods.
91105
*/
92106
@Override
93107
protected String[] determineUrlsForHandler(String beanName) {
@@ -101,7 +115,7 @@ protected String[] determineUrlsForHandler(String beanName) {
101115
String[] typeLevelPatterns = mapping.value();
102116
if (typeLevelPatterns.length > 0) {
103117
// @RequestMapping specifies paths at type level
104-
String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType);
118+
String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true);
105119
for (String typeLevelPattern : typeLevelPatterns) {
106120
if (!typeLevelPattern.startsWith("/")) {
107121
typeLevelPattern = "/" + typeLevelPattern;
@@ -110,7 +124,8 @@ protected String[] determineUrlsForHandler(String beanName) {
110124
for (String methodLevelPattern : methodLevelPatterns) {
111125
if (methodLevelPattern == null) {
112126
hasEmptyMethodLevelMappings = true;
113-
} else {
127+
}
128+
else {
114129
String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
115130
addUrlsForPath(urls, combinedPattern);
116131
}
@@ -124,12 +139,12 @@ protected String[] determineUrlsForHandler(String beanName) {
124139
}
125140
else {
126141
// actual paths specified by @RequestMapping at method level
127-
return determineUrlsForHandlerMethods(handlerType);
142+
return determineUrlsForHandlerMethods(handlerType, false);
128143
}
129144
}
130145
else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
131146
// @RequestMapping to be introspected at method level
132-
return determineUrlsForHandlerMethods(handlerType);
147+
return determineUrlsForHandlerMethods(handlerType, false);
133148
}
134149
else {
135150
return null;
@@ -138,13 +153,17 @@ else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null)
138153

139154
/**
140155
* Derive URL mappings from the handler's method-level mappings.
141-
*
142-
* <p>The returned array may contain {@code null}, indicating an empty {@link RequestMapping} value.
143-
*
144156
* @param handlerType the handler type to introspect
157+
* @param indicateEmpty whether the returned array should contain
158+
* <code>null</code> in case of an empty {@link RequestMapping} value.
145159
* @return the array of mapped URLs
146160
*/
147-
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType) {
161+
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType, final boolean indicateEmpty) {
162+
String[] subclassResult = determineUrlsForHandlerMethods(handlerType);
163+
if (subclassResult != null) {
164+
return subclassResult;
165+
}
166+
148167
final Set<String> urls = new LinkedHashSet<String>();
149168
Class<?>[] handlerTypes =
150169
Proxy.isProxyClass(handlerType) ? handlerType.getInterfaces() : new Class<?>[]{handlerType};
@@ -158,7 +177,8 @@ public void doWith(Method method) {
158177
for (String mappedPattern : mappedPatterns) {
159178
addUrlsForPath(urls, mappedPattern);
160179
}
161-
} else {
180+
}
181+
else if (indicateEmpty) {
162182
// empty method-level RequestMapping
163183
urls.add(null);
164184
}
@@ -169,9 +189,17 @@ public void doWith(Method method) {
169189
return StringUtils.toStringArray(urls);
170190
}
171191

192+
/**
193+
* Derive URL mappings from the handler's method-level mappings.
194+
* @param handlerType the handler type to introspect
195+
* @return the array of mapped URLs
196+
*/
197+
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType) {
198+
return null;
199+
}
200+
172201
/**
173202
* Add URLs and/or URL patterns for the given path.
174-
*
175203
* @param urls the Set of URLs for the current bean
176204
* @param path the currently introspected path
177205
*/
@@ -183,9 +211,9 @@ protected void addUrlsForPath(Set<String> urls, String path) {
183211
}
184212
}
185213

214+
186215
/**
187216
* Validate the given annotated handler against the current request.
188-
*
189217
* @see #validateMapping
190218
*/
191219
@Override
@@ -200,9 +228,8 @@ protected void validateHandler(Object handler, HttpServletRequest request) throw
200228
}
201229

202230
/**
203-
* Validate the given type-level mapping metadata against the current request, checking HTTP request method and
204-
* parameter conditions.
205-
*
231+
* Validate the given type-level mapping metadata against the current request,
232+
* checking HTTP request method and parameter conditions.
206233
* @param mapping the mapping metadata to validate
207234
* @param request current HTTP request
208235
* @throws Exception if validation failed
@@ -224,9 +251,9 @@ protected void validateMapping(RequestMapping mapping, HttpServletRequest reques
224251

225252
String[] mappedHeaders = mapping.headers();
226253
if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) {
227-
throw new ServletRequestBindingException(
228-
"Header conditions \"" + StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
229-
"\" not met for actual request");
254+
throw new ServletRequestBindingException("Header conditions \"" +
255+
StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
256+
"\" not met for actual request");
230257
}
231258
}
232259

0 commit comments

Comments
 (0)