Skip to content

Commit 2fa0e63

Browse files
committed
Discover controllers based on type @RequestMapping
This was supported in DefaultAnnotationHandlerMapping but not in the RequestMappingHandlerMapping. The specific scenario where this matters is a controller decorated with a JDK proxy. In this scenario the HandlerMapping looks at interfaces only to decide if the bean is a controller. The @controller annotation is better left (and required) on the class. Issue: SPR-9374 Backport-Issue: SPR-9384 Backport-Commit: f61f4a9
1 parent 83ac44d commit 2fa0e63

File tree

2 files changed

+70
-69
lines changed

2 files changed

+70
-69
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java

Lines changed: 19 additions & 18 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.
@@ -32,8 +32,8 @@
3232
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
3333

3434
/**
35-
* Creates {@link RequestMappingInfo} instances from type and method-level
36-
* {@link RequestMapping @RequestMapping} annotations in
35+
* Creates {@link RequestMappingInfo} instances from type and method-level
36+
* {@link RequestMapping @RequestMapping} annotations in
3737
* {@link Controller @Controller} classes.
3838
*
3939
* @author Arjen Poutsma
@@ -45,16 +45,16 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
4545
private boolean useSuffixPatternMatch = true;
4646

4747
private boolean useTrailingSlashMatch = true;
48-
48+
4949
/**
5050
* Whether to use suffix pattern match (".*") when matching patterns to
5151
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
52-
* <p>The default value is {@code true}.
52+
* <p>The default value is {@code true}.
5353
*/
5454
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
5555
this.useSuffixPatternMatch = useSuffixPatternMatch;
5656
}
57-
57+
5858
/**
5959
* Whether to match to URLs irrespective of the presence of a trailing slash.
6060
* If enabled a method mapped to "/users" also matches to "/users/".
@@ -78,21 +78,22 @@ public boolean useTrailingSlashMatch() {
7878
}
7979

8080
/**
81-
* {@inheritDoc}
81+
* {@inheritDoc}
8282
* Expects a handler to have a type-level @{@link Controller} annotation.
8383
*/
8484
@Override
8585
protected boolean isHandler(Class<?> beanType) {
86-
return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
86+
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
87+
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
8788
}
8889

8990
/**
9091
* Uses method and type-level @{@link RequestMapping} annotations to create
9192
* the RequestMappingInfo.
92-
*
93+
*
9394
* @return the created RequestMappingInfo, or {@code null} if the method
9495
* does not have a {@code @RequestMapping} annotation.
95-
*
96+
*
9697
* @see #getCustomMethodCondition(Method)
9798
* @see #getCustomTypeCondition(Class)
9899
*/
@@ -114,22 +115,22 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handler
114115

115116
/**
116117
* Provide a custom method-level request condition.
117-
* The custom {@link RequestCondition} can be of any type so long as the
118+
* The custom {@link RequestCondition} can be of any type so long as the
118119
* same condition type is returned from all calls to this method in order
119-
* to ensure custom request conditions can be combined and compared.
120+
* to ensure custom request conditions can be combined and compared.
120121
* @param method the handler method for which to create the condition
121122
* @return the condition, or {@code null}
122123
*/
123124
protected RequestCondition<?> getCustomMethodCondition(Method method) {
124125
return null;
125126
}
126-
127+
127128
/**
128129
* Provide a custom type-level request condition.
129-
* The custom {@link RequestCondition} can be of any type so long as the
130+
* The custom {@link RequestCondition} can be of any type so long as the
130131
* same condition type is returned from all calls to this method in order
131-
* to ensure custom request conditions can be combined and compared.
132-
* @param method the handler method for which to create the condition
132+
* to ensure custom request conditions can be combined and compared.
133+
* @param handlerType the handler type for which to create the condition
133134
* @return the condition, or {@code null}
134135
*/
135136
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
@@ -141,13 +142,13 @@ protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
141142
*/
142143
private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
143144
return new RequestMappingInfo(
144-
new PatternsRequestCondition(annotation.value(),
145+
new PatternsRequestCondition(annotation.value(),
145146
getUrlPathHelper(), getPathMatcher(), this.useSuffixPatternMatch, this.useTrailingSlashMatch),
146147
new RequestMethodsRequestCondition(annotation.method()),
147148
new ParamsRequestCondition(annotation.params()),
148149
new HeadersRequestCondition(annotation.headers()),
149150
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
150-
new ProducesRequestCondition(annotation.produces(), annotation.headers()),
151+
new ProducesRequestCondition(annotation.produces(), annotation.headers()),
151152
customCondition);
152153
}
153154

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

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -51,37 +51,37 @@
5151
import org.springframework.web.servlet.ModelAndView;
5252

5353
/**
54-
* Test various scenarios for detecting method-level and method parameter annotations depending
55-
* on where they are located -- on interfaces, parent classes, in parameterized methods, or in
54+
* Test various scenarios for detecting method-level and method parameter annotations depending
55+
* on where they are located -- on interfaces, parent classes, in parameterized methods, or in
5656
* combination with proxies.
57-
*
57+
*
5858
* @author Rossen Stoyanchev
5959
*/
6060
@RunWith(Parameterized.class)
6161
public class HandlerMethodAnnotationDetectionTests {
62-
62+
6363
@Parameters
6464
public static Collection<Object[]> handlerTypes() {
6565
Object[][] array = new Object[12][2];
6666

6767
array[0] = new Object[] { SimpleController.class, true}; // CGLib proxy
6868
array[1] = new Object[] { SimpleController.class, false};
69-
69+
7070
array[2] = new Object[] { AbstractClassController.class, true }; // CGLib proxy
7171
array[3] = new Object[] { AbstractClassController.class, false };
72-
73-
array[4] = new Object[] { ParameterizedAbstractClassController.class, false}; // CGLib proxy
74-
array[5] = new Object[] { ParameterizedAbstractClassController.class, false};
75-
72+
73+
array[4] = new Object[] { ParameterizedAbstractClassController.class, false}; // CGLib proxy
74+
array[5] = new Object[] { ParameterizedAbstractClassController.class, false};
75+
7676
array[6] = new Object[] { InterfaceController.class, true }; // JDK dynamic proxy
77-
array[7] = new Object[] { InterfaceController.class, false };
78-
79-
array[8] = new Object[] { ParameterizedInterfaceController.class, false}; // no AOP
80-
array[9] = new Object[] { ParameterizedInterfaceController.class, false};
81-
77+
array[7] = new Object[] { InterfaceController.class, false };
78+
79+
array[8] = new Object[] { ParameterizedInterfaceController.class, false}; // no AOP
80+
array[9] = new Object[] { ParameterizedInterfaceController.class, false};
81+
8282
array[10] = new Object[] { SupportClassController.class, true}; // CGLib proxy
8383
array[11] = new Object[] { SupportClassController.class, false};
84-
84+
8585
return Arrays.asList(array);
8686
}
8787

@@ -101,7 +101,7 @@ public HandlerMethodAnnotationDetectionTests(Class<?> controllerType, boolean us
101101
context.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
102102
}
103103
context.refresh();
104-
104+
105105
handlerMapping.setApplicationContext(context);
106106
handlerAdapter.afterPropertiesSet();
107107
exceptionResolver.afterPropertiesSet();
@@ -113,12 +113,12 @@ public void testRequestMappingMethod() throws Exception {
113113
SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
114114
String dateA = "11:01:2011";
115115
String dateB = "11:02:2011";
116-
116+
117117
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path1/path2");
118118
request.setParameter("datePattern", datePattern);
119119
request.addHeader("header1", dateA);
120120
request.addHeader("header2", dateB);
121-
121+
122122
HandlerExecutionChain chain = handlerMapping.getHandler(request);
123123
assertNotNull(chain);
124124

@@ -133,7 +133,7 @@ public void testRequestMappingMethod() throws Exception {
133133
assertEquals("failure", response.getContentAsString());
134134
}
135135

136-
136+
137137
/**
138138
* SIMPLE CASE
139139
*/
@@ -156,15 +156,15 @@ public void initModel(@RequestHeader("header1") Date date, Model model) {
156156
public Date handle(@RequestHeader("header2") Date date) throws Exception {
157157
return date;
158158
}
159-
159+
160160
@ExceptionHandler(Exception.class)
161161
@ResponseBody
162162
public String handleException(Exception exception) {
163163
return exception.getMessage();
164164
}
165-
}
165+
}
166+
166167

167-
168168
@Controller
169169
static abstract class MappingAbstractClass {
170170

@@ -177,15 +177,15 @@ static abstract class MappingAbstractClass {
177177
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
178178
@ModelAttribute("attr2")
179179
public abstract Date handle(Date date, Model model) throws Exception;
180-
180+
181181
@ExceptionHandler(Exception.class)
182182
@ResponseBody
183183
public abstract String handleException(Exception exception);
184-
}
184+
}
185185

186186
/**
187187
* CONTROLLER WITH ABSTRACT CLASS
188-
*
188+
*
189189
* <p>All annotations can be on methods in the abstract class except parameter annotations.
190190
*/
191191
static class AbstractClassController extends MappingAbstractClass {
@@ -202,14 +202,15 @@ public void initModel(@RequestHeader("header1") Date date, Model model) {
202202
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
203203
return date;
204204
}
205-
205+
206206
public String handleException(Exception exception) {
207207
return exception.getMessage();
208208
}
209209
}
210-
211-
212-
@Controller
210+
211+
// SPR-9374
212+
213+
@RequestMapping
213214
static interface MappingInterface {
214215

215216
@InitBinder
@@ -221,15 +222,15 @@ static interface MappingInterface {
221222
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
222223
@ModelAttribute("attr2")
223224
Date handle(@RequestHeader("header2") Date date, Model model) throws Exception;
224-
225+
225226
@ExceptionHandler(Exception.class)
226227
@ResponseBody
227228
String handleException(Exception exception);
228-
}
229+
}
229230

230231
/**
231232
* CONTROLLER WITH INTERFACE
232-
*
233+
*
233234
* No AOP:
234235
* All annotations can be on interface methods except parameter annotations.
235236
*
@@ -250,7 +251,7 @@ public void initModel(@RequestHeader("header1") Date date, Model model) {
250251
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
251252
return date;
252253
}
253-
254+
254255
public String handleException(Exception exception) {
255256
return exception.getMessage();
256257
}
@@ -269,15 +270,15 @@ static abstract class MappingParameterizedAbstractClass<A, B, C> {
269270
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
270271
@ModelAttribute("attr2")
271272
public abstract Date handle(C date, Model model) throws Exception;
272-
273+
273274
@ExceptionHandler(Exception.class)
274275
@ResponseBody
275276
public abstract String handleException(Exception exception);
276-
}
277+
}
277278

278279
/**
279280
* CONTROLLER WITH PARAMETERIZED BASE CLASS
280-
*
281+
*
281282
* <p>All annotations can be on methods in the abstract class except parameter annotations.
282283
*/
283284
static class ParameterizedAbstractClassController extends MappingParameterizedAbstractClass<String, Date, Date> {
@@ -294,14 +295,13 @@ public void initModel(@RequestHeader("header1") Date date, Model model) {
294295
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
295296
return date;
296297
}
297-
298+
298299
public String handleException(Exception exception) {
299300
return exception.getMessage();
300301
}
301302
}
302303

303-
304-
@Controller
304+
@RequestMapping
305305
static interface MappingParameterizedInterface<A, B, C> {
306306

307307
@InitBinder
@@ -313,17 +313,17 @@ static interface MappingParameterizedInterface<A, B, C> {
313313
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
314314
@ModelAttribute("attr2")
315315
Date handle(C date, Model model) throws Exception;
316-
316+
317317
@ExceptionHandler(Exception.class)
318318
@ResponseBody
319319
String handleException(Exception exception);
320-
}
320+
}
321321

322322
/**
323323
* CONTROLLER WITH PARAMETERIZED INTERFACE
324-
*
324+
*
325325
* <p>All annotations can be on interface except parameter annotations.
326-
*
326+
*
327327
* <p>Cannot be used as JDK dynamic proxy since parameterized interface does not contain type information.
328328
*/
329329
static class ParameterizedInterfaceController implements MappingParameterizedInterface<String, Date, Date> {
@@ -344,18 +344,18 @@ public void initModel(@RequestHeader("header1") Date date, Model model) {
344344
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
345345
return date;
346346
}
347-
347+
348348
@ExceptionHandler(Exception.class)
349349
@ResponseBody
350350
public String handleException(Exception exception) {
351351
return exception.getMessage();
352352
}
353-
}
354-
355-
353+
}
354+
355+
356356
/**
357357
* SPR-8248
358-
*
358+
*
359359
* <p>Support class contains all annotations. Subclass has type-level @{@link RequestMapping}.
360360
*/
361361
@Controller
@@ -377,17 +377,17 @@ public void initModel(@RequestHeader("header1") Date date, Model model) {
377377
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
378378
return date;
379379
}
380-
380+
381381
@ExceptionHandler(Exception.class)
382382
@ResponseBody
383383
public String handleException(Exception exception) {
384384
return exception.getMessage();
385385
}
386-
}
386+
}
387387

388388
@Controller
389389
@RequestMapping("/path1")
390390
static class SupportClassController extends MappingSupportClass {
391-
}
391+
}
392392

393393
}

0 commit comments

Comments
 (0)