13 changed files with 18 additions and 931 deletions
@ -1,39 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2024 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 org.springframework.core.io.ResourceLoader; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
|
|
||||||
/** |
|
||||||
* Internal delegate for instantiating {@link MetadataReaderFactory} implementations. |
|
||||||
* For JDK < 24, the {@link SimpleMetadataReaderFactory} is being used. |
|
||||||
* |
|
||||||
* @author Brian Clozel |
|
||||||
* @since 7.0 |
|
||||||
* @see MetadataReaderFactory |
|
||||||
*/ |
|
||||||
abstract class MetadataReaderFactoryDelegate { |
|
||||||
|
|
||||||
static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { |
|
||||||
return new SimpleMetadataReaderFactory(resourceLoader); |
|
||||||
} |
|
||||||
|
|
||||||
static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { |
|
||||||
return new SimpleMetadataReaderFactory(classLoader); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,129 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2024 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.reflect.Array; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.LinkedHashMap; |
|
||||||
import java.util.LinkedHashSet; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
import org.springframework.core.annotation.AnnotationFilter; |
|
||||||
import org.springframework.core.annotation.MergedAnnotation; |
|
||||||
import org.springframework.core.annotation.MergedAnnotations; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
import org.springframework.util.ClassUtils; |
|
||||||
|
|
||||||
abstract class ClassFileAnnotationMetadata { |
|
||||||
|
|
||||||
static MergedAnnotations createMergedAnnotations(String entryName, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) { |
|
||||||
Set<MergedAnnotation<?>> annotations = new LinkedHashSet<>(4); |
|
||||||
annotationAttribute.annotations().forEach(ann -> { |
|
||||||
MergedAnnotation<java.lang.annotation.Annotation> mergedAnnotation = createMergedAnnotation(entryName, ann, classLoader); |
|
||||||
if (mergedAnnotation != null) { |
|
||||||
annotations.add(mergedAnnotation); |
|
||||||
} |
|
||||||
}); |
|
||||||
return MergedAnnotations.of(annotations); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") |
|
||||||
@Nullable |
|
||||||
private static <A extends java.lang.annotation.Annotation> MergedAnnotation<A> createMergedAnnotation(String entryName, Annotation annotation, @Nullable ClassLoader classLoader) { |
|
||||||
String typeName = fromTypeDescriptor(annotation.className().stringValue()); |
|
||||||
if (AnnotationFilter.PLAIN.matches(typeName)) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
Map<String, Object> attributes = new LinkedHashMap<>(4); |
|
||||||
try { |
|
||||||
Class<A> annotationType = (Class<A>) ClassUtils.forName(typeName, classLoader); |
|
||||||
for (AnnotationElement element : annotation.elements()) { |
|
||||||
attributes.put(element.name().stringValue(), readAnnotationValue(element.value(), classLoader)); |
|
||||||
} |
|
||||||
Map<String, Object> compactedAttributes = (attributes.isEmpty() ? Collections.emptyMap() : attributes); |
|
||||||
return MergedAnnotation.of(classLoader, new Source(entryName), annotationType, compactedAttributes); |
|
||||||
} |
|
||||||
catch (ClassNotFoundException | LinkageError ex) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static Object readAnnotationValue(AnnotationValue elementValue, @Nullable ClassLoader classLoader) throws ClassNotFoundException { |
|
||||||
switch (elementValue) { |
|
||||||
case AnnotationValue.OfArray arrayValue -> { |
|
||||||
List<AnnotationValue> rawValues = arrayValue.values(); |
|
||||||
List<Object> values = new ArrayList<>(rawValues.size()); |
|
||||||
for (AnnotationValue arrayEntry : rawValues) { |
|
||||||
values.add(readAnnotationValue(arrayEntry, classLoader)); |
|
||||||
} |
|
||||||
Class<?> elementType = getArrayElementType(values); |
|
||||||
return values.toArray((Object[]) Array.newInstance(elementType, rawValues.size())); |
|
||||||
} |
|
||||||
case AnnotationValue.OfAnnotation annotationValue -> { |
|
||||||
return annotationValue.annotation(); |
|
||||||
} |
|
||||||
case AnnotationValue.OfClass classValue -> { |
|
||||||
return fromTypeDescriptor(classValue.className().stringValue()); |
|
||||||
} |
|
||||||
case AnnotationValue.OfEnum enumValue -> { |
|
||||||
return parseEnum(enumValue, classLoader); |
|
||||||
} |
|
||||||
case AnnotationValue.OfConstant constantValue -> { |
|
||||||
return constantValue.resolvedValue(); |
|
||||||
} |
|
||||||
default -> { |
|
||||||
return elementValue; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static Class<?> getArrayElementType(List<Object> values) { |
|
||||||
if (values.isEmpty()) { |
|
||||||
return Object.class; |
|
||||||
} |
|
||||||
Object firstElement = values.getFirst(); |
|
||||||
if (firstElement instanceof Enum<?> enumeration) { |
|
||||||
return enumeration.getDeclaringClass(); |
|
||||||
} |
|
||||||
return firstElement.getClass(); |
|
||||||
} |
|
||||||
|
|
||||||
private static String fromTypeDescriptor(String descriptor) { |
|
||||||
return descriptor.substring(1, descriptor.length() - 1) |
|
||||||
.replace('/', '.'); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") |
|
||||||
private static <E extends Enum<E>> Enum<E> parseEnum(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) throws ClassNotFoundException { |
|
||||||
String enumClassName = fromTypeDescriptor(enumValue.className().stringValue()); |
|
||||||
Class<E> enumClass = (Class<E>) ClassUtils.forName(enumClassName, classLoader); |
|
||||||
return Enum.valueOf(enumClass, enumValue.constantName().stringValue()); |
|
||||||
} |
|
||||||
|
|
||||||
record Source(String entryName) { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,315 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2024 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.HashSet; |
|
||||||
import java.util.LinkedHashSet; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
import org.springframework.core.annotation.MergedAnnotations; |
|
||||||
import org.springframework.core.type.AnnotationMetadata; |
|
||||||
import org.springframework.core.type.MethodMetadata; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
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 |
|
||||||
*/ |
|
||||||
class ClassFileClassMetadata implements AnnotationMetadata { |
|
||||||
|
|
||||||
private final String className; |
|
||||||
|
|
||||||
private final AccessFlags accessFlags; |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private final String enclosingClassName; |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private final 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; |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private 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 = superClassName; |
|
||||||
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 |
|
||||||
@Nullable |
|
||||||
public String getEnclosingClassName() { |
|
||||||
return this.enclosingClassName; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
@Nullable |
|
||||||
public 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 nestHost -> { |
|
||||||
builder.enclosingClass(nestHost.nestHost()); |
|
||||||
} |
|
||||||
case InnerClassesAttribute innerClasses -> { |
|
||||||
builder.nestMembers(currentClassName, innerClasses); |
|
||||||
} |
|
||||||
case RuntimeVisibleAnnotationsAttribute annotationsAttribute -> { |
|
||||||
builder.mergedAnnotations(ClassFileAnnotationMetadata.createMergedAnnotations(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; |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private String enclosingClassName; |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private String superClassName; |
|
||||||
|
|
||||||
private Set<String> interfaceNames = new HashSet<>(); |
|
||||||
|
|
||||||
private Set<String> memberClassNames = new HashSet<>(); |
|
||||||
|
|
||||||
private Set<MethodMetadata> declaredMethods = new HashSet<>(); |
|
||||||
|
|
||||||
private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet()); |
|
||||||
|
|
||||||
public Builder(ClassLoader classLoader) { |
|
||||||
this.clasLoader = classLoader; |
|
||||||
} |
|
||||||
|
|
||||||
Builder classEntry(ClassEntry classEntry) { |
|
||||||
this.className = ClassUtils.convertResourcePathToClassName(classEntry.name().stringValue()); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
Builder accessFlags(AccessFlags accessFlags) { |
|
||||||
this.accessFlags = accessFlags; |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
Builder innerAccessFlags(Set<AccessFlag> innerAccessFlags) { |
|
||||||
this.innerAccessFlags = innerAccessFlags; |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
Builder enclosingClass(ClassEntry enclosingClass) { |
|
||||||
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(enclosingClass.name().stringValue()); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
Builder superClass(Superclass superClass) { |
|
||||||
this.superClassName = ClassUtils.convertResourcePathToClassName(superClass.superclassEntry().name().stringValue()); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
Builder interfaces(Interfaces interfaces) { |
|
||||||
for (ClassEntry entry : interfaces.interfaces()) { |
|
||||||
this.interfaceNames.add(ClassUtils.convertResourcePathToClassName(entry.name().stringValue())); |
|
||||||
} |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
Builder nestMembers(String currentClassName, InnerClassesAttribute innerClasses) { |
|
||||||
for (InnerClassInfo classInfo : innerClasses.classes()) { |
|
||||||
String innerClassName = classInfo.innerClass().name().stringValue(); |
|
||||||
// the current class is an inner class
|
|
||||||
if (currentClassName.equals(innerClassName)) { |
|
||||||
this.innerAccessFlags = classInfo.flags(); |
|
||||||
} |
|
||||||
// collecting data about actual inner classes
|
|
||||||
else { |
|
||||||
this.memberClassNames.add(ClassUtils.convertResourcePathToClassName(innerClassName)); |
|
||||||
} |
|
||||||
} |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
Builder mergedAnnotations(MergedAnnotations mergedAnnotations) { |
|
||||||
this.mergedAnnotations = mergedAnnotations; |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
Builder method(MethodModel method) { |
|
||||||
this.declaredMethods.add(ClassFileMethodMetadata.of(method, this.clasLoader)); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,68 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2024 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.io.IOException; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.lang.classfile.ClassFile; |
|
||||||
import java.lang.classfile.ClassModel; |
|
||||||
|
|
||||||
import org.springframework.core.io.Resource; |
|
||||||
import org.springframework.core.type.AnnotationMetadata; |
|
||||||
import org.springframework.core.type.ClassMetadata; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
|
|
||||||
/** |
|
||||||
* {@link MetadataReader} implementation based on the {@link ClassFile} API. |
|
||||||
* |
|
||||||
* @author Brian Clozel |
|
||||||
*/ |
|
||||||
final class ClassFileMetadataReader implements MetadataReader { |
|
||||||
|
|
||||||
private final Resource resource; |
|
||||||
|
|
||||||
private final AnnotationMetadata annotationMetadata; |
|
||||||
|
|
||||||
|
|
||||||
ClassFileMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { |
|
||||||
this.resource = resource; |
|
||||||
this.annotationMetadata = ClassFileClassMetadata.of(getClassModel(resource), classLoader); |
|
||||||
} |
|
||||||
|
|
||||||
private static ClassModel getClassModel(Resource resource) throws IOException { |
|
||||||
try (InputStream is = resource.getInputStream()) { |
|
||||||
byte[] bytes = is.readAllBytes(); |
|
||||||
return ClassFile.of().parse(bytes); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Resource getResource() { |
|
||||||
return this.resource; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public ClassMetadata getClassMetadata() { |
|
||||||
return this.annotationMetadata; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public AnnotationMetadata getAnnotationMetadata() { |
|
||||||
return this.annotationMetadata; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,104 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2024 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.io.FileNotFoundException; |
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
import org.springframework.core.io.DefaultResourceLoader; |
|
||||||
import org.springframework.core.io.Resource; |
|
||||||
import org.springframework.core.io.ResourceLoader; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
import org.springframework.util.ClassUtils; |
|
||||||
|
|
||||||
/** |
|
||||||
* Implementation of the {@link MetadataReaderFactory} interface, |
|
||||||
* using the {@link java.lang.classfile.ClassFile} API for parsing the bytecode. |
|
||||||
* |
|
||||||
* @author Brian Clozel |
|
||||||
* @since 7.0 |
|
||||||
*/ |
|
||||||
public class ClassFileMetadataReaderFactory implements MetadataReaderFactory { |
|
||||||
|
|
||||||
|
|
||||||
private final ResourceLoader resourceLoader; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Create a new ClassFileMetadataReaderFactory for the default class loader. |
|
||||||
*/ |
|
||||||
public ClassFileMetadataReaderFactory() { |
|
||||||
this.resourceLoader = new DefaultResourceLoader(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Create a new ClassFileMetadataReaderFactory for the given resource loader. |
|
||||||
* @param resourceLoader the Spring ResourceLoader to use |
|
||||||
* (also determines the ClassLoader to use) |
|
||||||
*/ |
|
||||||
public ClassFileMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { |
|
||||||
this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Create a new ClassFileMetadataReaderFactory for the given class loader. |
|
||||||
* @param classLoader the ClassLoader to use |
|
||||||
*/ |
|
||||||
public ClassFileMetadataReaderFactory(@Nullable ClassLoader classLoader) { |
|
||||||
this.resourceLoader = |
|
||||||
(classLoader != null ? new DefaultResourceLoader(classLoader) : new DefaultResourceLoader()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Return the ResourceLoader that this MetadataReaderFactory has been |
|
||||||
* constructed with. |
|
||||||
*/ |
|
||||||
public final ResourceLoader getResourceLoader() { |
|
||||||
return this.resourceLoader; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public MetadataReader getMetadataReader(String className) throws IOException { |
|
||||||
try { |
|
||||||
String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + |
|
||||||
ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; |
|
||||||
Resource resource = this.resourceLoader.getResource(resourcePath); |
|
||||||
return getMetadataReader(resource); |
|
||||||
} |
|
||||||
catch (FileNotFoundException ex) { |
|
||||||
// Maybe an inner class name using the dot name syntax? Need to use the dollar syntax here...
|
|
||||||
// ClassUtils.forName has an equivalent check for resolution into Class references later on.
|
|
||||||
int lastDotIndex = className.lastIndexOf('.'); |
|
||||||
if (lastDotIndex != -1) { |
|
||||||
String innerClassName = |
|
||||||
className.substring(0, lastDotIndex) + '$' + className.substring(lastDotIndex + 1); |
|
||||||
String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + |
|
||||||
ClassUtils.convertClassNameToResourcePath(innerClassName) + ClassUtils.CLASS_FILE_SUFFIX; |
|
||||||
Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath); |
|
||||||
if (innerClassResource.exists()) { |
|
||||||
return getMetadataReader(innerClassResource); |
|
||||||
} |
|
||||||
} |
|
||||||
throw ex; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public MetadataReader getMetadataReader(Resource resource) throws IOException { |
|
||||||
return new ClassFileMetadataReader(resource, this.resourceLoader.getClassLoader()); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,162 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2024 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.MethodModel; |
|
||||||
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; |
|
||||||
import java.lang.constant.ClassDesc; |
|
||||||
import java.lang.constant.MethodTypeDesc; |
|
||||||
import java.lang.reflect.AccessFlag; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.stream.Collectors; |
|
||||||
import java.util.stream.Stream; |
|
||||||
|
|
||||||
import org.springframework.core.annotation.MergedAnnotation; |
|
||||||
import org.springframework.core.annotation.MergedAnnotations; |
|
||||||
import org.springframework.core.type.MethodMetadata; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
|
|
||||||
/** |
|
||||||
* {@link MethodMetadata} extracted from class bytecode using the |
|
||||||
* {@link java.lang.classfile.ClassFile} API. |
|
||||||
* @author Brian Clozel |
|
||||||
*/ |
|
||||||
class ClassFileMethodMetadata implements MethodMetadata { |
|
||||||
|
|
||||||
private final String methodName; |
|
||||||
|
|
||||||
private final AccessFlags accessFlags; |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private final String declaringClassName; |
|
||||||
|
|
||||||
private final String returnTypeName; |
|
||||||
|
|
||||||
// The source implements equals(), hashCode(), and toString() for the underlying method.
|
|
||||||
private final Object source; |
|
||||||
|
|
||||||
private final MergedAnnotations annotations; |
|
||||||
|
|
||||||
ClassFileMethodMetadata(String methodName, AccessFlags accessFlags, String declaringClassName, String returnTypeName, Object source, MergedAnnotations annotations) { |
|
||||||
this.methodName = methodName; |
|
||||||
this.accessFlags = accessFlags; |
|
||||||
this.declaringClassName = declaringClassName; |
|
||||||
this.returnTypeName = returnTypeName; |
|
||||||
this.source = source; |
|
||||||
this.annotations = annotations; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String getMethodName() { |
|
||||||
return this.methodName; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
@Nullable |
|
||||||
public String getDeclaringClassName() { |
|
||||||
return this.declaringClassName; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String getReturnTypeName() { |
|
||||||
return this.returnTypeName; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean isAbstract() { |
|
||||||
return this.accessFlags.has(AccessFlag.ABSTRACT); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean isStatic() { |
|
||||||
return this.accessFlags.has(AccessFlag.STATIC); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean isFinal() { |
|
||||||
return this.accessFlags.has(AccessFlag.FINAL); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean isOverridable() { |
|
||||||
return !isStatic() && !isFinal() && !isPrivate(); |
|
||||||
} |
|
||||||
|
|
||||||
private boolean isPrivate() { |
|
||||||
return this.accessFlags.has(AccessFlag.PRIVATE); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public MergedAnnotations getAnnotations() { |
|
||||||
return this.annotations; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean equals(@Nullable Object other) { |
|
||||||
return (this == other || (other instanceof ClassFileMethodMetadata that && this.source.equals(that.source))); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public int hashCode() { |
|
||||||
return this.source.hashCode(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String toString() { |
|
||||||
return this.source.toString(); |
|
||||||
} |
|
||||||
|
|
||||||
static ClassFileMethodMetadata of(MethodModel methodModel, ClassLoader classLoader) { |
|
||||||
String methodName = methodModel.methodName().stringValue(); |
|
||||||
AccessFlags flags = methodModel.flags(); |
|
||||||
String declaringClassName = methodModel.parent().map(parent -> parent.thisClass().name().stringValue()).orElse(null); |
|
||||||
String returnTypeName = methodModel.methodTypeSymbol().returnType().displayName(); |
|
||||||
Source source = new Source(declaringClassName, methodName, methodModel.methodTypeSymbol()); |
|
||||||
MergedAnnotations annotations = methodModel.elementStream() |
|
||||||
.filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute) |
|
||||||
.findFirst() |
|
||||||
.map(element -> ClassFileAnnotationMetadata.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader)) |
|
||||||
.orElse(MergedAnnotations.of(Collections.emptyList())); |
|
||||||
return new ClassFileMethodMetadata(methodName, flags, declaringClassName, returnTypeName, source, annotations); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* {@link MergedAnnotation} source. |
|
||||||
* @param declaringClassName the name of the declaring class
|
|
||||||
* @param methodName the name of the method |
|
||||||
* @param descriptor the bytecode descriptor for this method |
|
||||||
*/ |
|
||||||
record Source(String declaringClassName, String methodName, MethodTypeDesc descriptor) { |
|
||||||
|
|
||||||
@Override |
|
||||||
public String toString() { |
|
||||||
StringBuilder builder = new StringBuilder(); |
|
||||||
builder.append(this.declaringClassName); |
|
||||||
builder.append('.'); |
|
||||||
builder.append(this.methodName); |
|
||||||
builder.append('('); |
|
||||||
builder.append(Stream.of(this.descriptor.parameterArray()) |
|
||||||
.map(ClassDesc::displayName) |
|
||||||
.collect(Collectors.joining(","))); |
|
||||||
builder.append(')'); |
|
||||||
return builder.toString(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,38 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2024 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 org.springframework.core.io.ResourceLoader; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
|
|
||||||
/** |
|
||||||
* Internal delegate for instantiating {@link MetadataReaderFactory} implementations. |
|
||||||
* For JDK >= 24, the {@link ClassFileMetadataReaderFactory} is being used. |
|
||||||
* |
|
||||||
* @author Brian Clozel |
|
||||||
* @see MetadataReaderFactory |
|
||||||
*/ |
|
||||||
abstract class MetadataReaderFactoryDelegate { |
|
||||||
|
|
||||||
static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { |
|
||||||
return new ClassFileMetadataReaderFactory(resourceLoader); |
|
||||||
} |
|
||||||
|
|
||||||
static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { |
|
||||||
return new ClassFileMetadataReaderFactory(classLoader); |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue