diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RestControllerAdvice.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RestControllerAdvice.java new file mode 100644 index 00000000000..4dca5e36300 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RestControllerAdvice.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2015 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.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * A convenience annotation that is itself annotated with + * {@link ControllerAdvice @ControllerAdvice} + * and {@link ResponseBody @ResponseBody}. + * + *

Types that carry this annotation are treated as controller advice where + * {@link ExceptionHandler @ExceptionHandler} methods assume + * {@link ResponseBody @ResponseBody} semantics by default. + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ControllerAdvice +@ResponseBody +public @interface RestControllerAdvice { + + /** + * Alias for the {@link #basePackages} attribute. + *

Allows for more concise annotation declarations e.g.: + * {@code @ControllerAdvice("org.my.pkg")} is equivalent to + * {@code @ControllerAdvice(basePackages="org.my.pkg")}. + * @see #basePackages() + */ + @AliasFor("basePackages") + String[] value() default {}; + + /** + * Array of base packages. + *

Controllers that belong to those base packages or sub-packages thereof + * will be included, e.g.: {@code @ControllerAdvice(basePackages="org.my.pkg")} + * or {@code @ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"})}. + *

{@link #value} is an alias for this attribute, simply allowing for + * more concise use of the annotation. + *

Also consider using {@link #basePackageClasses()} as a type-safe + * alternative to String-based package names. + */ + @AliasFor("value") + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #value()} for specifying the packages + * to select Controllers to be assisted by the {@code @ControllerAdvice} + * annotated class. + *

Consider creating a special no-op marker class or interface in each package + * that serves no purpose other than being referenced by this attribute. + */ + Class[] basePackageClasses() default {}; + + /** + * Array of classes. + *

Controllers that are assignable to at least one of the given types + * will be assisted by the {@code @ControllerAdvice} annotated class. + */ + Class[] assignableTypes() default {}; + + /** + * Array of annotations. + *

Controllers that are annotated with this/one of those annotation(s) + * will be assisted by the {@code @ControllerAdvice} annotated class. + *

Consider creating a special annotation or use a predefined one, + * like {@link RestController @RestController}. + */ + Class[] annotations() default {}; + +} diff --git a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java index 0f8579520b3..cafc5846e7b 100644 --- a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java +++ b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.context.ApplicationContext; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.OrderUtils; import org.springframework.util.Assert; @@ -102,7 +103,9 @@ public class ControllerAdviceBean implements Ordered { this.order = initOrderFromBean(bean); } - ControllerAdvice annotation = AnnotationUtils.findAnnotation(beanType, ControllerAdvice.class); + ControllerAdvice annotation = AnnotatedElementUtils.findMergedAnnotation( + beanType, ControllerAdvice.class); + if (annotation != null) { this.basePackages = initBasePackages(annotation); this.assignableTypes = Arrays.asList(annotation.assignableTypes()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java index 8675990ae1e..abb471de957 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java @@ -37,6 +37,7 @@ import org.springframework.util.ClassUtils; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.annotation.ModelMethodProcessor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -330,30 +331,27 @@ public class ExceptionHandlerExceptionResolverTests { } - @ControllerAdvice + @RestControllerAdvice @Order(1) static class TestExceptionResolver { @ExceptionHandler - @ResponseBody public String handleException(IllegalStateException ex) { return "TestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); } @ExceptionHandler(ArrayIndexOutOfBoundsException.class) - @ResponseBody public String handleWithHandlerMethod(HandlerMethod handlerMethod) { return "HandlerMethod: " + handlerMethod.getMethod().getName(); } } - @ControllerAdvice + @RestControllerAdvice @Order(2) static class AnotherTestExceptionResolver { @ExceptionHandler({IllegalStateException.class, IllegalAccessException.class}) - @ResponseBody public String handleException(Exception ex) { return "AnotherTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); } @@ -373,36 +371,33 @@ public class ExceptionHandlerExceptionResolverTests { } - @ControllerAdvice("java.lang") + @RestControllerAdvice("java.lang") @Order(1) static class NotCalledTestExceptionResolver { @ExceptionHandler - @ResponseBody public String handleException(IllegalStateException ex) { return "NotCalledTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); } } - @ControllerAdvice("org.springframework.web.servlet.mvc.method.annotation") + @RestControllerAdvice("org.springframework.web.servlet.mvc.method.annotation") @Order(2) static class BasePackageTestExceptionResolver { @ExceptionHandler - @ResponseBody public String handleException(IllegalStateException ex) { return "BasePackageTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); } } - @ControllerAdvice + @RestControllerAdvice @Order(3) static class DefaultTestExceptionResolver { @ExceptionHandler - @ResponseBody public String handleException(IllegalStateException ex) { return "DefaultTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); } diff --git a/src/asciidoc/web-mvc.adoc b/src/asciidoc/web-mvc.adoc index 8382a35b922..b727cab1cd5 100644 --- a/src/asciidoc/web-mvc.adoc +++ b/src/asciidoc/web-mvc.adoc @@ -1495,8 +1495,9 @@ is a stereotype annotation that combines `@ResponseBody` and `@Controller`. More that, it gives more meaning to your Controller and also may carry additional semantics in future releases of the framework. -As with regular ++@Controller++s, a `@RestController` may be assisted by a -`@ControllerAdvice` Bean. See the <> section for more details. +As with regular ++@Controller++s, a `@RestController` may be assisted by +`@ControllerAdvice` or `@RestControllerAdvice` beans. See the <> +section for more details. [[mvc-ann-httpentity]] ==== Using HttpEntity @@ -1975,7 +1976,7 @@ which case they apply to matching controllers. This provides an alternative to u [[mvc-ann-controller-advice]] -==== Advising controllers with @ControllerAdvice +==== Advising controllers with @ControllerAdvice and @RestControllerAdvice The `@ControllerAdvice` annotation is a component annotation allowing implementation classes to be auto-detected through classpath scanning. It is automatically enabled when @@ -1986,8 +1987,10 @@ Classes annotated with `@ControllerAdvice` can contain `@ExceptionHandler`, `@RequestMapping` methods across all controller hierarchies as opposed to the controller hierarchy within which they are declared. -The `@ControllerAdvice` annotation can also target a subset of controllers with its -attributes: +`@RestControllerAdvice` is an alternative where `@ExceptionHandler` methods +assume `@ResponseBody` semantics by default. + +Both `@ControllerAdvice` and `@RestControllerAdvice` can target a subset of controllers: [source,java,indent=0] [subs="verbatim,quotes"] diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc index ed0a1eae313..359ba9c44c8 100644 --- a/src/asciidoc/whats-new.adoc +++ b/src/asciidoc/whats-new.adoc @@ -650,6 +650,9 @@ Spring 4.3 also improves the caching abstraction as follows: * `ConcurrentMapCacheManager` and `ConcurrentMapCache` now support the serialization of cache entries via a new `storeByValue` attribute. +=== Web Improvements + +* New `@RestControllerAdvice` annotation combines `@ControllerAdvice` with `@ResponseBody`. === Testing Improvements