From 467b5f3f2875775781387385173e03821299195f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 26 Feb 2016 18:30:08 +0100 Subject: [PATCH] Introduce composed annotations for @RequestMapping This commit introduces the following common composed annotations for @RequestMapping in Spring MVC and Spring MVC REST. - @GetMapping - @PostMapping - @PutMapping - @DeleteMapping - @PatchMapping Issue: SPR-13992 --- .../AsyncControllerJavaConfigTests.java | 8 +- .../samples/context/PersonController.java | 15 ++-- .../web/bind/annotation/DeleteMapping.java | 72 +++++++++++++++ .../web/bind/annotation/GetMapping.java | 78 ++++++++++++++++ .../web/bind/annotation/PatchMapping.java | 78 ++++++++++++++++ .../web/bind/annotation/PostMapping.java | 78 ++++++++++++++++ .../web/bind/annotation/PutMapping.java | 78 ++++++++++++++++ .../web/bind/annotation/RequestMapping.java | 5 ++ .../RequestMappingHandlerMappingTests.java | 88 +++++++++++++++++-- src/asciidoc/whats-new.adoc | 5 +- 10 files changed, 488 insertions(+), 17 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java create mode 100644 spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java create mode 100644 spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java create mode 100644 spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java create mode 100644 spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java index 319435124ef..0613048d6c7 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -23,6 +23,7 @@ import java.util.concurrent.Callable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -36,7 +37,7 @@ import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.async.CallableProcessingInterceptor; @@ -55,6 +56,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * Tests with Java configuration. * * @author Rossen Stoyanchev + * @author Sam Brannen */ @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @@ -123,7 +125,7 @@ public class AsyncControllerJavaConfigTests { @RestController static class AsyncController { - @RequestMapping(path = "/callable") + @GetMapping("/callable") public Callable> getCallable() { return () -> Collections.singletonMap("key", "value"); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java index 5e115f66f67..ee365936162 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -16,25 +16,24 @@ package org.springframework.test.web.servlet.samples.context; -import org.springframework.stereotype.Controller; import org.springframework.test.web.Person; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; -@Controller +@RestController +@RequestMapping("/person") public class PersonController { private final PersonDao personDao; - public PersonController(PersonDao personDao) { + PersonController(PersonDao personDao) { this.personDao = personDao; } - @RequestMapping(value="/person/{id}", method=RequestMethod.GET) - @ResponseBody + @GetMapping("/{id}") public Person getPerson(@PathVariable long id) { return this.personDao.getPerson(id); } diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java new file mode 100644 index 00000000000..1bfa31a8672 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; + +/** + * Annotation for mapping HTTP {@code DELETE} requests onto specific handler + * methods. + * + *

Specifically, {@code @DeleteMapping} is a composed annotation that + * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.DELETE)}. + * Furthermore, this annotation does not support the + * {@link RequestMapping#method method}, {@link RequestMapping#name name}, + * {@link RequestMapping#headers headers}, {@link RequestMapping#consumes consumes}, + * and {@link RequestMapping#produces produces} attributes of {@code @RequestMapping}. + * + * @author Sam Brannen + * @since 4.3 + * @see GetMapping + * @see PostMapping + * @see PutMapping + * @see PatchMapping + * @see RequestMapping + */ +@Target(METHOD) +@Retention(RUNTIME) +@Documented +@RequestMapping(method = DELETE) +public @interface DeleteMapping { + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + +} diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java new file mode 100644 index 00000000000..1b91143266d --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +/** + * Annotation for mapping HTTP {@code GET} requests onto specific handler + * methods. + * + *

Specifically, {@code @GetMapping} is a composed annotation that + * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.GET)}. + * Furthermore, this annotation does not support the + * {@link RequestMapping#method method}, {@link RequestMapping#name name}, + * {@link RequestMapping#headers headers}, and {@link RequestMapping#consumes + * consumes} attributes of {@code @RequestMapping}. + * + * @author Sam Brannen + * @since 4.3 + * @see PostMapping + * @see PutMapping + * @see DeleteMapping + * @see PatchMapping + * @see RequestMapping + */ +@Target(METHOD) +@Retention(RUNTIME) +@Documented +@RequestMapping(method = GET) +public @interface GetMapping { + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; + +} diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java new file mode 100644 index 00000000000..5235bc9d0eb --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.springframework.web.bind.annotation.RequestMethod.PATCH; + +/** + * Annotation for mapping HTTP {@code PATCH} requests onto specific handler + * methods. + * + *

Specifically, {@code @PatchMapping} is a composed annotation that + * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.PATCH)}. + * Furthermore, this annotation does not support the + * {@link RequestMapping#method method}, {@link RequestMapping#name name}, + * {@link RequestMapping#headers headers}, and {@link RequestMapping#produces + * produces} attributes of {@code @RequestMapping}. + * + * @author Sam Brannen + * @since 4.3 + * @see GetMapping + * @see PostMapping + * @see PutMapping + * @see DeleteMapping + * @see RequestMapping + */ +@Target(METHOD) +@Retention(RUNTIME) +@Documented +@RequestMapping(method = PATCH) +public @interface PatchMapping { + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + +} diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java new file mode 100644 index 00000000000..2b2db362bc4 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * Annotation for mapping HTTP {@code POST} requests onto specific handler + * methods. + * + *

Specifically, {@code @PostMapping} is a composed annotation that + * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.POST)}. + * Furthermore, this annotation does not support the + * {@link RequestMapping#method method}, {@link RequestMapping#name name}, + * {@link RequestMapping#headers headers}, and {@link RequestMapping#produces + * produces} attributes of {@code @RequestMapping}. + * + * @author Sam Brannen + * @since 4.3 + * @see GetMapping + * @see PutMapping + * @see DeleteMapping + * @see PatchMapping + * @see RequestMapping + */ +@Target(METHOD) +@Retention(RUNTIME) +@Documented +@RequestMapping(method = POST) +public @interface PostMapping { + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + +} diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java new file mode 100644 index 00000000000..dfd159810e7 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +/** + * Annotation for mapping HTTP {@code PUT} requests onto specific handler + * methods. + * + *

Specifically, {@code @PutMapping} is a composed annotation that + * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.PUT)}. + * Furthermore, this annotation does not support the + * {@link RequestMapping#method method}, {@link RequestMapping#name name}, + * {@link RequestMapping#headers headers}, and {@link RequestMapping#produces + * produces} attributes of {@code @RequestMapping}. + * + * @author Sam Brannen + * @since 4.3 + * @see GetMapping + * @see PostMapping + * @see DeleteMapping + * @see PatchMapping + * @see RequestMapping + */ +@Target(METHOD) +@Retention(RUNTIME) +@Documented +@RequestMapping(method = PUT) +public @interface PutMapping { + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + +} diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java index 6bc7160b507..cda331c4b8c 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java @@ -279,6 +279,11 @@ import org.springframework.core.annotation.AliasFor; * @author Arjen Poutsma * @author Sam Brannen * @since 2.5 + * @see GetMapping + * @see PostMapping + * @see PutMapping + * @see DeleteMapping + * @see PatchMapping * @see RequestParam * @see RequestAttribute * @see PathVariable diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java index a784f1f191f..13946db1e46 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -34,6 +34,11 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.PathExtensionContentNegotiationStrategy; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.context.support.StaticWebApplicationContext; @@ -132,12 +137,64 @@ public class RequestMappingHandlerMappingTests { @Test public void resolveRequestMappingViaComposedAnnotation() throws Exception { + RequestMappingInfo info = assertComposedAnnotationMapping("postJson", "/postJson", RequestMethod.POST); + + assertEquals(MediaType.APPLICATION_JSON_VALUE, + info.getConsumesCondition().getConsumableMediaTypes().iterator().next().toString()); + assertEquals(MediaType.APPLICATION_JSON_VALUE, + info.getProducesCondition().getProducibleMediaTypes().iterator().next().toString()); + } + + @Test + public void getMapping() throws Exception { + assertComposedAnnotationMapping(RequestMethod.GET); + } + + @Test + public void postMapping() throws Exception { + assertComposedAnnotationMapping(RequestMethod.POST); + } + + @Test + public void putMapping() throws Exception { + assertComposedAnnotationMapping(RequestMethod.PUT); + } + + @Test + public void deleteMapping() throws Exception { + assertComposedAnnotationMapping(RequestMethod.DELETE); + } + + @Test + public void patchMapping() throws Exception { + assertComposedAnnotationMapping(RequestMethod.PATCH); + } + + private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) throws Exception { + String methodName = requestMethod.name().toLowerCase(); + String path = "/" + methodName; + + return assertComposedAnnotationMapping(methodName, path, requestMethod); + } + + private RequestMappingInfo assertComposedAnnotationMapping(String methodName, String path, + RequestMethod requestMethod) throws Exception { + Class clazz = ComposedAnnotationController.class; - Method method = clazz.getMethod("handleInput"); + Method method = clazz.getMethod(methodName); RequestMappingInfo info = this.handlerMapping.getMappingForMethod(method, clazz); assertNotNull(info); - assertEquals(Collections.singleton("/input"), info.getPatternsCondition().getPatterns()); + + Set paths = info.getPatternsCondition().getPatterns(); + assertEquals(1, paths.size()); + assertEquals(path, paths.iterator().next()); + + Set methods = info.getMethodsCondition().getMethods(); + assertEquals(1, methods.size()); + assertEquals(requestMethod, methods.iterator().next()); + + return info; } @@ -148,9 +205,30 @@ public class RequestMappingHandlerMappingTests { public void handle() { } - @PostJson("/input") - public void handleInput() { + @PostJson("/postJson") + public void postJson() { + } + + @GetMapping("/get") + public void get() { } + + @PostMapping("/post") + public void post() { + } + + @PutMapping("/put") + public void put() { + } + + @DeleteMapping("/delete") + public void delete() { + } + + @PatchMapping("/patch") + public void patch() { + } + } @RequestMapping(method = RequestMethod.POST, diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc index b0e0ed3537c..0c5b7f86def 100644 --- a/src/asciidoc/whats-new.adoc +++ b/src/asciidoc/whats-new.adoc @@ -630,7 +630,7 @@ public @interface MyTestConfig { === Core Container Improvements * It is no longer necessary to specify the `@Autowired` annotation if the target - bean only define one constructor. + bean only defines one constructor. * `@Configuration` classes support constructor injection. * Any SpEL expression used to specify the `condition` of an `@EventListener` can now refer to beans (i.e. `@beanName.method()`). @@ -662,6 +662,8 @@ Spring 4.3 also improves the caching abstraction as follows: === Web Improvements * Built-in support for <>. +* New `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, and `@PatchMapping` + _composed annotations_ for `@RequestMapping`. * New `@RequestScope`, `@SessionScope`, and `@ApplicationScope` _composed annotations_ for web scopes. * New `@RestControllerAdvice` annotation with combined `@ControllerAdvice` with `@ResponseBody` semantics. * `@ResponseStatus` is now supported at the class level and inherited by all methods. @@ -684,3 +686,4 @@ Spring 4.3 also improves the caching abstraction as follows: * Client-side REST test support allows indicating how many times a request is expected and whether the order of declaration for expectations should be ignored (see <>). * Client-side REST Test supports expectations for form data in the request body. +