Browse Source

Avoid reflection for annotation attribute method invocations

As a follow up to 332b25b680, this commit consistently avoids the use of
reflection for annotation attribute method invocations.

See gh-29301
Closes gh-29448
pull/29589/head
Sam Brannen 3 years ago
parent
commit
f4b3333fa8
  1. 7
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java
  2. 36
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
  3. 4
      spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java
  4. 20
      spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java
  5. 2
      spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java
  6. 16
      spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java

7
spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java

@ -32,7 +32,6 @@ import java.util.Set;
import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet; import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -241,7 +240,7 @@ final class AnnotationTypeMapping {
mapping.claimedAliases.addAll(aliases); mapping.claimedAliases.addAll(aliases);
if (mapping.annotation != null) { if (mapping.annotation != null) {
int[] resolvedMirrors = mapping.mirrorSets.resolve(null, int[] resolvedMirrors = mapping.mirrorSets.resolve(null,
mapping.annotation, ReflectionUtils::invokeMethod); mapping.annotation, AnnotationUtils::invokeAnnotationMethod);
for (int i = 0; i < mapping.attributes.size(); i++) { for (int i = 0; i < mapping.attributes.size(); i++) {
if (aliases.contains(mapping.attributes.get(i))) { if (aliases.contains(mapping.attributes.get(i))) {
this.annotationValueMappings[attributeIndex] = resolvedMirrors[i]; this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];
@ -505,7 +504,7 @@ final class AnnotationTypeMapping {
if (source == this && metaAnnotationsOnly) { if (source == this && metaAnnotationsOnly) {
return null; return null;
} }
return ReflectionUtils.invokeMethod(source.attributes.get(mappedIndex), source.annotation); return AnnotationUtils.invokeAnnotationMethod(source.attributes.get(mappedIndex), source.annotation);
} }
/** /**
@ -594,7 +593,7 @@ final class AnnotationTypeMapping {
AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType()); AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
for (int i = 0; i < attributes.size(); i++) { for (int i = 0; i < attributes.size(); i++) {
Method attribute = attributes.get(i); Method attribute = attributes.get(i);
Object value1 = ReflectionUtils.invokeMethod(attribute, annotation); Object value1 = AnnotationUtils.invokeAnnotationMethod(attribute, annotation);
Object value2; Object value2;
if (extractedValue instanceof TypeMappedAnnotation) { if (extractedValue instanceof TypeMappedAnnotation) {
value2 = ((TypeMappedAnnotation<?>) extractedValue).getValue(attribute.getName()).orElse(null); value2 = ((TypeMappedAnnotation<?>) extractedValue).getValue(attribute.getName()).orElse(null);

36
spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

@ -19,9 +19,10 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -1052,23 +1053,42 @@ public abstract class AnnotationUtils {
} }
try { try {
Method method = annotation.annotationType().getDeclaredMethod(attributeName); Method method = annotation.annotationType().getDeclaredMethod(attributeName);
ReflectionUtils.makeAccessible(method); return invokeAnnotationMethod(method, annotation);
return method.invoke(annotation);
} }
catch (NoSuchMethodException ex) { catch (NoSuchMethodException ex) {
return null; return null;
} }
catch (InvocationTargetException ex) {
rethrowAnnotationConfigurationException(ex.getTargetException());
throw new IllegalStateException("Could not obtain value for annotation attribute '" +
attributeName + "' in " + annotation, ex);
}
catch (Throwable ex) { catch (Throwable ex) {
rethrowAnnotationConfigurationException(ex);
handleIntrospectionFailure(annotation.getClass(), ex); handleIntrospectionFailure(annotation.getClass(), ex);
return null; return null;
} }
} }
/**
* Invoke the supplied annotation attribute {@link Method} on the supplied
* {@link Annotation}.
* <p>An attempt will first be made to invoke the method via the annotation's
* {@link InvocationHandler} (if the annotation instance is a JDK dynamic proxy).
* If that fails, an attempt will be made to invoke the method via reflection.
* @param method the method to invoke
* @param annotation the annotation on which to invoke the method
* @return the value returned from the method invocation
* @since 5.3.24
*/
static Object invokeAnnotationMethod(Method method, Object annotation) {
if (Proxy.isProxyClass(annotation.getClass())) {
try {
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
return handler.invoke(annotation, method, null);
}
catch (Throwable ex) {
// ignore and fall back to reflection below
}
}
return ReflectionUtils.invokeMethod(method, annotation);
}
/** /**
* If the supplied throwable is an {@link AnnotationConfigurationException}, * If the supplied throwable is an {@link AnnotationConfigurationException},
* it will be cast to an {@code AnnotationConfigurationException} and thrown, * it will be cast to an {@code AnnotationConfigurationException} and thrown,

4
spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java

@ -109,7 +109,7 @@ final class AttributeMethods {
for (int i = 0; i < size(); i++) { for (int i = 0; i < size(); i++) {
if (canThrowTypeNotPresentException(i)) { if (canThrowTypeNotPresentException(i)) {
try { try {
get(i).invoke(annotation); AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
} }
catch (Throwable ex) { catch (Throwable ex) {
return false; return false;
@ -134,7 +134,7 @@ final class AttributeMethods {
for (int i = 0; i < size(); i++) { for (int i = 0; i < size(); i++) {
if (canThrowTypeNotPresentException(i)) { if (canThrowTypeNotPresentException(i)) {
try { try {
get(i).invoke(annotation); AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
} }
catch (Throwable ex) { catch (Throwable ex) {
throw new IllegalStateException("Could not obtain annotation attribute value for " + throw new IllegalStateException("Could not obtain annotation attribute value for " +

20
spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java

@ -18,16 +18,13 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable; import java.lang.annotation.Repeatable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map; import java.util.Map;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/** /**
* Strategy used to determine annotations that act as containers for other * Strategy used to determine annotations that act as containers for other
@ -133,19 +130,6 @@ public abstract class RepeatableContainers {
return NoRepeatableContainers.INSTANCE; return NoRepeatableContainers.INSTANCE;
} }
private static Object invokeAnnotationMethod(Annotation annotation, Method method) {
if (Proxy.isProxyClass(annotation.getClass())) {
try {
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
return handler.invoke(annotation, method, null);
}
catch (Throwable ex) {
// ignore and fall back to reflection below
}
}
return ReflectionUtils.invokeMethod(method, annotation);
}
/** /**
* Standard {@link RepeatableContainers} implementation that searches using * Standard {@link RepeatableContainers} implementation that searches using
@ -168,7 +152,7 @@ public abstract class RepeatableContainers {
Annotation[] findRepeatedAnnotations(Annotation annotation) { Annotation[] findRepeatedAnnotations(Annotation annotation) {
Method method = getRepeatedAnnotationsMethod(annotation.annotationType()); Method method = getRepeatedAnnotationsMethod(annotation.annotationType());
if (method != null) { if (method != null) {
return (Annotation[]) invokeAnnotationMethod(annotation, method); return (Annotation[]) AnnotationUtils.invokeAnnotationMethod(method, annotation);
} }
return super.findRepeatedAnnotations(annotation); return super.findRepeatedAnnotations(annotation);
} }
@ -255,7 +239,7 @@ public abstract class RepeatableContainers {
@Nullable @Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) { Annotation[] findRepeatedAnnotations(Annotation annotation) {
if (this.container.isAssignableFrom(annotation.annotationType())) { if (this.container.isAssignableFrom(annotation.annotationType())) {
return (Annotation[]) invokeAnnotationMethod(annotation, this.valueMethod); return (Annotation[]) AnnotationUtils.invokeAnnotationMethod(this.valueMethod, annotation);
} }
return super.findRepeatedAnnotations(annotation); return super.findRepeatedAnnotations(annotation);
} }

2
spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java

@ -111,7 +111,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
for (int i = 0; i < this.attributes.size(); i++) { for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i); Method attribute = this.attributes.get(i);
Object thisValue = getAttributeValue(attribute); Object thisValue = getAttributeValue(attribute);
Object otherValue = ReflectionUtils.invokeMethod(attribute, other); Object otherValue = AnnotationUtils.invokeAnnotationMethod(attribute, other);
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) { if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
return false; return false;
} }

16
spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java

@ -33,7 +33,6 @@ import java.util.function.Predicate;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/** /**
* {@link MergedAnnotation} that adapts attributes from a root annotation by * {@link MergedAnnotation} that adapts attributes from a root annotation by
@ -43,9 +42,9 @@ import org.springframework.util.ReflectionUtils;
* {@code BiFunction}. This allows various different annotation models to be * {@code BiFunction}. This allows various different annotation models to be
* supported by the same class. For example, the attributes source might be an * supported by the same class. For example, the attributes source might be an
* actual {@link Annotation} instance where methods on the annotation instance * actual {@link Annotation} instance where methods on the annotation instance
* are {@linkplain ReflectionUtils#invokeMethod(Method, Object) invoked} to extract * are {@linkplain AnnotationUtils#invokeAnnotationMethod(Method, Object) invoked}
* values. Equally, the source could be a simple {@link Map} with values * to extract values. Similarly, the source could be a simple {@link Map} with
* extracted using {@link Map#get(Object)}. * values extracted using {@link Map#get(Object)}.
* *
* <p>Extracted root attribute values must be compatible with the attribute * <p>Extracted root attribute values must be compatible with the attribute
* return type, namely: * return type, namely:
@ -434,7 +433,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
} }
if (value == null) { if (value == null) {
Method attribute = this.mapping.getAttributes().get(attributeIndex); Method attribute = this.mapping.getAttributes().get(attributeIndex);
value = ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation()); value = AnnotationUtils.invokeAnnotationMethod(attribute, this.mapping.getAnnotation());
} }
return value; return value;
} }
@ -555,7 +554,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
private ValueExtractor getValueExtractor(Object value) { private ValueExtractor getValueExtractor(Object value) {
if (value instanceof Annotation) { if (value instanceof Annotation) {
return ReflectionUtils::invokeMethod; return AnnotationUtils::invokeAnnotationMethod;
} }
if (value instanceof Map) { if (value instanceof Map) {
return TypeMappedAnnotation::extractFromMap; return TypeMappedAnnotation::extractFromMap;
@ -615,7 +614,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source, A annotation) { static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source, A annotation) {
Assert.notNull(annotation, "Annotation must not be null"); Assert.notNull(annotation, "Annotation must not be null");
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotation.annotationType()); AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotation.annotationType());
return new TypeMappedAnnotation<>(mappings.get(0), null, source, annotation, ReflectionUtils::invokeMethod, 0); return new TypeMappedAnnotation<>(
mappings.get(0), null, source, annotation, AnnotationUtils::invokeAnnotationMethod, 0);
} }
static <A extends Annotation> MergedAnnotation<A> of( static <A extends Annotation> MergedAnnotation<A> of(
@ -649,7 +649,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
int aggregateIndex, IntrospectionFailureLogger logger) { int aggregateIndex, IntrospectionFailureLogger logger) {
return createIfPossible(mapping, source, annotation, return createIfPossible(mapping, source, annotation,
ReflectionUtils::invokeMethod, aggregateIndex, logger); AnnotationUtils::invokeAnnotationMethod, aggregateIndex, logger);
} }
@Nullable @Nullable

Loading…
Cancel
Save