Browse Source

Add @RestControllerAdvice

Issue: SPR-13673
pull/912/merge
Rossen Stoyanchev 10 years ago
parent
commit
12b73caa84
  1. 95
      spring-web/src/main/java/org/springframework/web/bind/annotation/RestControllerAdvice.java
  2. 5
      spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
  3. 17
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java
  4. 13
      src/asciidoc/web-mvc.adoc
  5. 3
      src/asciidoc/whats-new.adoc

95
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}.
*
* <p>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.
* <p>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.
* <p>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"})}.
* <p>{@link #value} is an alias for this attribute, simply allowing for
* more concise use of the annotation.
* <p>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.
* <p>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.
* <p>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.
* <p>Controllers that are annotated with this/one of those annotation(s)
* will be assisted by the {@code @ControllerAdvice} annotated class.
* <p>Consider creating a special annotation or use a predefined one,
* like {@link RestController @RestController}.
*/
Class<? extends Annotation>[] annotations() default {};
}

5
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.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.OrderUtils; import org.springframework.core.annotation.OrderUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -102,7 +103,9 @@ public class ControllerAdviceBean implements Ordered {
this.order = initOrderFromBean(bean); this.order = initOrderFromBean(bean);
} }
ControllerAdvice annotation = AnnotationUtils.findAnnotation(beanType, ControllerAdvice.class); ControllerAdvice annotation = AnnotatedElementUtils.findMergedAnnotation(
beanType, ControllerAdvice.class);
if (annotation != null) { if (annotation != null) {
this.basePackages = initBasePackages(annotation); this.basePackages = initBasePackages(annotation);
this.assignableTypes = Arrays.asList(annotation.assignableTypes()); this.assignableTypes = Arrays.asList(annotation.assignableTypes());

17
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.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; 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.HandlerMethod;
import org.springframework.web.method.annotation.ModelMethodProcessor; import org.springframework.web.method.annotation.ModelMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@ -330,30 +331,27 @@ public class ExceptionHandlerExceptionResolverTests {
} }
@ControllerAdvice @RestControllerAdvice
@Order(1) @Order(1)
static class TestExceptionResolver { static class TestExceptionResolver {
@ExceptionHandler @ExceptionHandler
@ResponseBody
public String handleException(IllegalStateException ex) { public String handleException(IllegalStateException ex) {
return "TestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); return "TestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
} }
@ExceptionHandler(ArrayIndexOutOfBoundsException.class) @ExceptionHandler(ArrayIndexOutOfBoundsException.class)
@ResponseBody
public String handleWithHandlerMethod(HandlerMethod handlerMethod) { public String handleWithHandlerMethod(HandlerMethod handlerMethod) {
return "HandlerMethod: " + handlerMethod.getMethod().getName(); return "HandlerMethod: " + handlerMethod.getMethod().getName();
} }
} }
@ControllerAdvice @RestControllerAdvice
@Order(2) @Order(2)
static class AnotherTestExceptionResolver { static class AnotherTestExceptionResolver {
@ExceptionHandler({IllegalStateException.class, IllegalAccessException.class}) @ExceptionHandler({IllegalStateException.class, IllegalAccessException.class})
@ResponseBody
public String handleException(Exception ex) { public String handleException(Exception ex) {
return "AnotherTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); return "AnotherTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
} }
@ -373,36 +371,33 @@ public class ExceptionHandlerExceptionResolverTests {
} }
@ControllerAdvice("java.lang") @RestControllerAdvice("java.lang")
@Order(1) @Order(1)
static class NotCalledTestExceptionResolver { static class NotCalledTestExceptionResolver {
@ExceptionHandler @ExceptionHandler
@ResponseBody
public String handleException(IllegalStateException ex) { public String handleException(IllegalStateException ex) {
return "NotCalledTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); return "NotCalledTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
} }
} }
@ControllerAdvice("org.springframework.web.servlet.mvc.method.annotation") @RestControllerAdvice("org.springframework.web.servlet.mvc.method.annotation")
@Order(2) @Order(2)
static class BasePackageTestExceptionResolver { static class BasePackageTestExceptionResolver {
@ExceptionHandler @ExceptionHandler
@ResponseBody
public String handleException(IllegalStateException ex) { public String handleException(IllegalStateException ex) {
return "BasePackageTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); return "BasePackageTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
} }
} }
@ControllerAdvice @RestControllerAdvice
@Order(3) @Order(3)
static class DefaultTestExceptionResolver { static class DefaultTestExceptionResolver {
@ExceptionHandler @ExceptionHandler
@ResponseBody
public String handleException(IllegalStateException ex) { public String handleException(IllegalStateException ex) {
return "DefaultTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); return "DefaultTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
} }

13
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 that, it gives more meaning to your Controller and also may carry additional semantics
in future releases of the framework. in future releases of the framework.
As with regular ++@Controller++s, a `@RestController` may be assisted by a As with regular ++@Controller++s, a `@RestController` may be assisted by
`@ControllerAdvice` Bean. See the <<mvc-ann-controller-advice>> section for more details. `@ControllerAdvice` or `@RestControllerAdvice` beans. See the <<mvc-ann-controller-advice>>
section for more details.
[[mvc-ann-httpentity]] [[mvc-ann-httpentity]]
==== Using HttpEntity ==== Using HttpEntity
@ -1975,7 +1976,7 @@ which case they apply to matching controllers. This provides an alternative to u
[[mvc-ann-controller-advice]] [[mvc-ann-controller-advice]]
==== Advising controllers with @ControllerAdvice ==== Advising controllers with @ControllerAdvice and @RestControllerAdvice
The `@ControllerAdvice` annotation is a component annotation allowing implementation The `@ControllerAdvice` annotation is a component annotation allowing implementation
classes to be auto-detected through classpath scanning. It is automatically enabled when 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 `@RequestMapping` methods across all controller hierarchies as opposed to the controller
hierarchy within which they are declared. hierarchy within which they are declared.
The `@ControllerAdvice` annotation can also target a subset of controllers with its `@RestControllerAdvice` is an alternative where `@ExceptionHandler` methods
attributes: assume `@ResponseBody` semantics by default.
Both `@ControllerAdvice` and `@RestControllerAdvice` can target a subset of controllers:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]

3
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 * `ConcurrentMapCacheManager` and `ConcurrentMapCache` now support the serialization
of cache entries via a new `storeByValue` attribute. of cache entries via a new `storeByValue` attribute.
=== Web Improvements
* New `@RestControllerAdvice` annotation combines `@ControllerAdvice` with `@ResponseBody`.
=== Testing Improvements === Testing Improvements

Loading…
Cancel
Save