From b830d7362df24ed0953009b8172a7d67f7b73fe9 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 20 Nov 2013 21:58:36 +0100 Subject: [PATCH] Support non-public anno. attr. values in AnnoUtils Prior to this commit, the getValue(Annotation, String) method in AnnotationUtils failed to retrieve the value of the desired annotation attribute if the annotation itself was not public -- for example if the annotation was declared as package private. This commit addresses this issue by ensuring that getValue(Annotation, String) uses reflection to make the desired annotation attribute method accessible before attempting to invoke it to retrieve the value. Issue: SPR-11104 --- .../core/annotation/AnnotationUtils.java | 4 +- .../core/annotation/AnnotationUtilsTests.java | 52 ++++++++++++++----- .../subpackage/NonPublicAnnotatedClass.java | 28 ++++++++++ .../subpackage/NonPublicAnnotation.java | 32 ++++++++++++ 4 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAnnotatedClass.java create mode 100644 spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAnnotation.java diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index e86ad1c6b5..eb3595b85a 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -31,6 +31,7 @@ import java.util.WeakHashMap; import org.springframework.core.BridgeMethodResolver; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; /** * General utility methods for working with annotations, handling bridge methods (which the compiler @@ -525,6 +526,7 @@ public abstract class AnnotationUtils { public static Object getValue(Annotation annotation, String attributeName) { try { Method method = annotation.annotationType().getDeclaredMethod(attributeName, new Class[0]); + ReflectionUtils.makeAccessible(method); return method.invoke(annotation); } catch (Exception ex) { @@ -585,7 +587,6 @@ public abstract class AnnotationUtils { private static class AnnotationCollector { - private final Class containerAnnotationType; private final Class annotationType; @@ -628,6 +629,7 @@ public abstract class AnnotationUtils { private A[] getValue(Annotation annotation) { try { Method method = annotation.annotationType().getDeclaredMethod("value"); + ReflectionUtils.makeAccessible(method); return (A[]) method.invoke(annotation); } catch (Exception ex) { 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 e5ec3f1fc1..0de78f64a0 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 @@ -29,10 +29,10 @@ import java.util.Set; import org.junit.Test; import org.springframework.core.Ordered; +import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass; import org.springframework.stereotype.Component; import static org.hamcrest.Matchers.*; - import static org.junit.Assert.*; import static org.springframework.core.annotation.AnnotationUtils.*; @@ -46,7 +46,7 @@ import static org.springframework.core.annotation.AnnotationUtils.*; public class AnnotationUtilsTests { @Test - public void testFindMethodAnnotationOnLeaf() throws SecurityException, NoSuchMethodException { + public void findMethodAnnotationOnLeaf() throws Exception { Method m = Leaf.class.getMethod("annotatedOnLeaf", (Class[]) null); assertNotNull(m.getAnnotation(Order.class)); assertNotNull(getAnnotation(m, Order.class)); @@ -54,7 +54,7 @@ public class AnnotationUtilsTests { } @Test - public void testFindMethodAnnotationOnRoot() throws SecurityException, NoSuchMethodException { + public void findMethodAnnotationOnRoot() throws Exception { Method m = Leaf.class.getMethod("annotatedOnRoot", (Class[]) null); assertNotNull(m.getAnnotation(Order.class)); assertNotNull(getAnnotation(m, Order.class)); @@ -62,7 +62,7 @@ public class AnnotationUtilsTests { } @Test - public void testFindMethodAnnotationOnRootButOverridden() throws SecurityException, NoSuchMethodException { + public void findMethodAnnotationOnRootButOverridden() throws Exception { Method m = Leaf.class.getMethod("overrideWithoutNewAnnotation", (Class[]) null); assertNull(m.getAnnotation(Order.class)); assertNull(getAnnotation(m, Order.class)); @@ -70,13 +70,13 @@ public class AnnotationUtilsTests { } @Test - public void testFindMethodAnnotationNotAnnotated() throws SecurityException, NoSuchMethodException { + public void findMethodAnnotationNotAnnotated() throws Exception { Method m = Leaf.class.getMethod("notAnnotated", (Class[]) null); assertNull(findAnnotation(m, Order.class)); } @Test - public void testFindMethodAnnotationOnBridgeMethod() throws Exception { + public void findMethodAnnotationOnBridgeMethod() throws Exception { Method m = SimpleFoo.class.getMethod("something", Object.class); assertTrue(m.isBridge()); assertNull(m.getAnnotation(Order.class)); @@ -88,7 +88,7 @@ public class AnnotationUtilsTests { } // TODO consider whether we want this to handle annotations on interfaces - // public void testFindMethodAnnotationFromInterfaceImplementedByRoot() + // public void findMethodAnnotationFromInterfaceImplementedByRoot() // throws Exception { // Method m = Leaf.class.getMethod("fromInterfaceImplementedByRoot", // (Class[]) null); @@ -241,7 +241,7 @@ public class AnnotationUtilsTests { } @Test - public void testGetValueFromAnnotation() throws Exception { + public void getValueFromAnnotation() throws Exception { Method method = SimpleFoo.class.getMethod("something", Object.class); Order order = findAnnotation(method, Order.class); @@ -250,7 +250,18 @@ public class AnnotationUtilsTests { } @Test - public void testGetDefaultValueFromAnnotation() throws Exception { + public void getValueFromNonPublicAnnotation() throws Exception { + Annotation[] declaredAnnotations = NonPublicAnnotatedClass.class.getDeclaredAnnotations(); + assertEquals(1, declaredAnnotations.length); + Annotation annotation = declaredAnnotations[0]; + assertNotNull(annotation); + assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName()); + assertEquals(42, AnnotationUtils.getValue(annotation, AnnotationUtils.VALUE)); + assertEquals(42, AnnotationUtils.getValue(annotation)); + } + + @Test + public void getDefaultValueFromAnnotation() throws Exception { Method method = SimpleFoo.class.getMethod("something", Object.class); Order order = findAnnotation(method, Order.class); @@ -259,34 +270,46 @@ public class AnnotationUtilsTests { } @Test - public void testGetDefaultValueFromAnnotationType() throws Exception { + public void getDefaultValueFromNonPublicAnnotation() throws Exception { + Annotation[] declaredAnnotations = NonPublicAnnotatedClass.class.getDeclaredAnnotations(); + assertEquals(1, declaredAnnotations.length); + Annotation annotation = declaredAnnotations[0]; + assertNotNull(annotation); + assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName()); + assertEquals(-1, AnnotationUtils.getDefaultValue(annotation, AnnotationUtils.VALUE)); + assertEquals(-1, AnnotationUtils.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)); } @Test - public void testFindAnnotationFromInterface() throws Exception { + public void findAnnotationFromInterface() throws Exception { Method method = ImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo"); Order order = findAnnotation(method, Order.class); assertNotNull(order); } @Test - public void testFindAnnotationFromInterfaceOnSuper() throws Exception { + public void findAnnotationFromInterfaceOnSuper() throws Exception { Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo"); Order order = findAnnotation(method, Order.class); assertNotNull(order); } @Test - public void testFindAnnotationFromInterfaceWhenSuperDoesNotImplementMethod() throws Exception { + public void findAnnotationFromInterfaceWhenSuperDoesNotImplementMethod() + throws Exception { Method method = SubOfAbstractImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo"); Order order = findAnnotation(method, Order.class); assertNotNull(order); } @Test - public void testGetRepeatableFromMethod() throws Exception { + public void getRepeatableFromMethod() throws Exception { Method method = InterfaceWithRepeated.class.getMethod("foo"); Set annotions = AnnotationUtils.getRepeatableAnnotation(method, MyRepeatableContainer.class, MyRepeatable.class); @@ -319,6 +342,7 @@ public class AnnotationUtilsTests { static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation { } + public static interface AnnotatedInterface { @Order(0) diff --git a/spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAnnotatedClass.java b/spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAnnotatedClass.java new file mode 100644 index 0000000000..7e79084ac8 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAnnotatedClass.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2013 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.subpackage; + +/** + * Class annotated with a non-public (i.e., package private) custom annotation. + * + * @author Sam Brannen + * @since 4.0 + */ +@NonPublicAnnotation(42) +public class NonPublicAnnotatedClass { + +} diff --git a/spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAnnotation.java b/spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAnnotation.java new file mode 100644 index 0000000000..9f5e0d8892 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAnnotation.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2013 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.subpackage; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Non-public (i.e., package private) custom annotation. + * + * @author Sam Brannen + * @since 4.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@interface NonPublicAnnotation { + + int value() default -1; +}