首页 > 编程语言 >Spring 注解编程之 AnnotationMetadata

Spring 注解编程之 AnnotationMetadata

时间:2024-04-05 09:32:40浏览次数:19  
标签:String Spring 编程 content header AnnotationMetadata public ASM

Spring 注解编程之 AnnotationMetadata

这篇文章我们主要深入 AnnotationMetadata,了解其底层原理。

Spring 版本为 5.1.8-RELEASE

AnnotationMetadata 结构

使用 IDEA 生成 AnnotationMetadata 类图,如下:
在这里插入图片描述

AnnotationMetadata 存在两个实现类分别为 StandardAnnotationMetadata与 AnnotationMetadataReadingVisitor。StandardAnnotationMetadata主要使用 Java 反射原理获取元数据,而 AnnotationMetadataReadingVisitor 使用 ASM 框架获取元数据。
Java 反射原理大家一般比较熟悉,而 ASM 技术可能会比较陌生,下面主要篇幅介绍 AnnotationMetadataReadingVisitor 实现原理。

基于 AnnotationMetadata#getMetaAnnotationTypes方法,查看两者实现区别。

AnnotationMetadataReadingVisitor
ASM 是一个通用的 Java 字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。 ASM 虽然提供与其他 Java 字节码框架如 Javassist,CGLIB 类似的功能,但是其设计与实现小而快,且性能足够高。

Spring 直接将 ASM 框架核心源码内嵌于 Spring-core中,目前 Spring 5.1 使用 ASM 7 版本。

ASM框架简单应用

Java 源代码经过编译器编译之后生成了 .class 文件。

Class文件是有8个字节为基础的字节流构成的,这些字节流之间都严格按照规定的顺序排列,并且字节之间不存在任何空隙,对于超过8个字节的数据,将按 照Big-Endian的顺序存储的,也就是说高位字节存储在低的地址上面,而低位字节存储到高地址上面,其实这也是class文件要>跨平台的关键,因为 PowerPC架构的处理采用Big-Endian的存储顺序,而x86系列的处理器则采用Little-Endian的存储顺序,因此为了Class文 件在各中处理器架构下保持统一的存储顺序,虚拟机规范必须对起进行统一。

Class 文件中包含类的所有信息,如接口,字段属性,方法,在内部这些信息按照一定规则紧凑排序。ASM 框会以文件流的形式读取 class 文件,然后解析过程中使用观察者模式(Visitor),当解析器碰到相应的信息委托给观察者(Visitor)。使用 ASM 框架首先需要继承 ClassVisitor,完成解析相应信息,如解析方法,字段等。

import org.springframework.stereotype.Component;

@Component
public class Message {

    private Header header;

    private String content;

    public Message(Header header, String content) {
        this.header = header;
        this.content = content;
    }

    public Header getHeader() {
        return header;
    }

    public void setHeader(Header header) {
        this.header = header;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return String.format("[version=%d,contentLength=%d,sessionId=%s,content=%s]",
                header.getVersion(),
                header.getContentLength(),
                header.getSessiongId(),
                content);
    }
}

import com.lvyuanj.core.mytest.netty.model.Message;
import org.springframework.asm.*;


import java.io.IOException;

public class MyClassPrinter extends ClassVisitor {

    public MyClassPrinter() {
        super(7 << 16 | 0 << 8);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        System.out.println(name + " extends "+  superName + " {");
    }


    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        System.out.println("method:"+"    "+ name + descriptor);
        return null;
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        System.out.println("Annotation:"+"     "+ desc + "   ");
        return null;
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        System.out.println("field:"+"   "+ desc + "  "+ name + "  "+ Type.getType(desc).getClass());
        return null;
    }

    @Override
    public void visitEnd() {
        System.out.println("}");
    }

    public static void main(String[] args) throws IOException {
        MyClassPrinter myClassPrinter = new MyClassPrinter();
        ClassReader reader = new ClassReader(Message.class.getName());
        reader.accept(myClassPrinter, 0);
    }
}

然后使用 ClassReader 读取类文件,然后再使用 ClassReader#accpet 接受 ClassVisitor。
输出结果:

com/lvyuanj/core/mytest/netty/model/Message extends java/lang/Object {
Annotation:     Lorg/springframework/stereotype/Component;   
field:   Lcom/lvyuanj/core/mytest/netty/model/Header;  header  class org.springframework.asm.Type
field:   Ljava/lang/String;  content  class org.springframework.asm.Type
method:    <init>(Lcom/lvyuanj/core/mytest/netty/model/Header;Ljava/lang/String;)V
method:    getHeader()Lcom/lvyuanj/core/mytest/netty/model/Header;
method:    setHeader(Lcom/lvyuanj/core/mytest/netty/model/Header;)V
method:    getContent()Ljava/lang/String;
method:    setContent(Ljava/lang/String;)V
method:    toString()Ljava/lang/String;
}

可以看到 ClassVisitor 相应方法可以用来解析类的相关信息,这里我们主要关注解析类上注解信息。解析注解将会在 ClassVisitor#visitAnnotation完成解析。 该方法返回了一个 AnnotationVisitor 对象,其也是一个 Visitor 对象。后续解析器会继续调用 AnnotationVisitor内部方法进行再次解析。

以上实现采用 ASM Core API ,而 ASM 框架还提供 Tree API 用法。具体用法参考:https://asm.ow2.io/

AnnotationMetadataReadingVisitor#getMetaAnnotationTypes 源码解析

AnnotationMetadataReadingVisitor#getMetaAnnotationTypes 方法实现非常简单,直接从 metaAnnotationMap 根据注解类名称获取其上面所有元注解。注解相关信息解析由 AnnotationMetadataReadingVisitor#visitAnnotation 完成。

    @Override
	public Set<String> getMetaAnnotationTypes(String annotationName) {
		Set<String> metaAnnotationTypes = this.metaAnnotationMap.get(annotationName);
		return (metaAnnotationTypes != null ? metaAnnotationTypes : Collections.emptySet());
	}

在 visitAnnotation 方法中,metaAnnotationMap当做构造参数传入了 AnnotationAttributesReadingVisitor 对象中,metaAnnotationMap会在这里面完成赋值。

    @Override
	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		String className = Type.getType(desc).getClassName();
		this.annotationSet.add(className);
		return new AnnotationAttributesReadingVisitor(
				className, this.attributesMap, this.metaAnnotationMap, this.classLoader);
	}

AnnotationAttributesReadingVisitor#visitEnd 将会排除 java.lang.annotation 下的注解,然后通过递归调用 recursivelyCollectMetaAnnotations获取元注解,不断将元注解置入 metaAnnotationMap中。

public void visitEnd() {
		super.visitEnd();

		Class<? extends Annotation> 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);
			}
			if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) {
				try {
					Annotation[] metaAnnotations = annotationClass.getAnnotations();
					if (!ObjectUtils.isEmpty(metaAnnotations)) {
						Set<Annotation> visited = new LinkedHashSet<>();
						for (Annotation metaAnnotation : metaAnnotations) {
							recursivelyCollectMetaAnnotations(visited, metaAnnotation);
						}
						if (!visited.isEmpty()) {
							Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
							for (Annotation ann : visited) {
								metaAnnotationTypeNames.add(ann.annotationType().getName());
							}
							this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
						}
					}
				}
				catch (Throwable ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Failed to introspect meta-annotations on " + annotationClass + ": " + ex);
					}
				}
			}
		}
	}
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);
				}
			}
		}
	}

最后使用 UML 时序图中,概括以上调用流程。
在这里插入图片描述

Spring 4 之后版本才有递归查找元注解的方法。各位同学可以翻阅 Spring3 的版本作为比较,可以看出 Spring 的代码功能也是逐渐迭代升级的。

StandardAnnotationMetadata

StandardAnnotationMetadata 主要使用 Java 反射原理获取相关信息。在 Spring 中封装很多了反射工具类用于操作。StandardAnnotationMetadata#getMetaAnnotationTypes 通过使用 Spring 工具类 AnnotatedElementUtils.getMetaAnnotationTypes方法获取。源码调用比较清晰,各位同学可以自行翻阅理解,可以参考下面时序图理解,这里不再叙述。
在这里插入图片描述

        StandardAnnotationMetadata standardAnnotationMetadata = new StandardAnnotationMetadata(Message.class);
        MergedAnnotations annotations = standardAnnotationMetadata.getAnnotations();
        annotations.forEach(o-> System.out.println(o.getType()));

结果:

interface org.springframework.stereotype.Component
interface org.springframework.stereotype.Indexed
总结

本文介绍了 AnnotationMetadata两种实现方案,一种基于 Java 反射,另一种基于 ASM 框架。两种实现方案适用于不同场景。StandardAnnotationMetadata 基于 Java 反射,需要加载类文件。而 AnnotationMetadataReadingVisitor基于 ASM 框架无需提前加载类,所以适用于 Spring 应用扫描指定范围内模式注解时使用。

标签:String,Spring,编程,content,header,AnnotationMetadata,public,ASM
From: https://blog.csdn.net/lvyuanj/article/details/137391999

相关文章

  • Java项目:基于Springboot+vue实现的医院住院管理系统设计与实现(源码+数据库+开题报告+
    一、项目简介本项目是一套基于Springboot+vue实现的医院住院管理系统设包含:项目源码、数据库脚本等,该项目附带全部源码可作为毕设使用。项目都经过严格调试,eclipse或者idea确保可以运行!该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值......
  • [转帖]shell编程-流程控制-if语句
    1.单分支if条件语句单分支if条件语句有三种写法:if[条件判断式];then操作fi或者if[条件判断式]then操作fi或者if[条件判断式];then操作;fi说明:只有条件成立,才执行相应的操作。示例:if[aa==aa];thenechoyes;fi1注意要点:if语句使用......
  • 一键部署 SpringCloud 微服务,这套流程值得学习一波儿!
    文章目录前言1、开发者将代码PUSH到Git2、通过Jenkins部署,自动到Git上PULL代码2.1、配置SSH-KEY2.1.1、生成/添加SSH公钥2.1.2、将公钥配置到git平台2.1.3、测试2.2、配置Jenkins的pipeline自动clone代码2.2.1、Jenkins创建任务2.2.2、测试拉代码流程3、通过maven......
  • 【附源码】计算机毕业设计智慧社区团购系统的设计(java+springboot+mysql+mybatis+论文
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义随着互联网技术的发展和普及,社区团购作为一种新兴的电商模式,正逐渐改变着人们的购物习惯。然而,传统的社区团购系统存在着一些问题,如信息不透明、效率低下、用户体......
  • 【附源码】计算机毕业设计游戏分享网站(java+springboot+mysql+mybatis+论文)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义随着互联网技术的发展,游戏行业正逐渐向数字化、网络化方向发展。越来越多的游戏玩家开始通过网络分享自己的游戏心得、攻略和视频等内容,形成了一个庞大的游戏分享......
  • 【附源码】计算机毕业设计在线药品销售系统(java+springboot+mysql+mybatis+论文)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义随着互联网技术的不断发展,人们的生活方式也在逐渐改变。在药品销售领域,传统的线下药店已经不能满足人们的需求。在线药品销售系统应运而生,为人们提供了一个更加便......
  • 【附源码】计算机毕业设计中医保健网站(java+springboot+mysql+mybatis+论文)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义中医保健网站是一个提供中医养生、保健知识的在线平台。随着人们生活水平的提高,越来越多的人开始关注自己的身体健康,而中医作为中国传统医学的一种,具有悠久的历史......
  • 【附源码】计算机毕业设计长护险管理系统的设计与实现(java+springboot+mysql+mybatis+
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义长护险管理系统是一种基于互联网技术的信息化管理平台,旨在提高长期护理保险(简称“长护险”)的管理效率和服务质量。随着人口老龄化的加剧和社会保障体系的完善,长护......
  • 基于springboot实现社区医院信息平台系统项目【项目源码+论文说明】
    基于springboot实现社区医院信息平台系统演示摘要随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了社区医院信息平台的开发全过程。通过分析社区医院信息平台管理的不足,创建了一个计算机管理社区医院信息平台的方案。文章介......
  • 基于springboot实现足球青训俱乐部管理后台系统项目【项目源码+论文说明】
    基于springboot实现足球青训俱乐部管理系统演示摘要随着社会经济的快速发展,人们对足球俱乐部的需求日益增加,加快了足球健身俱乐部的发展,足球俱乐部管理工作日益繁忙,传统的管理方式已经无法满足足球俱乐部管理需求,因此,为了提高足球俱乐部管理效率,足球俱乐部管理后台系统应......