asm扫描原理
asm扫描原理
对于spring是如何扫描scan(包名),来注册到BeanDefinitionMap中的:
spring的做法:
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
//判断当前的你传进来的路径是什么类型的?
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {//VFS
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {//jar
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
通过上面一连串的代码来看,spring是通过Java中的流技术来把指定的包下的资源进行加载,但是我们回到真正扫描完成的方法
看到他获取的是一个Resource数组,这里很奇怪了为什么他存一个这样的数组,他直接通过反射得到一个class数组不好吗?
这里个人的看法是,由于很多类,其实会包含有些特殊的逻辑或者代码,比如静态代码块,这个静态代码块的是要该类调用过后才会被加载到JVM中的,并且下次不再被调用,生成新的实例,所以这里他这么做的道理是为不干扰用户行为。
再从这张图来看,他会把获取到resource数组转换为一个metadataReader,为什么?原因很简单,你上面都自己加载了一个resource了,那下面就不是普通的class了,所以需要通过spring定制的class的描述方式来描述当前的class。
下面来讲解spring使用ASM字节码编译技术:
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
//该candidates存放被扫描出来的beanDefinition
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
//拼接PackageUrl路径
String packageSearchPath =
ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) +
'/' +
this.resourcePattern;
//开始用spring自己使用的流工具完成文件的扫描,得到指定包中的文件
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
try {
//这里为什么spring不转换为class?
/*原因:
* 1.由于有些类中是会存在静态代码块,对于用户而言这些静态代码块是按需加载的,当我不需要的时候我就不会去加载
* 2.那如果在这里spring使用class的形式来提前或者是自说自话的进行class加载,那是不是就间接的把class中static代码中的内容加载了
* 3.是不是对spring的使用者很不友好
* 4.那么当前,我们对于metadataReader就可以理解spring为了防止上述的情况所创建的"spring版本的class"
* 5.我们可以看到getMetadataReaderFactory()方法的返回对象是MetadataReaderFactory->SimpleMetadataReaderFactory-ASM->MetadataReader
*/
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
//判断这个类是否符合要求,扫描器有属于自己的规则,isCandidateComponent(metadataReader)这个方法就是判断当前的metadataReader是否符合spring对于scanner的要求
/*
* 1.当前的被加载的metadataReader是否要被剔除
* 2.当前的metadataReader是否需要被转化
* 3.由于扫描器没有添加excludeFilters所以当前isCandidateComponent方法中根本不会去过滤这个集合中的元素
* 4.是否需要转化,扫描器添加了includeFilters 3个过滤规则
*/
//总结:一句话,spring得到包路径后,会去扫描,得到属于spring特有的Class,然后判断是否有3个过滤规则,有就转化为BeanDefinition
// 由于mybatis在扩展spring的时候没有添任何的规则所以这里永远为true,
// 那么作为mybatis作者的角度来看,其实他想说的是我认为只要你指定了包,
// 那么这个包下的所有的类都是可以Mapper
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 在默认为spring的情况下不会调用isCandidateComponent()方法进行判断当前类是否是一个正常类
// 在mybatis扩展spring的情况下,会调用mybatis扩展重写子类,并且重写方法isCandidateComponent()方法
// mybatis获取到当前的BeanDefinition的class进行判断是否为interface
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
//如果是一个正常类就加入到candidates集合中
candidates.add(sbd);
} else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
} else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
} catch (FileNotFoundException ex) {
if (traceEnabled) {
logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
}
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
可以看到在ClassPathScanningCandidateComponentProvider类中,把resource传入到getMetadataReader方法中构建出来一个metaDateReader对象。
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
我们点进getMetadataReaderFactory方法中发现他的返回类型是一个什么什么工厂,再通过,getMetadataReader方法构建出来了一个metaDateReader对象。
/**
* Return the MetadataReaderFactory used by this component provider.
* 返回此组件提供程序使用的元数据读取器工厂。
*/
public final MetadataReaderFactory getMetadataReaderFactory() {
if (this.metadataReaderFactory == null) {
this.metadataReaderFactory = new CachingMetadataReaderFactory();
}
return this.metadataReaderFactory;
}
再看后面的方法,我们想要点进去系统提示有两个实现,说明该方法是一个接口,但是这次我们进入到SimpleMetadataReaderFactory中。
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
}
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
getClassReader(resource).accept(visitor, PARSING_OPTIONS);
this.resource = resource;
this.annotationMetadata = visitor.getMetadata();
}
关键点来了spring先调用getMetadataReader()方法,然后实例化SimpleMetadataReader方法,这里我们可以看见第一行是new了一个SimpleAnnotationMetadataReadingVisitor()对象,传入一个类加载器。我们回忆一下设计模式中的访问者模式。然后再说一下市面上面的ASM框架很多,其实万变不离其宗都是基于最原始的ASM框架开发出来的,像JDK原生的ASM框架,所以Spring自己也有一套Spring的ASM框架。
下面我们来模仿spring将一个类通过ASM技术来将一个类解析为MetaDataClass的:
public class Application {
//定义全局变量,指定ASM要处理那些类属性
private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES;
public static void main(String[] args) throws IOException {
//1.创建一个ASM解析器
ClassParse4Asm parse = new ClassParse4Asm();
//2.调用spring的类解析器来完成解析其实就是反射那套只不过spring进行了封装
ClassReader classReader = new ClassReader("com.lukp.live.utils.ClassParse4ASm");
//3.由于spring在类解析的时候是采用的访问者模式来进行解析的,所以这个方法就是接受一个classVisitor,并且你也可以看到ClassParse4Asm继承了classVisitor
classReader.accept(parse, PARSING_OPTIONS);
//4.将当前的类解析完成后返回当前我们自定义封装的类的信息
MetaDataLuKP metaDataLuKP = parse.getMetaDataLuKP();
System.out.println("我自定义生成完成的MetaDataLuKP:" + metaDataLuKP);
}
}
/**
* @User lukuangpeng
* @Date 2023/8/9 22:09
* 该类是模拟了spring的MetaDataReader
**/
@Setter
@Getter
public class MetaDataLuKP {
//该list存放着一个类的注解信息
List<AnnotationDataLuKP> list = new ArrayList<>();
//当前解释的类
String className;
}
/**
* @User lukuangpeng
* @Date 2023/8/9 22:09
* 该类用来存放注解相关的信息
**/
@Builder
@Setter
@Getter
public class AnnotationDataLuKP {
//注解的类型
Class annotationType;
//来着那个类
String source;
//注解的名称
String name;
}
/**
* @User lukuangpeng
* @Date 2023/8/9 22:26
* 模仿spring中对类中的注解的解析器
**/
public class AnnotationVisitorParse4Asm extends AnnotationVisitor {
List<MergedAnnotation> list;
String sourceName;
//在构造器中完成当前类信息的解析
public AnnotationVisitorParse4Asm(String descriptor, List list, String sourceName) {
super(SpringAsmInfo.ASM_VERSION);
this.list = list;
this.sourceName = sourceName;
//使用spring提供的类信息解析器,来解析出当前的类的className是什么
String className = Type.getType(descriptor).getClassName();
try {
//在通过反射得到类的信息
Class<?> aClass = ClassUtils.forName(className, AnnotationVisitorParse4Asm.class.getClassLoader());
//创建我们定义的ClassAnnotation描述类
AnnotationDataLuKP.AnnotationDataLuKPBuilder annotationDataLuKP = AnnotationDataLuKP
.builder()
.annotationType(aClass)
.source(sourceName);
list.add(annotationDataLuKP);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void visit(String name, Object value) {
super.visit(name, value);
}
@Override
public void visitEnd() {
super.visitEnd();
}
}
**
* @User lukuangpeng
* @Date 2023/8/8 22:54
* 模仿spring中对类的解析器
**/
@Getter
@Setter
public class ClassParse4Asm extends ClassVisitor {
private MetaDataLuKP metaDataLuKP;
private String className;
private List<AnnotationDataLuKP> list;
public ClassParse4Asm() {
super(SpringAsmInfo.ASM_VERSION);
}
public MetaDataLuKP getMetaDataLuKP() {
return metaDataLuKP;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.className = name;
System.out.println("visit:" + name);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
//在该方法中只负责解析当前的类中有哪些注解,至于真正的当前类的解析其实是调用了AnnotationVisitor来完成的
System.out.println("visitAnnotation:" + descriptor);
return new AnnotationVisitorParse4Asm(descriptor, list, className);
}
@Override
public void visitEnd() {
//在该方法中完成对类的信息的封装-(MetaDataLuKP)
metaDataLuKP = new MetaDataLuKP();
metaDataLuKP.setClassName(className);
metaDataLuKP.setList(list);
}
}
下面我们回过头来看看spring是怎么做的,是如何对一个类进行解析的:
- 首先也还是进行指定包下的扫描使用的就是前面我们说过的scan()方法来扫描。
- 拼接好路径后,进行解析转换为一个类Resource。
- 当前肯定是一个集合存储着Resource,然后遍历的去加载这个集合。
- ok,现在开始解析第一个Resource,我们会调用spring提供的ASM解析能力,其实也就是一个方法getMetadataReader(),在他的构造器中放入当前的resource。
- 下面其实就用到观察者模式来完成对于一个类的spring形式的解析,比如我们上面的实现:
- 先解析一个类,然后定义描述一个类的字段信息,描述一个类的注解信息,描述一个类的type等等等的,都是spring对于一个类的描述。
- 然后通过一层一层的拨丝抽茧,完成spring对于类的信息的完成描述,最后返回一个metadataReader,提供给spring进行实例化Bean。
- Tips:spring在调用解析方法的时候会先创建一个CachingMetadataReaderFactory(),其实原因也很简单,看一个cache中有没有?有就直接拿来,没有就自己解析一个出来,并且put到缓存中,这个缓存其实是一个Map,并且是一个CurrentHashMap。
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
if (this.metadataReaderCache instanceof ConcurrentMap) {
// No synchronization necessary... 先去Cache中拿一下
MetadataReader metadataReader = this.metadataReaderCache.get(resource);
if (metadataReader == null) {
//如果没有的话就会调用父类的getMetadataReader()方法去会将当前resource,传入getMetadataReader方法获取metadataReader
metadataReader = super.getMetadataReader(resource);
//并且put到Cache中
this.metadataReaderCache.put(resource, metadataReader);
}
//不为空就直接返回
return metadataReader;
}
else if (this.metadataReaderCache != null) {
synchronized (this.metadataReaderCache) {
MetadataReader metadataReader = this.metadataReaderCache.get(resource);
if (metadataReader == null) {
metadataReader = super.getMetadataReader(resource);
this.metadataReaderCache.put(resource, metadataReader);
}
return metadataReader;
}
}
else {
return super.getMetadataReader(resource);
}
}
我们走进父类的方法中:
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
//如果在cache中没有就会调用到SimpleMetadataReader的解析器来解析
return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
}
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
//SimpleAnnotationMetadataReadingVisitor:该类用于将封装类解析之后的数据,
//我们可以看到下面有很多的Visitor其实就类似于我们自己简单实现了Annotation的解析
SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
getClassReader(resource).accept(visitor, PARSING_OPTIONS);
this.resource = resource;
this.annotationMetadata = visitor.getMetadata();
}
转载自:https://juejin.cn/post/7383100103001407498