diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java new file mode 100644 index 000000000000..0c359da919c8 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2014 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.core.annotation; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Unit tests for {@link AnnotatedElementUtils}. + * + * @author Sam Brannen + * @since 4.0.3 + */ +public class AnnotatedElementUtilsTests { + + @Test + public void getAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { + AnnotationAttributes attributes = AnnotatedElementUtils.getAnnotationAttributes( + SubSubClassWithInheritedAnnotation.class, Transactional.class.getName()); + assertNotNull(attributes); + + // By inspecting SubSubClassWithInheritedAnnotation, one might expect that the + // readOnly flag should be true, since the immediate superclass is annotated with + // @Composed2; however, with the current implementation the readOnly flag will be + // false since @Transactional is declared as @Inherited. + assertFalse("readOnly flag for SubSubClassWithInheritedAnnotation", attributes.getBoolean("readOnly")); + } + + @Test + public void getAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { + AnnotationAttributes attributes = AnnotatedElementUtils.getAnnotationAttributes( + SubSubClassWithInheritedComposedAnnotation.class, Transactional.class.getName()); + assertNotNull(attributes); + + // By inspecting SubSubClassWithInheritedComposedAnnotation, one might expect that + // the readOnly flag should be true, since the immediate superclass is annotated + // with @Composed2; however, with the current implementation the readOnly flag + // will be false since @Composed1 is declared as @Inherited. + assertFalse("readOnly flag", attributes.getBoolean("readOnly")); + } + + + // ------------------------------------------------------------------------- + + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface Transactional { + + boolean readOnly() default false; + } + + @Transactional + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface Composed1 { + } + + @Transactional(readOnly = true) + @Retention(RetentionPolicy.RUNTIME) + @interface Composed2 { + } + + @Transactional + static class ClassWithInheritedAnnotation { + } + + @Composed2 + static class SubClassWithInheritedAnnotation extends ClassWithInheritedAnnotation { + } + + static class SubSubClassWithInheritedAnnotation extends SubClassWithInheritedAnnotation { + } + + @Composed1 + static class ClassWithInheritedComposedAnnotation { + } + + @Composed2 + static class SubClassWithInheritedComposedAnnotation extends ClassWithInheritedComposedAnnotation { + } + + static class SubSubClassWithInheritedComposedAnnotation extends SubClassWithInheritedComposedAnnotation { + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index 4623f648624f..c4a7a696c28d 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -100,14 +100,40 @@ public void findMethodAnnotationOnBridgeMethod() throws Exception { // } @Test - public void findAnnotationPrefersInterfacesOverLocalMetaAnnotations() { + public void findAnnotationFavorsInterfacesOverLocalMetaAnnotations() { Component component = AnnotationUtils.findAnnotation( - ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class); + ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class); + assertNotNull(component); // By inspecting ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface, one // might expect that "meta2" should be found; however, with the current // implementation "meta1" will be found. + assertEquals("meta1", component.value()); + } + + @Test + public void findAnnotationFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { + Transactional transactional = AnnotationUtils.findAnnotation(SubSubClassWithInheritedAnnotation.class, + Transactional.class); + assertNotNull(transactional); + + // By inspecting SubSubClassWithInheritedAnnotation, one might expect that the + // readOnly flag should be true, since the immediate superclass is annotated with + // @Composed2; however, with the current implementation the readOnly flag will be + // false since @Transactional is declared as @Inherited. + assertFalse("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly()); + } + + @Test + public void findAnnotationFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { + Component component = AnnotationUtils.findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, + Component.class); assertNotNull(component); + + // By inspecting SubSubClassWithInheritedMetaAnnotation, one might expect that + // "meta2" should be found, since the immediate superclass is annotated with + // @Meta2; however, with the current implementation "meta1" will be found since + // @Meta1 is declared as @Inherited. assertEquals("meta1", component.value()); } @@ -350,14 +376,15 @@ public void getRepeatableFromMethod() throws Exception { } - @Component(value="meta1") + @Component(value = "meta1") @Order @Retention(RetentionPolicy.RUNTIME) + @Inherited @interface Meta1 { } - @Component(value="meta2") - @Transactional + @Component(value = "meta2") + @Transactional(readOnly = true) @Retention(RetentionPolicy.RUNTIME) @interface Meta2 { } @@ -395,6 +422,28 @@ static interface InterfaceWithMetaAnnotation { static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation { } + @Meta1 + static class ClassWithInheritedMetaAnnotation { + } + + @Meta2 + static class SubClassWithInheritedMetaAnnotation extends ClassWithInheritedMetaAnnotation { + } + + static class SubSubClassWithInheritedMetaAnnotation extends SubClassWithInheritedMetaAnnotation { + } + + @Transactional + static class ClassWithInheritedAnnotation { + } + + @Meta2 + static class SubClassWithInheritedAnnotation extends ClassWithInheritedAnnotation { + } + + static class SubSubClassWithInheritedAnnotation extends SubClassWithInheritedAnnotation { + } + @MetaMeta static class MetaMetaAnnotatedClass { } @@ -453,6 +502,8 @@ public void overrideWithoutNewAnnotation() { @Retention(RetentionPolicy.RUNTIME) @Inherited @interface Transactional { + + boolean readOnly() default false; } public static abstract class Foo {