Skip to content

Commit 82a2602

Browse files
committed
Allow @responsebody on the type level
This change enables having @responsebody on the type-level in which case it inherited and does not need to be added on the method level. For added convenience, there is also a new @RestController annotation, a meta-annotation in turn annotated with @controller and @responsebody. Classes with the new annotation do not need to have @responsebody declared on the method level as it is inherited. Issue: SPR-10814
1 parent 172a0b9 commit 82a2602

File tree

5 files changed

+135
-21
lines changed

5 files changed

+135
-21
lines changed

spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseBody.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2013 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.
@@ -23,15 +23,18 @@
2323
import java.lang.annotation.Target;
2424

2525
/**
26-
* Annotation which indicates that a method return value should be bound to the web response body.
27-
* Supported for annotated handler methods in Servlet environments.
26+
* Annotation that indicates a method return value should be bound to the web response
27+
* body. Supported for annotated handler methods in Servlet environments.
28+
* <p>
29+
* As of version 4.0 this annotation can also be added on the type level in which case
30+
* is inherited and does not need to be added on the method level.
2831
*
2932
* @author Arjen Poutsma
3033
* @since 3.0
3134
* @see RequestBody
32-
* @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
35+
* @see RestController
3336
*/
34-
@Target(ElementType.METHOD)
37+
@Target({ElementType.TYPE, ElementType.METHOD})
3538
@Retention(RetentionPolicy.RUNTIME)
3639
@Documented
3740
public @interface ResponseBody {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.bind.annotation;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.stereotype.Controller;
26+
27+
/**
28+
* A convenience annotation that is itself annotated with {@link Controller @Controller}
29+
* and {@link ResponseBody @ResponseBody}.
30+
* <p>
31+
* Types that carry this annotation are treated as
32+
* controllers where {@link RequestMapping @RequestMapping} methods assume
33+
* {@link ResponseBody @ResponseBody} semantics by default.
34+
*
35+
* @author Rossen Stoyanchev
36+
* @since 4.0
37+
*/
38+
@Target(ElementType.TYPE)
39+
@Retention(RetentionPolicy.RUNTIME)
40+
@Documented
41+
@Controller
42+
@ResponseBody
43+
public @interface RestController {
44+
45+
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ public boolean supportsParameter(MethodParameter parameter) {
8181

8282
@Override
8383
public boolean supportsReturnType(MethodParameter returnType) {
84-
return returnType.getMethodAnnotation(ResponseBody.class) != null;
84+
return ((AnnotationUtils.findAnnotation(returnType.getDeclaringClass(), ResponseBody.class) != null)
85+
|| (returnType.getMethodAnnotation(ResponseBody.class) != null));
8586
}
8687

8788
/**

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
package org.springframework.web.servlet.mvc.method.annotation;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertNotNull;
21-
2219
import java.io.Serializable;
2320
import java.lang.reflect.Method;
2421
import java.util.ArrayList;
@@ -39,12 +36,17 @@
3936
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
4037
import org.springframework.web.bind.WebDataBinder;
4138
import org.springframework.web.bind.annotation.RequestBody;
39+
import org.springframework.web.bind.annotation.RequestMapping;
40+
import org.springframework.web.bind.annotation.ResponseBody;
41+
import org.springframework.web.bind.annotation.RestController;
4242
import org.springframework.web.bind.support.WebDataBinderFactory;
4343
import org.springframework.web.context.request.NativeWebRequest;
4444
import org.springframework.web.context.request.ServletWebRequest;
4545
import org.springframework.web.method.HandlerMethod;
4646
import org.springframework.web.method.support.ModelAndViewContainer;
4747

48+
import static org.junit.Assert.*;
49+
4850
/**
4951
* Test fixture for a {@link RequestResponseBodyMethodProcessor} with actual delegation
5052
* to HttpMessageConverter instances.
@@ -231,6 +233,34 @@ public void handleReturnValueStringAcceptCharset() throws Exception {
231233
assertEquals("text/plain;charset=UTF-8", servletResponse.getHeader("Content-Type"));
232234
}
233235

236+
@Test
237+
public void supportsReturnTypeResponseBodyOnType() throws Exception {
238+
239+
Method method = ResponseBodyController.class.getMethod("handle");
240+
MethodParameter returnType = new MethodParameter(method, -1);
241+
242+
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
243+
converters.add(new StringHttpMessageConverter());
244+
245+
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
246+
247+
assertTrue("Failed to recognize type-level @ResponseBody", processor.supportsReturnType(returnType));
248+
}
249+
250+
@Test
251+
public void supportsReturnTypeRestController() throws Exception {
252+
253+
Method method = TestRestController.class.getMethod("handle");
254+
MethodParameter returnType = new MethodParameter(method, -1);
255+
256+
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
257+
converters.add(new StringHttpMessageConverter());
258+
259+
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
260+
261+
assertTrue("Failed to recognize type-level @RestController", processor.supportsReturnType(returnType));
262+
}
263+
234264

235265
public String handle(
236266
@RequestBody List<SimpleBean> list,
@@ -289,4 +319,22 @@ public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, St
289319
}
290320
}
291321

322+
@ResponseBody
323+
private static class ResponseBodyController {
324+
325+
@RequestMapping
326+
public String handle() {
327+
return "hello";
328+
}
329+
}
330+
331+
@RestController
332+
private static class TestRestController {
333+
334+
@RequestMapping
335+
public String handle() {
336+
return "hello";
337+
}
338+
}
339+
292340
}

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

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@
1616

1717
package org.springframework.web.servlet.mvc.method.annotation;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertFalse;
21-
import static org.junit.Assert.assertNotNull;
22-
import static org.junit.Assert.assertNull;
23-
import static org.junit.Assert.assertSame;
24-
import static org.junit.Assert.assertTrue;
25-
import static org.junit.Assert.fail;
26-
2719
import java.beans.PropertyEditorSupport;
2820
import java.io.IOException;
2921
import java.io.Serializable;
@@ -64,10 +56,6 @@
6456
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
6557
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
6658
import org.springframework.aop.support.DefaultPointcutAdvisor;
67-
import org.springframework.tests.sample.beans.DerivedTestBean;
68-
import org.springframework.tests.sample.beans.GenericBean;
69-
import org.springframework.tests.sample.beans.ITestBean;
70-
import org.springframework.tests.sample.beans.TestBean;
7159
import org.springframework.beans.factory.BeanCreationException;
7260
import org.springframework.beans.factory.annotation.Autowired;
7361
import org.springframework.beans.factory.annotation.Value;
@@ -101,6 +89,10 @@
10189
import org.springframework.mock.web.test.MockServletContext;
10290
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
10391
import org.springframework.stereotype.Controller;
92+
import org.springframework.tests.sample.beans.DerivedTestBean;
93+
import org.springframework.tests.sample.beans.GenericBean;
94+
import org.springframework.tests.sample.beans.ITestBean;
95+
import org.springframework.tests.sample.beans.TestBean;
10496
import org.springframework.ui.ExtendedModelMap;
10597
import org.springframework.ui.Model;
10698
import org.springframework.ui.ModelMap;
@@ -123,6 +115,7 @@
123115
import org.springframework.web.bind.annotation.RequestParam;
124116
import org.springframework.web.bind.annotation.ResponseBody;
125117
import org.springframework.web.bind.annotation.ResponseStatus;
118+
import org.springframework.web.bind.annotation.RestController;
126119
import org.springframework.web.bind.annotation.SessionAttributes;
127120
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
128121
import org.springframework.web.bind.support.WebArgumentResolver;
@@ -142,6 +135,8 @@
142135
import org.springframework.web.servlet.support.RequestContextUtils;
143136
import org.springframework.web.servlet.view.InternalResourceViewResolver;
144137

138+
import static org.junit.Assert.*;
139+
145140
/**
146141
* The origin of this test class is {@link ServletAnnotationControllerHandlerMethodTests}.
147142
*
@@ -1569,6 +1564,18 @@ public void initialize(GenericWebApplicationContext context) {
15691564
assertEquals("count:3", response.getContentAsString());
15701565
}
15711566

1567+
@Test
1568+
public void restController() throws Exception {
1569+
1570+
initServletWithControllers(ThisWillActuallyRun.class);
1571+
1572+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
1573+
MockHttpServletResponse response = new MockHttpServletResponse();
1574+
getServlet().service(request, response);
1575+
assertEquals("Hello World!", response.getContentAsString());
1576+
}
1577+
1578+
15721579
/*
15731580
* Controllers
15741581
*/
@@ -2979,6 +2986,16 @@ public void message(int param, Writer writer) throws IOException {
29792986
}
29802987
}
29812988

2989+
@RestController
2990+
static class ThisWillActuallyRun {
2991+
2992+
@RequestMapping(value = "/", method = RequestMethod.GET)
2993+
public String home() {
2994+
return "Hello World!";
2995+
}
2996+
}
2997+
2998+
29822999
// Test cases deleted from the original SevletAnnotationControllerTests:
29833000

29843001
// @Ignore("Controller interface => no method-level @RequestMapping annotation")

0 commit comments

Comments
 (0)