代码从ClassPathBeanDefinitionScanner类的doScan方法开始阅读。
处理流程整体说来并不复杂,下面我们就看下流程图。
1.流程图
流程图
2.时序图
3.asm读取class文件的信息
我们看下如何利用asm读取class文件的信息的:
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
InputStream is = new BufferedInputStream(resource.getInputStream());
ClassReader classReader;
try {
classReader = new ClassReader(is);
}
catch (IllegalArgumentException ex) {
throw new NestedIOException("ASM ClassReader failed to parse class file - " +
"probably due to a new Java class file version that isn't supported yet: " + resource, ex);
}
finally {
is.close();
}
//下面会重点讲解
AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
classReader.accept(visitor, ClassReader.SKIP_DEBUG);
this.annotationMetadata = visitor;
// (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
this.classMetadata = visitor;
this.resource = resource;
}
AnnotationMetadataReadingVisitor,这个继承了ClassVisitor抽象类,这个visitor里面定义了一堆的回调方法:
public abstract class ClassVisitor {
public ClassVisitor(int api);
public ClassVisitor(int api, ClassVisitor cv);
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces);
public void visitSource(String source, String debug);
public void visitOuterClass(String owner, String name, String desc);
// 解析到class文件中的注解时回调本方法
AnnotationVisitor visitAnnotation(String desc, boolean visible);
public void visitAttribute(Attribute attr);
public void visitInnerClass(String name, String outerName,String innerName,int access);
// 解析到field时回调
public FieldVisitor visitField(int access, String name, String desc,String signature, Object value);
// 解析到method时回调
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);
void visitEnd();
}
这其中,方法的访问顺序如下:
visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd
代表:
visit必须最先访问;
接着是最多一次的visitSource,再接着是最多一次的visitOuterClass;
接着是任意多次的visitAnnotation | visitAttribute ,这两个,顺序随意;
再接着是,任意多次的visitInnerClass | visitField | visitMethod ,顺序随意
最后,visitEnd
这个顺序的? * () 等符号,其实类似于正则表达式的语法,对吧,还是比较好理解的。
AnnotationMetadataReadingVisitor还实现了AnnotationMetadata接口。
AnnotationMetadata接口方法如下:
下面我们看下,两个非常重要的方法:
1.获取在当前class上的直接注解:getAnnotationTypes();
2.获取某个直接注解的父注解,比如你这里传个controller进去,就能给你拿到controller这个注解的父注解:getMetaAnnotationTypes(String annotationName);
方法1:
@Override
public Set<String> getAnnotationTypes() {
return this.annotationSet;
}
annotationSet里的数据是如何来的呢?
这里每访问到一个注解,就会回调visitAnnotation方法,放入到annotationSet中。
@Override
public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
String className = Type.getType(desc).getClassName();
this.annotationSet.add(className);
return new AnnotationAttributesReadingVisitor(
className, this.attributesMap, this.metaAnnotationMap, this.classLoader);
}
方法2:
@Override
public Set<String> getMetaAnnotationTypes(String annotationName) {
return this.metaAnnotationMap.get(annotationName);
}
metaAnnotationMap中的数据是从哪里来的呢:
AnnotationAttributesReadingVisitor,这个visitor会在:asm访问注解的具体属性时,其中的如下方法被回调。
@Override
public void visitEnd() {
super.visitEnd();
Class<?> annotationClass = this.attributes.annotationType();
if (annotationClass != null) {
List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType);
if (attributeList == null) {
this.attributesMap.add(this.annotationType, this.attributes);
}
else {
attributeList.add(0, this.attributes);
}
Set<Annotation> visited = new LinkedHashSet<>();
//获取所有的父注解
Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
if (!ObjectUtils.isEmpty(metaAnnotations)) {
for (Annotation metaAnnotation : metaAnnotations) {
//非元注解
if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
recursivelyCollectMetaAnnotations(visited, metaAnnotation);
}
}
}
Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
for (Annotation ann : visited) {
metaAnnotationTypeNames.add(ann.annotationType().getName());
}
this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
}
private void recursivelyCollectMetaAnnotations(Set<Annotation> visited, Annotation annotation) {
Class<? extends Annotation> annotationType = annotation.annotationType();
String annotationName = annotationType.getName();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) && visited.add(annotation)) {
try {
// Only do attribute scanning for public annotations; we'd run into
// IllegalAccessExceptions otherwise, and we don't want to mess with
// accessibility in a SecurityManager environment.
if (Modifier.isPublic(annotationType.getModifiers())) {
this.attributesMap.add(annotationName,
AnnotationUtils.getAnnotationAttributes(annotation, false, true));
}
for (Annotation metaMetaAnnotation : annotationType.getAnnotations()) {
// 递归调用自己
recursivelyCollectMetaAnnotations(visited, metaMetaAnnotation);
}
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect meta-annotations on [" + annotation + "]: " + ex);
}
}
}
}
通过asm的解析,最终返回MetadataReader的对象,通过MetadataReader可以读取到类的元数据和注解的元数据。
再利用MetadataReader,创建出ScannedGenericBeanDefinition。
4.为什么使用asm,而不是反射来获取元数据
1.asm方式的性能,远高于反射实现,因为无需加载class,直接解析class文件的字节码。
asm是一个字节码操作和分析的框架,能够用来修改已存在的class,或者动态生成class,直接以二进制的形式。ASM提供一些通用的字节码转换和分析算法,通过这些算法,可以构建复杂的字节码转换和代码分析工具。ASM提供和其他字节码框架类似的功能,但是其专注于性能。因为它被设计和实现为,尽可能的小,尽可能的快。
2.Class.forName()方法在执行时,类中static代码块会执行,这会让静态代码块过早的执行,对执行时机造成了干扰。