mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:30:28 +00:00
Consistently ignore non-loadable annotation types with ClassFile/ASM
Uses ClassFileAnnotationMetadata name for actual AnnotationMetadata. Moves JSR-305 dependency to compile-only for all spring-core tests. Closes gh-36432
This commit is contained in:
@@ -92,7 +92,7 @@ dependencies {
|
|||||||
optional("org.jetbrains.kotlin:kotlin-stdlib")
|
optional("org.jetbrains.kotlin:kotlin-stdlib")
|
||||||
optional("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
optional("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||||
optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||||
testCompileOnly("com.github.ben-manes.caffeine:caffeine")
|
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||||
testFixturesImplementation("io.projectreactor:reactor-test")
|
testFixturesImplementation("io.projectreactor:reactor-test")
|
||||||
testFixturesImplementation("org.assertj:assertj-core")
|
testFixturesImplementation("org.assertj:assertj-core")
|
||||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter")
|
testFixturesImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
@@ -100,7 +100,6 @@ dependencies {
|
|||||||
testFixturesImplementation("org.xmlunit:xmlunit-assertj")
|
testFixturesImplementation("org.xmlunit:xmlunit-assertj")
|
||||||
testImplementation("com.fasterxml.jackson.core:jackson-databind")
|
testImplementation("com.fasterxml.jackson.core:jackson-databind")
|
||||||
testImplementation("com.fasterxml.woodstox:woodstox-core")
|
testImplementation("com.fasterxml.woodstox:woodstox-core")
|
||||||
testImplementation("com.google.code.findbugs:jsr305")
|
|
||||||
testImplementation("com.squareup.okhttp3:mockwebserver3")
|
testImplementation("com.squareup.okhttp3:mockwebserver3")
|
||||||
testImplementation("io.projectreactor:reactor-test")
|
testImplementation("io.projectreactor:reactor-test")
|
||||||
testImplementation("io.projectreactor.tools:blockhound")
|
testImplementation("io.projectreactor.tools:blockhound")
|
||||||
|
|||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-present 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
|
||||||
|
*
|
||||||
|
* https://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.type.classreading;
|
||||||
|
|
||||||
|
import java.lang.classfile.Annotation;
|
||||||
|
import java.lang.classfile.AnnotationElement;
|
||||||
|
import java.lang.classfile.AnnotationValue;
|
||||||
|
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
||||||
|
import java.lang.constant.ClassDesc;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationFilter;
|
||||||
|
import org.springframework.core.annotation.MergedAnnotation;
|
||||||
|
import org.springframework.core.annotation.MergedAnnotations;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse {@link RuntimeVisibleAnnotationsAttribute} into {@link MergedAnnotations}
|
||||||
|
* instances.
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
* @author Juergen Hoeller
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
abstract class ClassFileAnnotationDelegate {
|
||||||
|
|
||||||
|
static MergedAnnotations createMergedAnnotations(
|
||||||
|
String className, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) {
|
||||||
|
|
||||||
|
Set<MergedAnnotation<?>> annotations = annotationAttribute.annotations()
|
||||||
|
.stream()
|
||||||
|
.map(ann -> createMergedAnnotation(className, ann, classLoader))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
return MergedAnnotations.of(annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <A extends java.lang.annotation.Annotation> @Nullable MergedAnnotation<A> createMergedAnnotation(
|
||||||
|
String className, Annotation annotation, @Nullable ClassLoader classLoader) {
|
||||||
|
|
||||||
|
String typeName = fromTypeDescriptor(annotation.className().stringValue());
|
||||||
|
if (AnnotationFilter.PLAIN.matches(typeName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Fail early when annotation type is not loadable (before resolving annotation values)
|
||||||
|
Class<A> annotationType = (Class<A>) ClassUtils.forName(typeName, classLoader);
|
||||||
|
Map<String, Object> attributes = new LinkedHashMap<>(4);
|
||||||
|
for (AnnotationElement element : annotation.elements()) {
|
||||||
|
Object annotationValue = readAnnotationValue(className, element.value(), classLoader);
|
||||||
|
if (annotationValue != null) {
|
||||||
|
attributes.put(element.name().stringValue(), annotationValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, Object> compactedAttributes = (attributes.isEmpty() ? Collections.emptyMap() : attributes);
|
||||||
|
return MergedAnnotation.of(classLoader, new Source(annotation), annotationType, compactedAttributes);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException | LinkageError ex) {
|
||||||
|
// Non-loadable annotation type -> ignore.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable Object readAnnotationValue(
|
||||||
|
String className, AnnotationValue elementValue, @Nullable ClassLoader classLoader) {
|
||||||
|
|
||||||
|
switch (elementValue) {
|
||||||
|
case AnnotationValue.OfConstant constantValue -> {
|
||||||
|
return constantValue.resolvedValue();
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfAnnotation annotationValue -> {
|
||||||
|
return createMergedAnnotation(className, annotationValue.annotation(), classLoader);
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfClass classValue -> {
|
||||||
|
return fromTypeDescriptor(classValue.className().stringValue());
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfEnum enumValue -> {
|
||||||
|
return parseEnum(enumValue, classLoader);
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfArray arrayValue -> {
|
||||||
|
return parseArrayValue(className, classLoader, arrayValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fromTypeDescriptor(String descriptor) {
|
||||||
|
ClassDesc classDesc = ClassDesc.ofDescriptor(descriptor);
|
||||||
|
return (classDesc.isPrimitive() ? classDesc.displayName() :
|
||||||
|
classDesc.packageName() + "." + classDesc.displayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
|
||||||
|
if (arrayValue.values().isEmpty()) {
|
||||||
|
return new Object[0];
|
||||||
|
}
|
||||||
|
Stream<AnnotationValue> stream = arrayValue.values().stream();
|
||||||
|
switch (arrayValue.values().getFirst()) {
|
||||||
|
case AnnotationValue.OfInt _ -> {
|
||||||
|
return stream.map(AnnotationValue.OfInt.class::cast).mapToInt(AnnotationValue.OfInt::intValue).toArray();
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfDouble _ -> {
|
||||||
|
return stream.map(AnnotationValue.OfDouble.class::cast).mapToDouble(AnnotationValue.OfDouble::doubleValue).toArray();
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfLong _ -> {
|
||||||
|
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
|
||||||
|
return stream
|
||||||
|
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader))
|
||||||
|
.toArray(length -> (Object[]) Array.newInstance(arrayElementType, length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <E extends Enum<E>> Enum<E> parseEnum(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
|
||||||
|
Class<E> enumClass = (Class<E>) loadEnumClass(enumValue, classLoader);
|
||||||
|
return Enum.valueOf(enumClass, enumValue.constantName().stringValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> loadEnumClass(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
|
||||||
|
String className = fromTypeDescriptor(enumValue.className().stringValue());
|
||||||
|
return ClassUtils.resolveClassName(className, classLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
|
||||||
|
AnnotationValue firstValue = values.getFirst();
|
||||||
|
switch (firstValue) {
|
||||||
|
case AnnotationValue.OfConstant constantValue -> {
|
||||||
|
return constantValue.resolvedValue().getClass();
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfAnnotation _ -> {
|
||||||
|
return MergedAnnotation.class;
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfClass _ -> {
|
||||||
|
return String.class;
|
||||||
|
}
|
||||||
|
case AnnotationValue.OfEnum enumValue -> {
|
||||||
|
return loadEnumClass(enumValue, classLoader);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
return Object.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
record Source(Annotation entryName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+263
-134
@@ -16,165 +16,294 @@
|
|||||||
|
|
||||||
package org.springframework.core.type.classreading;
|
package org.springframework.core.type.classreading;
|
||||||
|
|
||||||
import java.lang.classfile.Annotation;
|
import java.lang.classfile.AccessFlags;
|
||||||
import java.lang.classfile.AnnotationElement;
|
import java.lang.classfile.ClassModel;
|
||||||
import java.lang.classfile.AnnotationValue;
|
import java.lang.classfile.Interfaces;
|
||||||
|
import java.lang.classfile.MethodModel;
|
||||||
|
import java.lang.classfile.Superclass;
|
||||||
|
import java.lang.classfile.attribute.InnerClassInfo;
|
||||||
|
import java.lang.classfile.attribute.InnerClassesAttribute;
|
||||||
|
import java.lang.classfile.attribute.NestHostAttribute;
|
||||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
||||||
import java.lang.constant.ClassDesc;
|
import java.lang.classfile.constantpool.ClassEntry;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.AccessFlag;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.annotation.AnnotationFilter;
|
|
||||||
import org.springframework.core.annotation.MergedAnnotation;
|
|
||||||
import org.springframework.core.annotation.MergedAnnotations;
|
import org.springframework.core.annotation.MergedAnnotations;
|
||||||
|
import org.springframework.core.type.AnnotationMetadata;
|
||||||
|
import org.springframework.core.type.MethodMetadata;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse {@link RuntimeVisibleAnnotationsAttribute} into {@link MergedAnnotations}
|
* {@link AnnotationMetadata} implementation that leverages
|
||||||
* instances.
|
* the {@link java.lang.classfile.ClassFile} API.
|
||||||
*
|
*
|
||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
|
* @author Juergen Hoeller
|
||||||
* @since 7.0
|
* @since 7.0
|
||||||
*/
|
*/
|
||||||
abstract class ClassFileAnnotationMetadata {
|
final class ClassFileAnnotationMetadata implements AnnotationMetadata {
|
||||||
|
|
||||||
static MergedAnnotations createMergedAnnotations(
|
private final String className;
|
||||||
String className, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) {
|
|
||||||
|
|
||||||
Set<MergedAnnotation<?>> annotations = annotationAttribute.annotations()
|
private final AccessFlags accessFlags;
|
||||||
.stream()
|
|
||||||
.map(ann -> createMergedAnnotation(className, ann, classLoader))
|
private final @Nullable String enclosingClassName;
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toSet());
|
private final @Nullable String superClassName;
|
||||||
return MergedAnnotations.of(annotations);
|
|
||||||
|
private final boolean independentInnerClass;
|
||||||
|
|
||||||
|
private final Set<String> interfaceNames;
|
||||||
|
|
||||||
|
private final Set<String> memberClassNames;
|
||||||
|
|
||||||
|
private final Set<MethodMetadata> declaredMethods;
|
||||||
|
|
||||||
|
private final MergedAnnotations mergedAnnotations;
|
||||||
|
|
||||||
|
private @Nullable Set<String> annotationTypes;
|
||||||
|
|
||||||
|
|
||||||
|
ClassFileAnnotationMetadata(String className, AccessFlags accessFlags, @Nullable String enclosingClassName,
|
||||||
|
@Nullable String superClassName, boolean independentInnerClass, Set<String> interfaceNames,
|
||||||
|
Set<String> memberClassNames, Set<MethodMetadata> declaredMethods, MergedAnnotations mergedAnnotations) {
|
||||||
|
|
||||||
|
this.className = className;
|
||||||
|
this.accessFlags = accessFlags;
|
||||||
|
this.enclosingClassName = enclosingClassName;
|
||||||
|
this.superClassName = (!className.endsWith(".package-info")) ? superClassName : null;
|
||||||
|
this.independentInnerClass = independentInnerClass;
|
||||||
|
this.interfaceNames = interfaceNames;
|
||||||
|
this.memberClassNames = memberClassNames;
|
||||||
|
this.declaredMethods = declaredMethods;
|
||||||
|
this.mergedAnnotations = mergedAnnotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <A extends java.lang.annotation.Annotation> @Nullable MergedAnnotation<A> createMergedAnnotation(
|
|
||||||
String className, Annotation annotation, @Nullable ClassLoader classLoader) {
|
|
||||||
|
|
||||||
String typeName = fromTypeDescriptor(annotation.className().stringValue());
|
@Override
|
||||||
if (AnnotationFilter.PLAIN.matches(typeName)) {
|
public String getClassName() {
|
||||||
return null;
|
return this.className;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInterface() {
|
||||||
|
return this.accessFlags.has(AccessFlag.INTERFACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAnnotation() {
|
||||||
|
return this.accessFlags.has(AccessFlag.ANNOTATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAbstract() {
|
||||||
|
return this.accessFlags.has(AccessFlag.ABSTRACT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinal() {
|
||||||
|
return this.accessFlags.has(AccessFlag.FINAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isIndependent() {
|
||||||
|
return (this.enclosingClassName == null || this.independentInnerClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getEnclosingClassName() {
|
||||||
|
return this.enclosingClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getSuperClassName() {
|
||||||
|
return this.superClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getInterfaceNames() {
|
||||||
|
return StringUtils.toStringArray(this.interfaceNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getMemberClassNames() {
|
||||||
|
return StringUtils.toStringArray(this.memberClassNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MergedAnnotations getAnnotations() {
|
||||||
|
return this.mergedAnnotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getAnnotationTypes() {
|
||||||
|
Set<String> annotationTypes = this.annotationTypes;
|
||||||
|
if (annotationTypes == null) {
|
||||||
|
annotationTypes = Collections.unmodifiableSet(
|
||||||
|
AnnotationMetadata.super.getAnnotationTypes());
|
||||||
|
this.annotationTypes = annotationTypes;
|
||||||
}
|
}
|
||||||
Map<String, Object> attributes = new LinkedHashMap<>(4);
|
return annotationTypes;
|
||||||
try {
|
}
|
||||||
for (AnnotationElement element : annotation.elements()) {
|
|
||||||
Object annotationValue = readAnnotationValue(className, element.value(), classLoader);
|
@Override
|
||||||
if (annotationValue != null) {
|
public Set<MethodMetadata> getAnnotatedMethods(String annotationName) {
|
||||||
attributes.put(element.name().stringValue(), annotationValue);
|
Set<MethodMetadata> result = new LinkedHashSet<>(4);
|
||||||
|
for (MethodMetadata annotatedMethod : this.declaredMethods) {
|
||||||
|
if (annotatedMethod.isAnnotated(annotationName)) {
|
||||||
|
result.add(annotatedMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableSet(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<MethodMetadata> getDeclaredMethods() {
|
||||||
|
return Collections.unmodifiableSet(this.declaredMethods);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object other) {
|
||||||
|
return (this == other || (other instanceof ClassFileAnnotationMetadata that && this.className.equals(that.className)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.className.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.className;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ClassFileAnnotationMetadata of(ClassModel classModel, @Nullable ClassLoader classLoader) {
|
||||||
|
Builder builder = new Builder(classLoader);
|
||||||
|
builder.classEntry(classModel.thisClass());
|
||||||
|
String currentClassName = classModel.thisClass().name().stringValue();
|
||||||
|
classModel.elementStream().forEach(classElement -> {
|
||||||
|
switch (classElement) {
|
||||||
|
case AccessFlags flags -> {
|
||||||
|
builder.accessFlags(flags);
|
||||||
|
}
|
||||||
|
case NestHostAttribute _ -> {
|
||||||
|
builder.enclosingClass(classModel.thisClass());
|
||||||
|
}
|
||||||
|
case InnerClassesAttribute innerClasses -> {
|
||||||
|
builder.nestMembers(currentClassName, innerClasses);
|
||||||
|
}
|
||||||
|
case RuntimeVisibleAnnotationsAttribute annotationsAttribute -> {
|
||||||
|
builder.mergedAnnotations(ClassFileAnnotationDelegate.createMergedAnnotations(
|
||||||
|
ClassUtils.convertResourcePathToClassName(currentClassName), annotationsAttribute, classLoader));
|
||||||
|
}
|
||||||
|
case Superclass superclass -> {
|
||||||
|
builder.superClass(superclass);
|
||||||
|
}
|
||||||
|
case Interfaces interfaces -> {
|
||||||
|
builder.interfaces(interfaces);
|
||||||
|
}
|
||||||
|
case MethodModel method -> {
|
||||||
|
builder.method(method);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
// ignore class element
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map<String, Object> compactedAttributes = (attributes.isEmpty() ? Collections.emptyMap() : attributes);
|
});
|
||||||
Class<A> annotationType = (Class<A>) ClassUtils.forName(typeName, classLoader);
|
return builder.build();
|
||||||
return MergedAnnotation.of(classLoader, new Source(annotation), annotationType, compactedAttributes);
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException | LinkageError ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @Nullable Object readAnnotationValue(
|
|
||||||
String className, AnnotationValue elementValue, @Nullable ClassLoader classLoader) {
|
|
||||||
|
|
||||||
switch (elementValue) {
|
|
||||||
case AnnotationValue.OfConstant constantValue -> {
|
|
||||||
return constantValue.resolvedValue();
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfAnnotation annotationValue -> {
|
|
||||||
return createMergedAnnotation(className, annotationValue.annotation(), classLoader);
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfClass classValue -> {
|
|
||||||
return fromTypeDescriptor(classValue.className().stringValue());
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfEnum enumValue -> {
|
|
||||||
return parseEnum(enumValue, classLoader);
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfArray arrayValue -> {
|
|
||||||
return parseArrayValue(className, classLoader, arrayValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String fromTypeDescriptor(String descriptor) {
|
|
||||||
ClassDesc classDesc = ClassDesc.ofDescriptor(descriptor);
|
|
||||||
return classDesc.isPrimitive() ? classDesc.displayName() :
|
|
||||||
classDesc.packageName() + "." + classDesc.displayName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?> loadClass(String className, @Nullable ClassLoader classLoader) {
|
|
||||||
String name = fromTypeDescriptor(className);
|
|
||||||
return ClassUtils.resolveClassName(name, classLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
|
|
||||||
if (arrayValue.values().isEmpty()) {
|
|
||||||
return new Object[0];
|
|
||||||
}
|
|
||||||
Stream<AnnotationValue> stream = arrayValue.values().stream();
|
|
||||||
switch (arrayValue.values().getFirst()) {
|
|
||||||
case AnnotationValue.OfInt _ -> {
|
|
||||||
return stream.map(AnnotationValue.OfInt.class::cast).mapToInt(AnnotationValue.OfInt::intValue).toArray();
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfDouble _ -> {
|
|
||||||
return stream.map(AnnotationValue.OfDouble.class::cast).mapToDouble(AnnotationValue.OfDouble::doubleValue).toArray();
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfLong _ -> {
|
|
||||||
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
|
|
||||||
return stream
|
|
||||||
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader))
|
|
||||||
.toArray(s -> (Object[]) Array.newInstance(arrayElementType, s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static @Nullable <E extends Enum<E>> Enum<E> parseEnum(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
|
|
||||||
String enumClassName = fromTypeDescriptor(enumValue.className().stringValue());
|
|
||||||
try {
|
|
||||||
Class<E> enumClass = (Class<E>) ClassUtils.forName(enumClassName, classLoader);
|
|
||||||
return Enum.valueOf(enumClass, enumValue.constantName().stringValue());
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException | LinkageError ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
|
|
||||||
AnnotationValue firstValue = values.getFirst();
|
|
||||||
switch (firstValue) {
|
|
||||||
case AnnotationValue.OfConstant constantValue -> {
|
|
||||||
return constantValue.resolvedValue().getClass();
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfAnnotation _ -> {
|
|
||||||
return MergedAnnotation.class;
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfClass _ -> {
|
|
||||||
return String.class;
|
|
||||||
}
|
|
||||||
case AnnotationValue.OfEnum enumValue -> {
|
|
||||||
return loadClass(enumValue.className().stringValue(), classLoader);
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
return Object.class;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
record Source(Annotation entryName) {
|
static class Builder {
|
||||||
|
|
||||||
|
private final ClassLoader classLoader;
|
||||||
|
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
private AccessFlags accessFlags;
|
||||||
|
|
||||||
|
private Set<AccessFlag> innerAccessFlags;
|
||||||
|
|
||||||
|
private @Nullable String enclosingClassName;
|
||||||
|
|
||||||
|
private @Nullable String superClassName;
|
||||||
|
|
||||||
|
private Set<String> interfaceNames = new LinkedHashSet<>(4);
|
||||||
|
|
||||||
|
private Set<String> memberClassNames = new LinkedHashSet<>(4);
|
||||||
|
|
||||||
|
private Set<MethodMetadata> declaredMethods = new LinkedHashSet<>(4);
|
||||||
|
|
||||||
|
private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet());
|
||||||
|
|
||||||
|
public Builder(ClassLoader classLoader) {
|
||||||
|
this.classLoader = classLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void classEntry(ClassEntry classEntry) {
|
||||||
|
this.className = ClassUtils.convertResourcePathToClassName(classEntry.name().stringValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
void accessFlags(AccessFlags accessFlags) {
|
||||||
|
this.accessFlags = accessFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enclosingClass(ClassEntry thisClass) {
|
||||||
|
String thisClassName = thisClass.name().stringValue();
|
||||||
|
int currentClassIndex = thisClassName.lastIndexOf('$');
|
||||||
|
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(thisClassName.substring(0, currentClassIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
void superClass(Superclass superClass) {
|
||||||
|
this.superClassName = ClassUtils.convertResourcePathToClassName(superClass.superclassEntry().name().stringValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
void interfaces(Interfaces interfaces) {
|
||||||
|
for (ClassEntry entry : interfaces.interfaces()) {
|
||||||
|
this.interfaceNames.add(ClassUtils.convertResourcePathToClassName(entry.name().stringValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nestMembers(String currentClassName, InnerClassesAttribute innerClasses) {
|
||||||
|
for (InnerClassInfo classInfo : innerClasses.classes()) {
|
||||||
|
String innerClassName = classInfo.innerClass().name().stringValue();
|
||||||
|
if (currentClassName.equals(innerClassName)) {
|
||||||
|
// the current class is an inner class
|
||||||
|
this.innerAccessFlags = classInfo.flags();
|
||||||
|
}
|
||||||
|
classInfo.outerClass().ifPresent(outerClass -> {
|
||||||
|
if (outerClass.name().stringValue().equals(currentClassName)) {
|
||||||
|
// collecting data about actual inner classes
|
||||||
|
this.memberClassNames.add(ClassUtils.convertResourcePathToClassName(innerClassName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mergedAnnotations(MergedAnnotations mergedAnnotations) {
|
||||||
|
this.mergedAnnotations = mergedAnnotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
void method(MethodModel method) {
|
||||||
|
ClassFileMethodMetadata classFileMethodMetadata = ClassFileMethodMetadata.of(method, this.classLoader);
|
||||||
|
if (!classFileMethodMetadata.isSynthetic() && !classFileMethodMetadata.isDefaultConstructor()) {
|
||||||
|
this.declaredMethods.add(classFileMethodMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassFileAnnotationMetadata build() {
|
||||||
|
boolean independentInnerClass = (this.enclosingClassName != null) && this.innerAccessFlags.contains(AccessFlag.STATIC);
|
||||||
|
return new ClassFileAnnotationMetadata(this.className, this.accessFlags, this.enclosingClassName, this.superClassName,
|
||||||
|
independentInnerClass, this.interfaceNames, this.memberClassNames, this.declaredMethods, this.mergedAnnotations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-308
@@ -1,308 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2002-present 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
|
|
||||||
*
|
|
||||||
* https://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.type.classreading;
|
|
||||||
|
|
||||||
import java.lang.classfile.AccessFlags;
|
|
||||||
import java.lang.classfile.ClassModel;
|
|
||||||
import java.lang.classfile.Interfaces;
|
|
||||||
import java.lang.classfile.MethodModel;
|
|
||||||
import java.lang.classfile.Superclass;
|
|
||||||
import java.lang.classfile.attribute.InnerClassInfo;
|
|
||||||
import java.lang.classfile.attribute.InnerClassesAttribute;
|
|
||||||
import java.lang.classfile.attribute.NestHostAttribute;
|
|
||||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
|
||||||
import java.lang.classfile.constantpool.ClassEntry;
|
|
||||||
import java.lang.reflect.AccessFlag;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
import org.springframework.core.annotation.MergedAnnotations;
|
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
|
||||||
import org.springframework.core.type.MethodMetadata;
|
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link AnnotationMetadata} implementation that leverages
|
|
||||||
* the {@link java.lang.classfile.ClassFile} API.
|
|
||||||
*
|
|
||||||
* @author Brian Clozel
|
|
||||||
* @since 7.0
|
|
||||||
*/
|
|
||||||
class ClassFileClassMetadata implements AnnotationMetadata {
|
|
||||||
|
|
||||||
private final String className;
|
|
||||||
|
|
||||||
private final AccessFlags accessFlags;
|
|
||||||
|
|
||||||
private final @Nullable String enclosingClassName;
|
|
||||||
|
|
||||||
private final @Nullable String superClassName;
|
|
||||||
|
|
||||||
private final boolean independentInnerClass;
|
|
||||||
|
|
||||||
private final Set<String> interfaceNames;
|
|
||||||
|
|
||||||
private final Set<String> memberClassNames;
|
|
||||||
|
|
||||||
private final Set<MethodMetadata> declaredMethods;
|
|
||||||
|
|
||||||
private final MergedAnnotations mergedAnnotations;
|
|
||||||
|
|
||||||
private @Nullable Set<String> annotationTypes;
|
|
||||||
|
|
||||||
|
|
||||||
ClassFileClassMetadata(String className, AccessFlags accessFlags, @Nullable String enclosingClassName,
|
|
||||||
@Nullable String superClassName, boolean independentInnerClass, Set<String> interfaceNames,
|
|
||||||
Set<String> memberClassNames, Set<MethodMetadata> declaredMethods, MergedAnnotations mergedAnnotations) {
|
|
||||||
|
|
||||||
this.className = className;
|
|
||||||
this.accessFlags = accessFlags;
|
|
||||||
this.enclosingClassName = enclosingClassName;
|
|
||||||
this.superClassName = (!className.endsWith(".package-info")) ? superClassName : null;
|
|
||||||
this.independentInnerClass = independentInnerClass;
|
|
||||||
this.interfaceNames = interfaceNames;
|
|
||||||
this.memberClassNames = memberClassNames;
|
|
||||||
this.declaredMethods = declaredMethods;
|
|
||||||
this.mergedAnnotations = mergedAnnotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getClassName() {
|
|
||||||
return this.className;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isInterface() {
|
|
||||||
return this.accessFlags.has(AccessFlag.INTERFACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAnnotation() {
|
|
||||||
return this.accessFlags.has(AccessFlag.ANNOTATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAbstract() {
|
|
||||||
return this.accessFlags.has(AccessFlag.ABSTRACT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFinal() {
|
|
||||||
return this.accessFlags.has(AccessFlag.FINAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isIndependent() {
|
|
||||||
return (this.enclosingClassName == null || this.independentInnerClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable String getEnclosingClassName() {
|
|
||||||
return this.enclosingClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable String getSuperClassName() {
|
|
||||||
return this.superClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getInterfaceNames() {
|
|
||||||
return StringUtils.toStringArray(this.interfaceNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getMemberClassNames() {
|
|
||||||
return StringUtils.toStringArray(this.memberClassNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MergedAnnotations getAnnotations() {
|
|
||||||
return this.mergedAnnotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getAnnotationTypes() {
|
|
||||||
Set<String> annotationTypes = this.annotationTypes;
|
|
||||||
if (annotationTypes == null) {
|
|
||||||
annotationTypes = Collections.unmodifiableSet(
|
|
||||||
AnnotationMetadata.super.getAnnotationTypes());
|
|
||||||
this.annotationTypes = annotationTypes;
|
|
||||||
}
|
|
||||||
return annotationTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<MethodMetadata> getAnnotatedMethods(String annotationName) {
|
|
||||||
Set<MethodMetadata> result = new LinkedHashSet<>(4);
|
|
||||||
for (MethodMetadata annotatedMethod : this.declaredMethods) {
|
|
||||||
if (annotatedMethod.isAnnotated(annotationName)) {
|
|
||||||
result.add(annotatedMethod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Collections.unmodifiableSet(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<MethodMetadata> getDeclaredMethods() {
|
|
||||||
return Collections.unmodifiableSet(this.declaredMethods);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable Object other) {
|
|
||||||
return (this == other || (other instanceof ClassFileClassMetadata that && this.className.equals(that.className)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return this.className.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return this.className;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static ClassFileClassMetadata of(ClassModel classModel, @Nullable ClassLoader classLoader) {
|
|
||||||
Builder builder = new Builder(classLoader);
|
|
||||||
builder.classEntry(classModel.thisClass());
|
|
||||||
String currentClassName = classModel.thisClass().name().stringValue();
|
|
||||||
classModel.elementStream().forEach(classElement -> {
|
|
||||||
switch (classElement) {
|
|
||||||
case AccessFlags flags -> {
|
|
||||||
builder.accessFlags(flags);
|
|
||||||
}
|
|
||||||
case NestHostAttribute _ -> {
|
|
||||||
builder.enclosingClass(classModel.thisClass());
|
|
||||||
}
|
|
||||||
case InnerClassesAttribute innerClasses -> {
|
|
||||||
builder.nestMembers(currentClassName, innerClasses);
|
|
||||||
}
|
|
||||||
case RuntimeVisibleAnnotationsAttribute annotationsAttribute -> {
|
|
||||||
builder.mergedAnnotations(ClassFileAnnotationMetadata.createMergedAnnotations(
|
|
||||||
ClassUtils.convertResourcePathToClassName(currentClassName), annotationsAttribute, classLoader));
|
|
||||||
}
|
|
||||||
case Superclass superclass -> {
|
|
||||||
builder.superClass(superclass);
|
|
||||||
}
|
|
||||||
case Interfaces interfaces -> {
|
|
||||||
builder.interfaces(interfaces);
|
|
||||||
}
|
|
||||||
case MethodModel method -> {
|
|
||||||
builder.method(method);
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
// ignore class element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static class Builder {
|
|
||||||
|
|
||||||
private final ClassLoader clasLoader;
|
|
||||||
|
|
||||||
private String className;
|
|
||||||
|
|
||||||
private AccessFlags accessFlags;
|
|
||||||
|
|
||||||
private Set<AccessFlag> innerAccessFlags;
|
|
||||||
|
|
||||||
private @Nullable String enclosingClassName;
|
|
||||||
|
|
||||||
private @Nullable String superClassName;
|
|
||||||
|
|
||||||
private Set<String> interfaceNames = new LinkedHashSet<>(4);
|
|
||||||
|
|
||||||
private Set<String> memberClassNames = new LinkedHashSet<>(4);
|
|
||||||
|
|
||||||
private Set<MethodMetadata> declaredMethods = new LinkedHashSet<>(4);
|
|
||||||
|
|
||||||
private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet());
|
|
||||||
|
|
||||||
public Builder(ClassLoader classLoader) {
|
|
||||||
this.clasLoader = classLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
void classEntry(ClassEntry classEntry) {
|
|
||||||
this.className = ClassUtils.convertResourcePathToClassName(classEntry.name().stringValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
void accessFlags(AccessFlags accessFlags) {
|
|
||||||
this.accessFlags = accessFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
void enclosingClass(ClassEntry thisClass) {
|
|
||||||
String thisClassName = thisClass.name().stringValue();
|
|
||||||
int currentClassIndex = thisClassName.lastIndexOf('$');
|
|
||||||
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(thisClassName.substring(0, currentClassIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
void superClass(Superclass superClass) {
|
|
||||||
this.superClassName = ClassUtils.convertResourcePathToClassName(superClass.superclassEntry().name().stringValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
void interfaces(Interfaces interfaces) {
|
|
||||||
for (ClassEntry entry : interfaces.interfaces()) {
|
|
||||||
this.interfaceNames.add(ClassUtils.convertResourcePathToClassName(entry.name().stringValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void nestMembers(String currentClassName, InnerClassesAttribute innerClasses) {
|
|
||||||
for (InnerClassInfo classInfo : innerClasses.classes()) {
|
|
||||||
String innerClassName = classInfo.innerClass().name().stringValue();
|
|
||||||
if (currentClassName.equals(innerClassName)) {
|
|
||||||
// the current class is an inner class
|
|
||||||
this.innerAccessFlags = classInfo.flags();
|
|
||||||
}
|
|
||||||
classInfo.outerClass().ifPresent(outerClass -> {
|
|
||||||
if (outerClass.name().stringValue().equals(currentClassName)) {
|
|
||||||
// collecting data about actual inner classes
|
|
||||||
this.memberClassNames.add(ClassUtils.convertResourcePathToClassName(innerClassName));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mergedAnnotations(MergedAnnotations mergedAnnotations) {
|
|
||||||
this.mergedAnnotations = mergedAnnotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
void method(MethodModel method) {
|
|
||||||
ClassFileMethodMetadata classFileMethodMetadata = ClassFileMethodMetadata.of(method, this.clasLoader);
|
|
||||||
if (!classFileMethodMetadata.isSynthetic() && !classFileMethodMetadata.isDefaultConstructor()) {
|
|
||||||
this.declaredMethods.add(classFileMethodMetadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassFileClassMetadata build() {
|
|
||||||
boolean independentInnerClass = (this.enclosingClassName != null) && this.innerAccessFlags.contains(AccessFlag.STATIC);
|
|
||||||
return new ClassFileClassMetadata(this.className, this.accessFlags, this.enclosingClassName, this.superClassName,
|
|
||||||
independentInnerClass, this.interfaceNames, this.memberClassNames, this.declaredMethods, this.mergedAnnotations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+3
-4
@@ -42,13 +42,12 @@ final class ClassFileMetadataReader implements MetadataReader {
|
|||||||
|
|
||||||
ClassFileMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
|
ClassFileMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
this.annotationMetadata = ClassFileClassMetadata.of(parseClassModel(resource), classLoader);
|
this.annotationMetadata = ClassFileAnnotationMetadata.of(parseClassModel(resource), classLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClassModel parseClassModel(Resource resource) throws IOException {
|
private static ClassModel parseClassModel(Resource resource) throws IOException {
|
||||||
try (InputStream is = resource.getInputStream()) {
|
try (InputStream inputStream = resource.getInputStream()) {
|
||||||
byte[] bytes = is.readAllBytes();
|
return ClassFile.of().parse(inputStream.readAllBytes());
|
||||||
return ClassFile.of().parse(bytes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -41,7 +41,7 @@ import org.springframework.util.ClassUtils;
|
|||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
* @since 7.0
|
* @since 7.0
|
||||||
*/
|
*/
|
||||||
class ClassFileMethodMetadata implements MethodMetadata {
|
final class ClassFileMethodMetadata implements MethodMetadata {
|
||||||
|
|
||||||
private final String methodName;
|
private final String methodName;
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ class ClassFileMethodMetadata implements MethodMetadata {
|
|||||||
MergedAnnotations annotations = methodModel.elementStream()
|
MergedAnnotations annotations = methodModel.elementStream()
|
||||||
.filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute)
|
.filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(element -> ClassFileAnnotationMetadata.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader))
|
.map(element -> ClassFileAnnotationDelegate.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader))
|
||||||
.orElse(MergedAnnotations.of(Collections.emptyList()));
|
.orElse(MergedAnnotations.of(Collections.emptyList()));
|
||||||
return new ClassFileMethodMetadata(methodName, flags, declaringClassName, returnTypeName, source, annotations);
|
return new ClassFileMethodMetadata(methodName, flags, declaringClassName, returnTypeName, source, annotations);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-29
@@ -32,10 +32,6 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.annotation.RegEx;
|
import javax.annotation.RegEx;
|
||||||
import javax.annotation.Syntax;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
import javax.annotation.meta.TypeQualifierNickname;
|
|
||||||
import javax.annotation.meta.When;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
@@ -343,22 +339,6 @@ class AnnotatedElementUtilsTests {
|
|||||||
assertThat(attributes.get("value")).as("value for TxFromMultipleComposedAnnotations.").isEqualTo(asList("TxInheritedComposed", "TxComposed"));
|
assertThat(attributes.get("value")).as("value for TxFromMultipleComposedAnnotations.").isEqualTo(asList("TxInheritedComposed", "TxComposed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
void getAllAnnotationAttributesOnLangType() {
|
|
||||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(
|
|
||||||
org.springframework.lang.NonNullApi.class, javax.annotation.Nonnull.class.getName());
|
|
||||||
assertThat(attributes).as("Annotation attributes map for @Nonnull on @NonNullApi").isNotNull();
|
|
||||||
assertThat(attributes.get("when")).as("value for @NonNullApi").isEqualTo(List.of(When.ALWAYS));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAllAnnotationAttributesOnJavaxType() {
|
|
||||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(RegEx.class, Syntax.class.getName());
|
|
||||||
assertThat(attributes).as("Annotation attributes map for @Syntax on @RegEx").isNotNull();
|
|
||||||
assertThat(attributes.get("when")).as("value for @RegEx").isEqualTo(List.of(When.ALWAYS));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getMergedAnnotationAttributesOnClassWithLocalAnnotation() {
|
void getMergedAnnotationAttributesOnClassWithLocalAnnotation() {
|
||||||
Class<?> element = TxConfig.class;
|
Class<?> element = TxConfig.class;
|
||||||
@@ -848,19 +828,11 @@ class AnnotatedElementUtilsTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void javaxAnnotationTypeViaFindMergedAnnotation() {
|
void jakartaAnnotationTypeViaFindMergedAnnotation() {
|
||||||
assertThat(findMergedAnnotation(ResourceHolder.class, Resource.class)).isEqualTo(ResourceHolder.class.getAnnotation(Resource.class));
|
assertThat(findMergedAnnotation(ResourceHolder.class, Resource.class)).isEqualTo(ResourceHolder.class.getAnnotation(Resource.class));
|
||||||
assertThat(findMergedAnnotation(SpringAppConfigClass.class, Resource.class)).isEqualTo(SpringAppConfigClass.class.getAnnotation(Resource.class));
|
assertThat(findMergedAnnotation(SpringAppConfigClass.class, Resource.class)).isEqualTo(SpringAppConfigClass.class.getAnnotation(Resource.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void javaxMetaAnnotationTypeViaFindMergedAnnotation() {
|
|
||||||
assertThat(findMergedAnnotation(ThreadSafe.class, Documented.class))
|
|
||||||
.isEqualTo(ThreadSafe.class.getAnnotation(Documented.class));
|
|
||||||
assertThat(findMergedAnnotation(ResourceHolder.class, TypeQualifierNickname.class))
|
|
||||||
.isEqualTo(RegEx.class.getAnnotation(TypeQualifierNickname.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void nullableAnnotationTypeViaFindMergedAnnotation() throws Exception {
|
void nullableAnnotationTypeViaFindMergedAnnotation() throws Exception {
|
||||||
Method method = TransactionalServiceImpl.class.getMethod("doIt");
|
Method method = TransactionalServiceImpl.class.getMethod("doIt");
|
||||||
|
|||||||
-7
@@ -19,8 +19,6 @@ package org.springframework.core.annotation;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.lang.Contract;
|
import org.springframework.lang.Contract;
|
||||||
@@ -83,11 +81,6 @@ class AnnotationFilterTests {
|
|||||||
assertThat(AnnotationFilter.JAVA.matches(Retention.class)).isTrue();
|
assertThat(AnnotationFilter.JAVA.matches(Retention.class)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void javaWhenJavaxAnnotationReturnsTrue() {
|
|
||||||
assertThat(AnnotationFilter.JAVA.matches(ThreadSafe.class)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
void javaWhenSpringLangAnnotationReturnsFalse() {
|
void javaWhenSpringLangAnnotationReturnsFalse() {
|
||||||
|
|||||||
-6
@@ -30,10 +30,6 @@ import java.util.Map;
|
|||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.annotation.RegEx;
|
|
||||||
import javax.annotation.Syntax;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -430,8 +426,6 @@ class AnnotationUtilsTests {
|
|||||||
@Test
|
@Test
|
||||||
void isAnnotationMetaPresentForPlainType() {
|
void isAnnotationMetaPresentForPlainType() {
|
||||||
assertThat(isAnnotationMetaPresent(Order.class, Documented.class)).isTrue();
|
assertThat(isAnnotationMetaPresent(Order.class, Documented.class)).isTrue();
|
||||||
assertThat(isAnnotationMetaPresent(ThreadSafe.class, Documented.class)).isTrue();
|
|
||||||
assertThat(isAnnotationMetaPresent(RegEx.class, Syntax.class)).isTrue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+6
-3
@@ -20,7 +20,6 @@ import java.io.IOException;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.core.type.AbstractAnnotationMetadataTests;
|
import org.springframework.core.type.AbstractAnnotationMetadataTests;
|
||||||
@@ -53,16 +52,20 @@ class DefaultAnnotationMetadataTests extends AbstractAnnotationMetadataTests {
|
|||||||
@Test
|
@Test
|
||||||
void getClassAttributeWhenUnknownClass() {
|
void getClassAttributeWhenUnknownClass() {
|
||||||
var annotation = get(WithClassMissingFromClasspath.class).getAnnotations().get(ClassAttributes.class);
|
var annotation = get(WithClassMissingFromClasspath.class).getAnnotations().get(ClassAttributes.class);
|
||||||
assertThat(annotation.getStringArray("types")).contains("com.github.benmanes.caffeine.cache.Caffeine");
|
assertThat(annotation.getStringArray("types")).contains("javax.annotation.meta.When");
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> annotation.getClassArray("types"));
|
assertThatIllegalArgumentException().isThrownBy(() -> annotation.getClassArray("types"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ClassAttributes(types = {Caffeine.class})
|
|
||||||
|
@ClassAttributes(types = {javax.annotation.meta.When.class})
|
||||||
|
@javax.annotation.Nonnull(when = javax.annotation.meta.When.MAYBE)
|
||||||
public static class WithClassMissingFromClasspath {
|
public static class WithClassMissingFromClasspath {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface ClassAttributes {
|
public @interface ClassAttributes {
|
||||||
|
|
||||||
Class<?>[] types();
|
Class<?>[] types();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -20,7 +20,6 @@ import java.io.IOException;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.core.type.AbstractAnnotationMetadataTests;
|
import org.springframework.core.type.AbstractAnnotationMetadataTests;
|
||||||
@@ -53,17 +52,20 @@ class SimpleAnnotationMetadataTests extends AbstractAnnotationMetadataTests {
|
|||||||
@Test
|
@Test
|
||||||
void getClassAttributeWhenUnknownClass() {
|
void getClassAttributeWhenUnknownClass() {
|
||||||
var annotation = get(WithClassMissingFromClasspath.class).getAnnotations().get(ClassAttributes.class);
|
var annotation = get(WithClassMissingFromClasspath.class).getAnnotations().get(ClassAttributes.class);
|
||||||
assertThat(annotation.getStringArray("types")).contains("com.github.benmanes.caffeine.cache.Caffeine");
|
assertThat(annotation.getStringArray("types")).contains("javax.annotation.meta.When");
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> annotation.getClassArray("types"));
|
assertThatIllegalArgumentException().isThrownBy(() -> annotation.getClassArray("types"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ClassAttributes(types = {Caffeine.class})
|
|
||||||
|
@ClassAttributes(types = {javax.annotation.meta.When.class})
|
||||||
|
@javax.annotation.Nonnull(when = javax.annotation.meta.When.MAYBE)
|
||||||
public static class WithClassMissingFromClasspath {
|
public static class WithClassMissingFromClasspath {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface ClassAttributes {
|
public @interface ClassAttributes {
|
||||||
|
|
||||||
Class<?>[] types();
|
Class<?>[] types();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user