mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Support annotation attribute aliases and overrides via @AliasFor
This commit introduces first-class support for aliases for annotation
attributes. Specifically, this commit introduces a new @AliasFor
annotation that can be used to declare a pair of aliased attributes
within a single annotation or an alias from an attribute in a custom
composed annotation to an attribute in a meta-annotation.
To support @AliasFor within annotation instances, AnnotationUtils has
been overhauled to "synthesize" any annotations returned by "get" and
"find" searches. A SynthesizedAnnotation is an annotation that is
wrapped in a JDK dynamic proxy which provides run-time support for
@AliasFor semantics. SynthesizedAnnotationInvocationHandler is the
actual handler behind the proxy.
In addition, the contract for @AliasFor is fully validated, and an
AnnotationConfigurationException is thrown in case invalid
configuration is detected.
For example, @ContextConfiguration from the spring-test module is now
declared as follows:
public @interface ContextConfiguration {
@AliasFor(attribute = "locations")
String[] value() default {};
@AliasFor(attribute = "value")
String[] locations() default {};
// ...
}
The following annotations and their related support classes have been
modified to use @AliasFor.
- @ManagedResource
- @ContextConfiguration
- @ActiveProfiles
- @TestExecutionListeners
- @TestPropertySource
- @Sql
- @ControllerAdvice
- @RequestMapping
Similarly, support for AnnotationAttributes has been reworked to
support @AliasFor as well. This allows for fine-grained control over
exactly which attributes are overridden within an annotation hierarchy.
In fact, it is now possible to declare an alias for the 'value'
attribute of a meta-annotation.
For example, given the revised declaration of @ContextConfiguration
above, one can now develop a composed annotation with a custom
attribute override as follows.
@ContextConfiguration
public @interface MyTestConfig {
@AliasFor(
annotation = ContextConfiguration.class,
attribute = "locations"
)
String[] xmlFiles();
// ...
}
Consequently, the following are functionally equivalent.
- @MyTestConfig(xmlFiles = "test.xml")
- @ContextConfiguration("test.xml")
- @ContextConfiguration(locations = "test.xml").
Issue: SPR-11512, SPR-11513
This commit is contained in:
+203
-23
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -26,11 +25,14 @@ import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
|
||||
@@ -46,9 +48,8 @@ public class AnnotatedElementUtilsTests {
|
||||
|
||||
private static final String TX_NAME = Transactional.class.getName();
|
||||
|
||||
private Set<String> names(Class<?>... classes) {
|
||||
return stream(classes).map(clazz -> clazz.getName()).collect(Collectors.toSet());
|
||||
}
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void getMetaAnnotationTypesOnNonAnnotatedClass() {
|
||||
@@ -180,7 +181,8 @@ public class AnnotatedElementUtilsTests {
|
||||
public void getAllAnnotationAttributesOnClassWithMultipleComposedAnnotations() {
|
||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class, TX_NAME);
|
||||
assertNotNull("Annotation attributes map for @Transactional on TxFromMultipleComposedAnnotations", attributes);
|
||||
assertEquals("value for TxFromMultipleComposedAnnotations.", asList("TxComposed1", "TxComposed2"), attributes.get("value"));
|
||||
assertEquals("value for TxFromMultipleComposedAnnotations.", asList("TxInheritedComposed", "TxComposed"),
|
||||
attributes.get("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -274,6 +276,77 @@ public class AnnotatedElementUtilsTests {
|
||||
assertTrue(isAnnotated(element, name));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationAttributesWithConventionBasedComposedAnnotation() {
|
||||
Class<?> element = ConventionBasedComposedContextConfigClass.class;
|
||||
String name = ContextConfig.class.getName();
|
||||
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||
|
||||
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
|
||||
assertArrayEquals("locations", new String[] { "explicitDeclaration" }, attributes.getStringArray("locations"));
|
||||
assertArrayEquals("value", new String[] { "explicitDeclaration" }, attributes.getStringArray("value"));
|
||||
|
||||
// Verify contracts between utility methods:
|
||||
assertTrue(isAnnotated(element, name));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationAttributesWithAliasedComposedAnnotation() {
|
||||
Class<?> element = AliasedComposedContextConfigClass.class;
|
||||
String name = ContextConfig.class.getName();
|
||||
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||
|
||||
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
|
||||
assertArrayEquals("value", new String[] { "test.xml" }, attributes.getStringArray("value"));
|
||||
assertArrayEquals("locations", new String[] { "test.xml" }, attributes.getStringArray("locations"));
|
||||
|
||||
// Verify contracts between utility methods:
|
||||
assertTrue(isAnnotated(element, name));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationAttributesWithAliasedValueComposedAnnotation() {
|
||||
Class<?> element = AliasedValueComposedContextConfigClass.class;
|
||||
String name = ContextConfig.class.getName();
|
||||
AnnotationAttributes attributes = getAnnotationAttributes(element, name);
|
||||
|
||||
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes);
|
||||
assertArrayEquals("locations", new String[] { "test.xml" }, attributes.getStringArray("locations"));
|
||||
assertArrayEquals("value", new String[] { "test.xml" }, attributes.getStringArray("value"));
|
||||
|
||||
// Verify contracts between utility methods:
|
||||
assertTrue(isAnnotated(element, name));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationAttributesWithInvalidConventionBasedComposedAnnotation() {
|
||||
Class<?> element = InvalidConventionBasedComposedContextConfigClass.class;
|
||||
String name = ContextConfig.class.getName();
|
||||
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(either(containsString("attribute [value] and its alias [locations]")).or(
|
||||
containsString("attribute [locations] and its alias [value]")));
|
||||
exception.expectMessage(either(
|
||||
containsString("values of [{duplicateDeclaration}] and [{requiredLocationsDeclaration}]")).or(
|
||||
containsString("values of [{requiredLocationsDeclaration}] and [{duplicateDeclaration}]")));
|
||||
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||
getAnnotationAttributes(element, name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationAttributesWithInvalidAliasedComposedAnnotation() {
|
||||
Class<?> element = InvalidAliasedComposedContextConfigClass.class;
|
||||
String name = ContextConfig.class.getName();
|
||||
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(either(containsString("attribute [value] and its alias [locations]")).or(
|
||||
containsString("attribute [locations] and its alias [value]")));
|
||||
exception.expectMessage(either(containsString("values of [{duplicateDeclaration}] and [{test.xml}]")).or(
|
||||
containsString("values of [{test.xml}] and [{duplicateDeclaration}]")));
|
||||
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||
getAnnotationAttributes(element, name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAnnotationAttributesOnInheritedAnnotationInterface() {
|
||||
AnnotationAttributes attributes = findAnnotationAttributes(InheritedAnnotationInterface.class, Transactional.class);
|
||||
@@ -375,27 +448,39 @@ public class AnnotatedElementUtilsTests {
|
||||
assertEquals("TX qualifier for MetaAndLocalTxConfigClass.", "localTxMgr", attributes.getString("qualifier"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() {
|
||||
AnnotationAttributes attributes = findAnnotationAttributes(AliasedTransactionalComponentClass.class,
|
||||
AliasedTransactional.class);
|
||||
assertNotNull("Should find @AliasedTransactional on AliasedTransactionalComponentClass", attributes);
|
||||
assertEquals("TX value for AliasedTransactionalComponentClass.", "aliasForQualifier",
|
||||
attributes.getString("value"));
|
||||
assertEquals("TX qualifier for AliasedTransactionalComponentClass.", "aliasForQualifier",
|
||||
attributes.getString("qualifier"));
|
||||
}
|
||||
|
||||
private Set<String> names(Class<?>... classes) {
|
||||
return stream(classes).map(clazz -> clazz.getName()).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@MetaCycle3
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@Documented
|
||||
@interface MetaCycle1 {
|
||||
}
|
||||
|
||||
@MetaCycle1
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@Documented
|
||||
@interface MetaCycle2 {
|
||||
}
|
||||
|
||||
@MetaCycle2
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@interface MetaCycle3 {
|
||||
}
|
||||
|
||||
@@ -407,7 +492,6 @@ public class AnnotatedElementUtilsTests {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@Inherited
|
||||
@interface Transactional {
|
||||
|
||||
@@ -418,19 +502,29 @@ public class AnnotatedElementUtilsTests {
|
||||
boolean readOnly() default false;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Inherited
|
||||
@interface AliasedTransactional {
|
||||
|
||||
@AliasFor(attribute = "qualifier")
|
||||
String value() default "";
|
||||
|
||||
@AliasFor(attribute = "value")
|
||||
String qualifier() default "";
|
||||
}
|
||||
|
||||
@Transactional(qualifier = "composed1")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Inherited
|
||||
@interface Composed1 {
|
||||
@interface InheritedComposed {
|
||||
}
|
||||
|
||||
@Transactional(qualifier = "composed2", readOnly = true)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@interface Composed2 {
|
||||
@interface Composed {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -440,14 +534,14 @@ public class AnnotatedElementUtilsTests {
|
||||
String qualifier() default "txMgr";
|
||||
}
|
||||
|
||||
@Transactional("TxComposed1")
|
||||
@Transactional("TxInheritedComposed")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface TxComposed1 {
|
||||
@interface TxInheritedComposed {
|
||||
}
|
||||
|
||||
@Transactional("TxComposed2")
|
||||
@Transactional("TxComposed")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface TxComposed2 {
|
||||
@interface TxComposed {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -461,6 +555,12 @@ public class AnnotatedElementUtilsTests {
|
||||
@interface ComposedTransactionalComponent {
|
||||
}
|
||||
|
||||
@AliasedTransactional(value = "aliasForQualifier")
|
||||
@Component
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface AliasedTransactionalComponent {
|
||||
}
|
||||
|
||||
@TxComposedWithOverride
|
||||
// Override default "txMgr" from @TxComposedWithOverride with "localTxMgr"
|
||||
@Transactional(qualifier = "localTxMgr")
|
||||
@@ -469,6 +569,63 @@ public class AnnotatedElementUtilsTests {
|
||||
@interface MetaAndLocalTxConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock of {@link org.springframework.test.context.ContextConfiguration}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface ContextConfig {
|
||||
|
||||
@AliasFor(attribute = "locations")
|
||||
String[] value() default {};
|
||||
|
||||
@AliasFor(attribute = "value")
|
||||
String[] locations() default {};
|
||||
}
|
||||
|
||||
@ContextConfig
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface ConventionBasedComposedContextConfig {
|
||||
|
||||
String[] locations() default {};
|
||||
}
|
||||
|
||||
@ContextConfig(value = "duplicateDeclaration")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface InvalidConventionBasedComposedContextConfig {
|
||||
|
||||
String[] locations();
|
||||
}
|
||||
|
||||
@ContextConfig
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasedComposedContextConfig {
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||
String[] xmlConfigFiles();
|
||||
}
|
||||
|
||||
@ContextConfig
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasedValueComposedContextConfig {
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "value")
|
||||
String[] locations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalid because the configuration declares a value for 'value' and
|
||||
* requires a value for the aliased 'locations'. So we likely end up with
|
||||
* both 'value' and 'locations' being present in {@link AnnotationAttributes}
|
||||
* but with different values, which violates the contract of {@code @AliasFor}.
|
||||
*/
|
||||
@ContextConfig(value = "duplicateDeclaration")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface InvalidAliasedComposedContextConfig {
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||
String[] xmlConfigFiles();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static class NonAnnotatedClass {
|
||||
@@ -485,22 +642,26 @@ public class AnnotatedElementUtilsTests {
|
||||
static class ComposedTransactionalComponentClass {
|
||||
}
|
||||
|
||||
@AliasedTransactionalComponent
|
||||
static class AliasedTransactionalComponentClass {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
static class ClassWithInheritedAnnotation {
|
||||
}
|
||||
|
||||
@Composed2
|
||||
@Composed
|
||||
static class SubClassWithInheritedAnnotation extends ClassWithInheritedAnnotation {
|
||||
}
|
||||
|
||||
static class SubSubClassWithInheritedAnnotation extends SubClassWithInheritedAnnotation {
|
||||
}
|
||||
|
||||
@Composed1
|
||||
@InheritedComposed
|
||||
static class ClassWithInheritedComposedAnnotation {
|
||||
}
|
||||
|
||||
@Composed2
|
||||
@Composed
|
||||
static class SubClassWithInheritedComposedAnnotation extends ClassWithInheritedComposedAnnotation {
|
||||
}
|
||||
|
||||
@@ -519,8 +680,8 @@ public class AnnotatedElementUtilsTests {
|
||||
static class DerivedTxConfig extends TxConfig {
|
||||
}
|
||||
|
||||
@TxComposed1
|
||||
@TxComposed2
|
||||
@TxInheritedComposed
|
||||
@TxComposed
|
||||
static class TxFromMultipleComposedAnnotations {
|
||||
}
|
||||
|
||||
@@ -595,4 +756,23 @@ public class AnnotatedElementUtilsTests {
|
||||
public static interface SubSubNonInheritedAnnotationInterface extends SubNonInheritedAnnotationInterface {
|
||||
}
|
||||
|
||||
@ConventionBasedComposedContextConfig(locations = "explicitDeclaration")
|
||||
static class ConventionBasedComposedContextConfigClass {
|
||||
}
|
||||
|
||||
@InvalidConventionBasedComposedContextConfig(locations = "requiredLocationsDeclaration")
|
||||
static class InvalidConventionBasedComposedContextConfigClass {
|
||||
}
|
||||
|
||||
@AliasedComposedContextConfig(xmlConfigFiles = "test.xml")
|
||||
static class AliasedComposedContextConfigClass {
|
||||
}
|
||||
|
||||
@AliasedValueComposedContextConfig(locations = "test.xml")
|
||||
static class AliasedValueComposedContextConfigClass {
|
||||
}
|
||||
|
||||
@InvalidAliasedComposedContextConfig(xmlConfigFiles = "test.xml")
|
||||
static class InvalidAliasedComposedContextConfigClass {
|
||||
}
|
||||
}
|
||||
|
||||
+382
-31
@@ -23,11 +23,13 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
||||
@@ -48,6 +50,9 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
*/
|
||||
public class AnnotationUtilsTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void findMethodAnnotationOnLeaf() throws Exception {
|
||||
Method m = Leaf.class.getMethod("annotatedOnLeaf");
|
||||
@@ -154,7 +159,8 @@ public class AnnotationUtilsTests {
|
||||
/** @since 4.1.2 */
|
||||
@Test
|
||||
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
|
||||
Component component = AnnotationUtils.findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
|
||||
Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class,
|
||||
Component.class);
|
||||
assertNotNull(component);
|
||||
assertEquals("meta2", component.value());
|
||||
}
|
||||
@@ -162,7 +168,7 @@ public class AnnotationUtilsTests {
|
||||
/** @since 4.0.3 */
|
||||
@Test
|
||||
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedAnnotations() {
|
||||
Transactional transactional = AnnotationUtils.findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class);
|
||||
Transactional transactional = findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class);
|
||||
assertNotNull(transactional);
|
||||
assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly());
|
||||
}
|
||||
@@ -170,21 +176,21 @@ public class AnnotationUtilsTests {
|
||||
/** @since 4.0.3 */
|
||||
@Test
|
||||
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedComposedAnnotations() {
|
||||
Component component = AnnotationUtils.findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class);
|
||||
Component component = findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class);
|
||||
assertNotNull(component);
|
||||
assertEquals("meta2", component.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findClassAnnotationOnMetaMetaAnnotatedClass() {
|
||||
Component component = AnnotationUtils.findAnnotation(MetaMetaAnnotatedClass.class, Component.class);
|
||||
Component component = findAnnotation(MetaMetaAnnotatedClass.class, Component.class);
|
||||
assertNotNull("Should find meta-annotation on composed annotation on class", component);
|
||||
assertEquals("meta2", component.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findClassAnnotationOnMetaMetaMetaAnnotatedClass() {
|
||||
Component component = AnnotationUtils.findAnnotation(MetaMetaMetaAnnotatedClass.class, Component.class);
|
||||
Component component = findAnnotation(MetaMetaMetaAnnotatedClass.class, Component.class);
|
||||
assertNotNull("Should find meta-annotation on meta-annotation on composed annotation on class", component);
|
||||
assertEquals("meta2", component.value());
|
||||
}
|
||||
@@ -192,55 +198,55 @@ public class AnnotationUtilsTests {
|
||||
@Test
|
||||
public void findClassAnnotationOnAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||
// TransactionalClass is NOT annotated or meta-annotated with @Component
|
||||
Component component = AnnotationUtils.findAnnotation(TransactionalClass.class, Component.class);
|
||||
Component component = findAnnotation(TransactionalClass.class, Component.class);
|
||||
assertNull("Should not find @Component on TransactionalClass", component);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findClassAnnotationOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||
Component component = AnnotationUtils.findAnnotation(MetaCycleAnnotatedClass.class, Component.class);
|
||||
Component component = findAnnotation(MetaCycleAnnotatedClass.class, Component.class);
|
||||
assertNull("Should not find @Component on MetaCycleAnnotatedClass", component);
|
||||
}
|
||||
|
||||
/** @since 4.2 */
|
||||
@Test
|
||||
public void findClassAnnotationOnInheritedAnnotationInterface() {
|
||||
Transactional tx = AnnotationUtils.findAnnotation(InheritedAnnotationInterface.class, Transactional.class);
|
||||
Transactional tx = findAnnotation(InheritedAnnotationInterface.class, Transactional.class);
|
||||
assertNotNull("Should find @Transactional on InheritedAnnotationInterface", tx);
|
||||
}
|
||||
|
||||
/** @since 4.2 */
|
||||
@Test
|
||||
public void findClassAnnotationOnSubInheritedAnnotationInterface() {
|
||||
Transactional tx = AnnotationUtils.findAnnotation(SubInheritedAnnotationInterface.class, Transactional.class);
|
||||
Transactional tx = findAnnotation(SubInheritedAnnotationInterface.class, Transactional.class);
|
||||
assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", tx);
|
||||
}
|
||||
|
||||
/** @since 4.2 */
|
||||
@Test
|
||||
public void findClassAnnotationOnSubSubInheritedAnnotationInterface() {
|
||||
Transactional tx = AnnotationUtils.findAnnotation(SubSubInheritedAnnotationInterface.class, Transactional.class);
|
||||
Transactional tx = findAnnotation(SubSubInheritedAnnotationInterface.class, Transactional.class);
|
||||
assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", tx);
|
||||
}
|
||||
|
||||
/** @since 4.2 */
|
||||
@Test
|
||||
public void findClassAnnotationOnNonInheritedAnnotationInterface() {
|
||||
Order order = AnnotationUtils.findAnnotation(NonInheritedAnnotationInterface.class, Order.class);
|
||||
Order order = findAnnotation(NonInheritedAnnotationInterface.class, Order.class);
|
||||
assertNotNull("Should find @Order on NonInheritedAnnotationInterface", order);
|
||||
}
|
||||
|
||||
/** @since 4.2 */
|
||||
@Test
|
||||
public void findClassAnnotationOnSubNonInheritedAnnotationInterface() {
|
||||
Order order = AnnotationUtils.findAnnotation(SubNonInheritedAnnotationInterface.class, Order.class);
|
||||
Order order = findAnnotation(SubNonInheritedAnnotationInterface.class, Order.class);
|
||||
assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", order);
|
||||
}
|
||||
|
||||
/** @since 4.2 */
|
||||
@Test
|
||||
public void findClassAnnotationOnSubSubNonInheritedAnnotationInterface() {
|
||||
Order order = AnnotationUtils.findAnnotation(SubSubNonInheritedAnnotationInterface.class, Order.class);
|
||||
Order order = findAnnotation(SubSubNonInheritedAnnotationInterface.class, Order.class);
|
||||
assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", order);
|
||||
}
|
||||
|
||||
@@ -375,13 +381,53 @@ public class AnnotationUtilsTests {
|
||||
assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationClass.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationAttributesWithoutAttributeAliases() {
|
||||
Component component = WebController.class.getAnnotation(Component.class);
|
||||
assertNotNull(component);
|
||||
|
||||
AnnotationAttributes attributes = (AnnotationAttributes) getAnnotationAttributes(component);
|
||||
assertNotNull(attributes);
|
||||
assertEquals("value attribute: ", "webController", attributes.getString(VALUE));
|
||||
assertEquals(Component.class, attributes.annotationType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationAttributesWithAttributeAliases() throws Exception {
|
||||
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||
WebMapping webMapping = method.getAnnotation(WebMapping.class);
|
||||
AnnotationAttributes attributes = (AnnotationAttributes) getAnnotationAttributes(webMapping);
|
||||
assertNotNull(attributes);
|
||||
assertEquals(WebMapping.class, attributes.annotationType());
|
||||
assertEquals("name attribute: ", "foo", attributes.getString("name"));
|
||||
assertEquals("value attribute: ", "/test", attributes.getString(VALUE));
|
||||
assertEquals("path attribute: ", "/test", attributes.getString("path"));
|
||||
|
||||
method = WebController.class.getMethod("handleMappedWithPathAttribute");
|
||||
webMapping = method.getAnnotation(WebMapping.class);
|
||||
attributes = (AnnotationAttributes) getAnnotationAttributes(webMapping);
|
||||
assertNotNull(attributes);
|
||||
assertEquals(WebMapping.class, attributes.annotationType());
|
||||
assertEquals("name attribute: ", "bar", attributes.getString("name"));
|
||||
assertEquals("value attribute: ", "/test", attributes.getString(VALUE));
|
||||
assertEquals("path attribute: ", "/test", attributes.getString("path"));
|
||||
|
||||
method = WebController.class.getMethod("handleMappedWithPathValueAndAttributes");
|
||||
webMapping = method.getAnnotation(WebMapping.class);
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("attribute [value] and its alias [path]"));
|
||||
exception.expectMessage(containsString("values of [/enigma] and [/test]"));
|
||||
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||
getAnnotationAttributes(webMapping);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueFromAnnotation() throws Exception {
|
||||
Method method = SimpleFoo.class.getMethod("something", Object.class);
|
||||
Order order = findAnnotation(method, Order.class);
|
||||
|
||||
assertEquals(1, AnnotationUtils.getValue(order, AnnotationUtils.VALUE));
|
||||
assertEquals(1, AnnotationUtils.getValue(order));
|
||||
assertEquals(1, getValue(order, VALUE));
|
||||
assertEquals(1, getValue(order));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -391,8 +437,8 @@ public class AnnotationUtilsTests {
|
||||
Annotation annotation = declaredAnnotations[0];
|
||||
assertNotNull(annotation);
|
||||
assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName());
|
||||
assertEquals(42, AnnotationUtils.getValue(annotation, AnnotationUtils.VALUE));
|
||||
assertEquals(42, AnnotationUtils.getValue(annotation));
|
||||
assertEquals(42, getValue(annotation, VALUE));
|
||||
assertEquals(42, getValue(annotation));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -400,8 +446,8 @@ public class AnnotationUtilsTests {
|
||||
Method method = SimpleFoo.class.getMethod("something", Object.class);
|
||||
Order order = findAnnotation(method, Order.class);
|
||||
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, AnnotationUtils.getDefaultValue(order, AnnotationUtils.VALUE));
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, AnnotationUtils.getDefaultValue(order));
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(order, VALUE));
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(order));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -411,14 +457,14 @@ public class AnnotationUtilsTests {
|
||||
Annotation annotation = declaredAnnotations[0];
|
||||
assertNotNull(annotation);
|
||||
assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName());
|
||||
assertEquals(-1, AnnotationUtils.getDefaultValue(annotation, AnnotationUtils.VALUE));
|
||||
assertEquals(-1, AnnotationUtils.getDefaultValue(annotation));
|
||||
assertEquals(-1, getDefaultValue(annotation, VALUE));
|
||||
assertEquals(-1, getDefaultValue(annotation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDefaultValueFromAnnotationType() throws Exception {
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, AnnotationUtils.getDefaultValue(Order.class, AnnotationUtils.VALUE));
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, AnnotationUtils.getDefaultValue(Order.class));
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class, VALUE));
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -431,14 +477,174 @@ public class AnnotationUtilsTests {
|
||||
@Test
|
||||
public void getRepeatableFromMethod() throws Exception {
|
||||
Method method = InterfaceWithRepeated.class.getMethod("foo");
|
||||
Set<MyRepeatable> annotions = AnnotationUtils.getRepeatableAnnotation(method,
|
||||
MyRepeatableContainer.class, MyRepeatable.class);
|
||||
Set<String> values = new HashSet<String>();
|
||||
for (MyRepeatable myRepeatable : annotions) {
|
||||
values.add(myRepeatable.value());
|
||||
Set<MyRepeatable> annotations = getRepeatableAnnotation(method, MyRepeatableContainer.class, MyRepeatable.class);
|
||||
assertNotNull(annotations);
|
||||
List<String> values = annotations.stream().map(MyRepeatable::value).collect(Collectors.toList());
|
||||
assertThat(values, equalTo(Arrays.asList("a", "b", "c", "meta")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRepeatableWithAttributeAliases() throws Exception {
|
||||
Set<ContextConfig> annotations = getRepeatableAnnotation(TestCase.class, Hierarchy.class, ContextConfig.class);
|
||||
assertNotNull(annotations);
|
||||
|
||||
List<String> locations = annotations.stream().map(ContextConfig::locations).collect(Collectors.toList());
|
||||
assertThat(locations, equalTo(Arrays.asList("A", "B")));
|
||||
|
||||
List<String> values = annotations.stream().map(ContextConfig::value).collect(Collectors.toList());
|
||||
assertThat(values, equalTo(Arrays.asList("A", "B")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedAttributeNameFromAliasedComposedAnnotation() throws Exception {
|
||||
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
|
||||
assertEquals("locations", getAliasedAttributeName(attribute, ContextConfig.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithoutAttributeAliases() throws Exception {
|
||||
Component component = findAnnotation(WebController.class, Component.class);
|
||||
assertNotNull(component);
|
||||
Component synthesizedComponent = synthesizeAnnotation(component);
|
||||
assertNotNull(synthesizedComponent);
|
||||
assertSame(component, synthesizedComponent);
|
||||
assertEquals("value attribute: ", "webController", synthesizedComponent.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
|
||||
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("Attribute [foo] in"));
|
||||
exception.expectMessage(containsString(AliasForNonexistentAttribute.class.getName()));
|
||||
exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute [bar]"));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasWithoutMirroredAliasFor() throws Exception {
|
||||
AliasForWithoutMirroredAliasFor annotation = AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class);
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("Attribute [bar] in"));
|
||||
exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName()));
|
||||
exception.expectMessage(containsString("must be declared as an @AliasFor [foo]"));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasWithMirroredAliasForWrongAttribute() throws Exception {
|
||||
AliasForWithMirroredAliasForWrongAttribute annotation = AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class);
|
||||
|
||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||
// reflection, we cannot make the test dependent on any specific ordering.
|
||||
//
|
||||
// In other words, we can't be certain which type of exception message we'll get,
|
||||
// so we allow for both possibilities.
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("Attribute [bar] in"));
|
||||
exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName()));
|
||||
exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")).
|
||||
or(containsString("is declared as an @AliasFor nonexistent attribute [quux]")));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasForAttributeOfDifferentType() throws Exception {
|
||||
AliasForAttributeOfDifferentType annotation = AliasForAttributeOfDifferentTypeClass.class.getAnnotation(AliasForAttributeOfDifferentType.class);
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||
exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName()));
|
||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||
// reflection, we cannot make the test dependent on any specific ordering.
|
||||
//
|
||||
// In other words, we don't know if "foo" or "bar" will come first.
|
||||
exception.expectMessage(containsString("attribute [foo]"));
|
||||
exception.expectMessage(containsString("attribute [bar]"));
|
||||
exception.expectMessage(containsString("must declare the same return type"));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasForWithMissingDefaultValues() throws Exception {
|
||||
AliasForWithMissingDefaultValues annotation = AliasForWithMissingDefaultValuesClass.class.getAnnotation(AliasForWithMissingDefaultValues.class);
|
||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
|
||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||
// reflection, we cannot make the test dependent on any specific ordering.
|
||||
//
|
||||
// In other words, we don't know if "foo" or "bar" will come first.
|
||||
exception.expectMessage(containsString("attribute [foo]"));
|
||||
exception.expectMessage(containsString("attribute [bar]"));
|
||||
exception.expectMessage(containsString("must declare default values"));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasForAttributeWithDifferentDefaultValue() throws Exception {
|
||||
AliasForAttributeWithDifferentDefaultValue annotation = AliasForAttributeWithDifferentDefaultValueClass.class.getAnnotation(AliasForAttributeWithDifferentDefaultValue.class);
|
||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
|
||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||
// reflection, we cannot make the test dependent on any specific ordering.
|
||||
//
|
||||
// In other words, we don't know if "foo" or "bar" will come first.
|
||||
exception.expectMessage(containsString("attribute [foo]"));
|
||||
exception.expectMessage(containsString("attribute [bar]"));
|
||||
exception.expectMessage(containsString("must declare the same default value"));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliases() throws Exception {
|
||||
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||
WebMapping webMapping = method.getAnnotation(WebMapping.class);
|
||||
assertNotNull(webMapping);
|
||||
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping);
|
||||
assertNotSame(webMapping, synthesizedWebMapping);
|
||||
assertThat(synthesizedWebMapping, instanceOf(SynthesizedAnnotation.class));
|
||||
|
||||
assertNotNull(synthesizedWebMapping);
|
||||
assertEquals("name attribute: ", "foo", synthesizedWebMapping.name());
|
||||
assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping.path());
|
||||
assertEquals("actual value attribute: ", "/test", synthesizedWebMapping.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception {
|
||||
Hierarchy hierarchy = TestCase.class.getAnnotation(Hierarchy.class);
|
||||
assertNotNull(hierarchy);
|
||||
Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
|
||||
assertNotSame(hierarchy, synthesizedHierarchy);
|
||||
assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class));
|
||||
|
||||
ContextConfig[] configs = synthesizedHierarchy.value();
|
||||
assertNotNull(configs);
|
||||
for (ContextConfig contextConfig : configs) {
|
||||
assertThat(contextConfig, instanceOf(SynthesizedAnnotation.class));
|
||||
}
|
||||
assertThat(values, equalTo((Set<String>) new HashSet<String>(
|
||||
Arrays.asList("a", "b", "c", "meta"))));
|
||||
|
||||
List<String> locations = Arrays.stream(configs).map(ContextConfig::locations).collect(Collectors.toList());
|
||||
assertThat(locations, equalTo(Arrays.asList("A", "B")));
|
||||
|
||||
List<String> values = Arrays.stream(configs).map(ContextConfig::value).collect(Collectors.toList());
|
||||
assertThat(values, equalTo(Arrays.asList("A", "B")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAlreadySynthesizedAnnotation() throws Exception {
|
||||
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||
WebMapping webMapping = method.getAnnotation(WebMapping.class);
|
||||
assertNotNull(webMapping);
|
||||
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping);
|
||||
assertNotSame(webMapping, synthesizedWebMapping);
|
||||
WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping);
|
||||
assertSame(synthesizedWebMapping, synthesizedAgainWebMapping);
|
||||
assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class));
|
||||
|
||||
assertNotNull(synthesizedAgainWebMapping);
|
||||
assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name());
|
||||
assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path());
|
||||
assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value());
|
||||
}
|
||||
|
||||
|
||||
@@ -710,4 +916,149 @@ public class AnnotationUtilsTests {
|
||||
void foo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock of {@link org.springframework.web.bind.annotation.RequestMapping}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface WebMapping {
|
||||
|
||||
String name();
|
||||
|
||||
@AliasFor(attribute = "path")
|
||||
String value() default "";
|
||||
|
||||
@AliasFor(attribute = "value")
|
||||
String path() default "";
|
||||
}
|
||||
|
||||
@Component("webController")
|
||||
static class WebController {
|
||||
|
||||
@WebMapping(value = "/test", name = "foo")
|
||||
public void handleMappedWithValueAttribute() {
|
||||
}
|
||||
|
||||
@WebMapping(path = "/test", name = "bar")
|
||||
public void handleMappedWithPathAttribute() {
|
||||
}
|
||||
|
||||
@WebMapping(value = "/enigma", path = "/test", name = "baz")
|
||||
public void handleMappedWithPathValueAndAttributes() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock of {@link org.springframework.test.context.ContextConfiguration}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface ContextConfig {
|
||||
|
||||
@AliasFor(attribute = "locations")
|
||||
String value() default "";
|
||||
|
||||
@AliasFor(attribute = "value")
|
||||
String locations() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock of {@link org.springframework.test.context.ContextHierarchy}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface Hierarchy {
|
||||
|
||||
ContextConfig[] value();
|
||||
}
|
||||
|
||||
@Hierarchy({ @ContextConfig("A"), @ContextConfig(locations = "B") })
|
||||
static class TestCase {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasForNonexistentAttribute {
|
||||
|
||||
@AliasFor(attribute = "bar")
|
||||
String foo() default "";
|
||||
}
|
||||
|
||||
@AliasForNonexistentAttribute
|
||||
static class AliasForNonexistentAttributeClass {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasForWithoutMirroredAliasFor {
|
||||
|
||||
@AliasFor(attribute = "bar")
|
||||
String foo() default "";
|
||||
|
||||
String bar() default "";
|
||||
}
|
||||
|
||||
@AliasForWithoutMirroredAliasFor
|
||||
static class AliasForWithoutMirroredAliasForClass {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasForWithMirroredAliasForWrongAttribute {
|
||||
|
||||
@AliasFor(attribute = "bar")
|
||||
String[] foo() default "";
|
||||
|
||||
@AliasFor(attribute = "quux")
|
||||
String[] bar() default "";
|
||||
}
|
||||
|
||||
@AliasForWithMirroredAliasForWrongAttribute
|
||||
static class AliasForWithMirroredAliasForWrongAttributeClass {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasForAttributeOfDifferentType {
|
||||
|
||||
@AliasFor(attribute = "bar")
|
||||
String[] foo() default "";
|
||||
|
||||
@AliasFor(attribute = "foo")
|
||||
boolean bar() default true;
|
||||
}
|
||||
|
||||
@AliasForAttributeOfDifferentType
|
||||
static class AliasForAttributeOfDifferentTypeClass {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasForWithMissingDefaultValues {
|
||||
|
||||
@AliasFor(attribute = "bar")
|
||||
String foo();
|
||||
|
||||
@AliasFor(attribute = "foo")
|
||||
String bar();
|
||||
}
|
||||
|
||||
@AliasForWithMissingDefaultValues(foo = "foo", bar = "bar")
|
||||
static class AliasForWithMissingDefaultValuesClass {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasForAttributeWithDifferentDefaultValue {
|
||||
|
||||
@AliasFor(attribute = "bar")
|
||||
String foo() default "X";
|
||||
|
||||
@AliasFor(attribute = "foo")
|
||||
String bar() default "Z";
|
||||
}
|
||||
|
||||
@AliasForAttributeWithDifferentDefaultValue
|
||||
static class AliasForAttributeWithDifferentDefaultValueClass {
|
||||
}
|
||||
|
||||
@ContextConfig
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface AliasedComposedContextConfig {
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||
String xmlConfigFile();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user