掘金 后端 ( ) • 2024-04-15 21:05

@[TOC]

一、认识MetadataReader

我们平时对一个类的解析,通常会用到反射的技术,比如:

Class<?> aClass = Class.forName("com.qstcloud.qcard.portal.context.domain.Test");
aClass.getMethods();
aClass.getDeclaredFields();
// ...

在Spring中需要去解析类的信息,比如类名、类中的方法、类上的注解,这些都可以称之为类的元数据,所以Spring中对类的元数据做了抽象,并提供了一些工具类。

MetadataReader表示类的元数据读取器,默认实现类为SimpleMetadataReader

需要注意的是,SimpleMetadataReader去解析类时,使用的ASM技术。 我们都知道,Class.forName执行之后,就会将这个类加载到JVM中,哪怕我们根本用不到这个类,也会被加载。 ​ Spring启动的时候需要去扫描所有的类,如果指定的包路径比较宽泛,那么扫描的类是非常多的,那如果在Spring启动时就把这些类全部加载进JVM了,这样对应用非常不友好,所以使用了ASM技术。

后续我们进一步分析MetadataReader的源码,就知道Spring的处理比我们自己处理到底高明在哪里了。

二、使用MetadataReader

SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();

// 构造一个MetadataReader
MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader("com.test.Test");

// 得到一个ClassMetadata,并获取了类名
ClassMetadata classMetadata = metadataReader.getClassMetadata();

System.out.println(classMetadata.getClassName());

// 获取一个AnnotationMetadata,并获取类上的注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
for (String annotationType : annotationMetadata.getAnnotationTypes()) {
    System.out.println(annotationType);
}

我们可以看到,使用MetadataReader对类进行解析,非常的方便,不需要我们编写大量的反射代码,Spring直接给我们处理好了。

三、源码分析

1、认识MetadataReaderFactory

MetadataReaderFactory是一个创建MetadataReader的工厂,有两个方法,可以通过类的全限定名或者类的Resource进行获取MetadataReader:

public interface MetadataReaderFactory {

	// 类全限定名
	MetadataReader getMetadataReader(String className) throws IOException;

	// 类的Resource
	MetadataReader getMetadataReader(Resource resource) throws IOException;

}

2、认识MetadataReader

MetadataReader接口有三个方法:

public interface MetadataReader {

	// 获取资源
	Resource getResource();

	// 获取ClassMetadata,里面包含一个类的基本信息
	ClassMetadata getClassMetadata();

	// 获取AnnotationMetadata ,里面包含一个类中注解的信息
	AnnotationMetadata getAnnotationMetadata();

}

3、认识ClassMetadata

ClassMetadata是一个接口,它的优势是以一种不需要加载特定类的形式定义该类的抽象元数据。 也就是说,通过ClassMetadata获取类的信息,并不需要加载该类的JVM中。

里面包含了获取类基本属性的所有方法,包括类名、类的接口、父类、内部类等等信息。

4、认识AnnotationMetadata

AnnotationMetadata是一个接口,它的优势是以不需要加载特定类的形式定义对该类注解内容。 也就是说,通过AnnotationMetadata获取类的注解信息,并不需要加载该类的JVM中。

里面包含了获取类中注解的信息。

5、MetadataReader在Spring的应用

Spring在进行包扫描时,就用到了MetadataReader。

因为扫描需要读取包路径下所有的类,所以使用MetadataReader,不需要加载额外的类到JVM中。 在这里插入图片描述

6、举一反三:扫描标注了我们自定义注解的所有类

通过Spring的扫描器,我们可以自定义我们自己的扫描器,扫描我们想要的结果:

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Set;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
}

@MyAnnotation
public class Test {

    public static void main(String[] args) throws IOException {
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new TypeFilter() {
            /**
             * 自定义过滤器,只扫描标注@MyAnnotation的类
             */
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                if (metadataReader.getAnnotationMetadata().hasAnnotation(MyAnnotation.class.getName())) {
                    return true;
                }

                return false;
            }
        });

        // 获取扫描结果,并并且会自动封装到BeanDefinition
        Set<BeanDefinition> candidateComponents = provider.findCandidateComponents("com.test");

        for (BeanDefinition candidateComponent : candidateComponents) {
        	// 输出我们Test类
            System.out.println(candidateComponent.getBeanClassName());
        }
    }
}