SpringMVC源码分析(六)--参数名称解析器

默认情况下编译时,不会带上方法参数名称,例如通过javac ./ParamNameResolverTest.java编译如下类

public class ParamNameResolverTest {public void test(String name, int age) {}
}

编译的结果如下:

public class ParamNameResolverTest {public ParamNameResolverTest() {}public void test(String var1, int var2) {}
}

SpringBoot在编译时会加上-parameters参数,即javac -parameters .\ParamNameResolverTest.java,会生成参数表,通过反射能够获取到方法参数名,编译后通过javap -c -v ./ParamNameResolverTest.java查看字节码,多了MethodParameters信息,如下:

public void test(java.lang.String, int);descriptor: (Ljava/lang/String;I)Vflags: ACC_PUBLICCode:stack=0, locals=3, args_size=30: returnLineNumberTable:line 6: 0MethodParameters:Name                           Flagsnameage

通过MethodParameters中的信息,就可以获取到参数名称

IDEA工具编译时会带上-g参数,即javac -g .\ParamNameResolverTest.java,如果是类,方法参数名会写入到局部变量表中,能够通过ASM获取到参数名,如果是接口,则无法获取到参数名

编译后通过javap -c -v ./ParamNameResolverTest.java查看字节码,多了LocalVariableTable信息,如下:

public void test(java.lang.String, int);descriptor: (Ljava/lang/String;I)Vflags: ACC_PUBLICCode:stack=0, locals=3, args_size=30: returnLineNumberTable:line 6: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       1     0  this   Lcom/limin/study/springmvc/A03/ParamNameResolverTest;0       1     1  name   Ljava/lang/String;0       1     2   age   I

通过LocalVariableTable信息也能够获取到参数名称

RequestMappingHandlerAdapter会创建DefaultParameterNameDiscoverer对象,它添加了两种解析器能够分别处理上述的两种情况,见DefaultParameterNameDiscoverer源码

public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {public DefaultParameterNameDiscoverer() {if (KotlinDetector.isKotlinReflectPresent() && !GraalDetector.inImageCode()) {this.addDiscoverer(new KotlinReflectionParameterNameDiscoverer());}// 通过反射获取方法参数名称this.addDiscoverer(new StandardReflectionParameterNameDiscoverer());// 通过局部变量表获取方法参数名称this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer());}
}

1)StandardReflectionParameterNameDiscoverer通过反射获取方法参数名称

public class StandardReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {@Override@Nullablepublic String[] getParameterNames(Method method) {return getParameterNames(method.getParameters());}@Override@Nullablepublic String[] getParameterNames(Constructor<?> ctor) {return getParameterNames(ctor.getParameters());}@Nullableprivate String[] getParameterNames(Parameter[] parameters) {String[] parameterNames = new String[parameters.length];for (int i = 0; i < parameters.length; i++) {Parameter param = parameters[i];// isNamePresent判断MethodParameters中的参数名是否存在,可查看JDK文档if (!param.isNamePresent()) {return null;}// 返回参数名称parameterNames[i] = param.getName();}return parameterNames;}
}

Parameter中的isNamePresent方法可以判断MethodParameters中的参数名是否存在,getName方法返回参数名称

2)LocalVariableTableParameterNameDiscoverer使用ASM通过局部变量表获取方法参数名称

public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer {@Override@Nullablepublic String[] getParameterNames(Method method) {// 如果是桥接方法返回原始方法,否则直接返回methodMethod originalMethod = BridgeMethodResolver.findBridgedMethod(method);return doGetParameterNames(originalMethod);}@Override@Nullablepublic String[] getParameterNames(Constructor<?> ctor) {return doGetParameterNames(ctor);}@Nullableprivate String[] doGetParameterNames(Executable executable) {Class<?> declaringClass = executable.getDeclaringClass();// 调用inspectClass读取字节码中的局部变量表Map<Executable, String[]> map = this.parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass);return (map != NO_DEBUG_INFO_MAP ? map.get(executable) : null);}private Map<Executable, String[]> inspectClass(Class<?> clazz) {// 获取类的字节码文件流InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));// 省略其他代码...try {ClassReader classReader = new ClassReader(is);Map<Executable, String[]> map = new ConcurrentHashMap<>(32);// 1.通过ASM读取字节码文件classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);return map;}// 省略其他代码...}private static class ParameterNameDiscoveringVisitor extends ClassVisitor {// 省略其他代码...@Override@Nullablepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {if (!isSyntheticOrBridged(access) && !STATIC_CLASS_INIT.equals(name)) {// 返回LocalVariableTableVisitor对象return new LocalVariableTableVisitor(this.clazz, this.executableMap, name, desc, isStatic(access));}return null;}}private static class LocalVariableTableVisitor extends MethodVisitor {// 省略其他代码...@Overridepublic void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) {this.hasLvtInfo = true;for (int i = 0; i < this.lvtSlotIndex.length; i++) {if (this.lvtSlotIndex[i] == index) {// 获取参数名称this.parameterNames[i] = name;}}}@Overridepublic void visitEnd() {if (this.hasLvtInfo || (this.isStatic && this.parameterNames.length == 0)) {// 方法处理结束时添加到executableMap中this.executableMap.put(resolveExecutable(), this.parameterNames);}}private Executable resolveExecutable() {ClassLoader loader = this.clazz.getClassLoader();// 获取参数类型Class<?>[] argTypes = new Class<?>[this.args.length];for (int i = 0; i < this.args.length; i++) {argTypes[i] = ClassUtils.resolveClassName(this.args[i].getClassName(), loader);}try {if (CONSTRUCTOR.equals(this.name)) {// 如果是构造器,则返回构造器方法return this.clazz.getDeclaredConstructor(argTypes);}// 否则则返回普通方法return this.clazz.getDeclaredMethod(this.name, argTypes);}catch (NoSuchMethodException ex) {throw new IllegalStateException("Method [" + this.name +"] was discovered in the .class file but cannot be resolved in the class object", ex);}}// 计算局部变量表中参数的索引private static int[] computeLvtSlotIndices(boolean isStatic, Type[] paramTypes) {int[] lvtIndex = new int[paramTypes.length];// 如果是静态方法则第一个slot没有this,否则第一个slot放thisint nextIndex = (isStatic ? 0 : 1);for (int i = 0; i < paramTypes.length; i++) {lvtIndex[i] = nextIndex;if (isWideType(paramTypes[i])) {nextIndex += 2;}else {nextIndex++;}}return lvtIndex;}// long和double占2个slotprivate static boolean isWideType(Type aType) {return (aType == Type.LONG_TYPE || aType == Type.DOUBLE_TYPE);}}
}

doGetParameterNames中会调用classReader.accept通过访问者模式读取字节码信息,在这个过程中会调用readMethod方法调用ParameterNameDiscoveringVisitor的methodVisitor方法创建LocalVariableTableVisitor对象

private int readMethod(final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) {    // 省略其他代码...// 调用classVisitor.visitMethod,此处返回的是LocalVariableTableVisitor对象MethodVisitor methodVisitor =classVisitor.visitMethod(context.currentMethodAccessFlags,context.currentMethodName,context.currentMethodDescriptor,signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer),exceptions);// 省略其他代码...if (codeOffset != 0) {methodVisitor.visitCode();// 读取字节码的各个部分readCode(methodVisitor, context, codeOffset);}// 调用visitEndmethodVisitor.visitEnd();// 省略其他代码...
}

随后调用readCode方法读取字节码的各个部分,而其中LocalVariableTableVisitor对象会访问visitLocalVariable局部变量表,从中获取其中的参数名称

private void readCode(final MethodVisitor methodVisitor, final Context context, final int codeOffset) {// 省略其他代码...int localVariableTableLength = readUnsignedShort(localVariableTableOffset);currentOffset = localVariableTableOffset + 2;while (localVariableTableLength-- > 0) {int startPc = readUnsignedShort(currentOffset);int length = readUnsignedShort(currentOffset + 2);String name = readUTF8(currentOffset + 4, charBuffer);String descriptor = readUTF8(currentOffset + 6, charBuffer);int index = readUnsignedShort(currentOffset + 8);currentOffset += 10;String signature = null;if (typeTable != null) {for (int i = 0; i < typeTable.length; i += 3) {if (typeTable[i] == startPc && typeTable[i + 1] == index) {signature = readUTF8(typeTable[i + 2], charBuffer);break;}}}// 访问局部变量表,调用LocalVariableTableVisitor中的visitLocalVariable方法methodVisitor.visitLocalVariable(name, descriptor, signature, labels[startPc], labels[startPc + length], index);}// 省略其他代码...
}

SpringMVC中通过DefaultParameterNameDiscoverer获取到方法参数名称后,可以进行日志打印、参数解析等,举个例子:Handler方法中有两个参数都被@RequestParam修饰

@Controller
public class Controller01 {@GetMapping("/test01")public void test01(@RequestParam String a2, @RequestParam String a1, HttpServletResponse response) throws IOException {System.out.println("a1: " + a1);System.out.println("a2: " + a2);response.getWriter().print("hello");}
}

浏览器发起http://127.0.0.1:8080/test01?a1=1&a2=2调用后,即可打印a1:1和a2:2,这是因为通过解析参数名称后,就可以通过名称进行匹配

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/780939.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript笔记 08

目录 01数组的遍历方法 02 清空数组的三种方式 03 Date 日期对象的创建和使用 04 Math数学对象 05 String字符串的常用方法 06 包装类的概述 01数组的遍历方法 数组有五中功能不同的遍历方法: 针对数组的每一个元素进行参数的回调函数 把当前元素作为第一实参 当前元素的…

java获取Date类型的年份

java获取Date类型的年份 Java获取Date类型的年份 在Java编程中&#xff0c;我们经常会涉及到日期和时间的操作。而获取一个Date类型对象的年份是其中的一个常见需求。本文将介绍如何使用Java获取Date类型的年份&#xff0c;并提供相应的代码示例。 Date类简介 在Java中&…

XSS 简述及解决

参考文章&#xff1a;https://www.writesoftwarewell.com/content-security-policy/ 什么是XSS XSS(Cross Site Scripting)即跨站脚本攻击&#xff0c;即当前站点加载并执行了来自其他站点的js&#xff0c;导致Cookie泄漏等安全问题。 怎么产生 多发生在允许用户填写并展示…

PHP定时任务框架taskPHP3.0的学习记录1(TaskPHP、执行任务类的实操代码实例)

TaskPHP是一个基于PHP的定时任务框架&#xff0c;它提供了一个简单、灵活且易于使用的解决方案&#xff0c;用于在PHP环境中执行定时任务。下面是对TaskPHP框架的简要介绍&#xff1a; 简单易用&#xff1a;TaskPHP的设计目标是让定时任务的创建和管理变得简单。通过简单的配置…

Learning To Count Everything

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;学习数一切东西1、研究背景2、提出方法3、模块详细3.1、多尺度特征提取模块3.2、密度预测模块 4、损失函数5、性能对比6、贡献 二…

城管智慧执法系统源码,基于微服务+java+springboot+vue开发

城管智慧执法系统源码&#xff0c;基于微服务javaspringbootvue开发 城管智慧执法系统源码有演示&#xff0c;自主研发&#xff0c;功能完善&#xff0c;正版授权&#xff0c;可商用上项目。 一套数字化的城管综合执法办案系统源码&#xff0c;提供了案件在线办理、当事人信用…

Platypus 一种集中式的央行数字货币方案

集中式的CBDC&#xff0c;混合使用账户模型和UTXO模型。 角色分类 中央银行&#xff1a;发行货币&#xff0c;交易验证&#xff0c;公开交易日志&#xff0c;防止双花。 不是完全受信任的&#xff0c;假定为会遵守监管要求&#xff0c;但可能会破坏交易隐私&#xff0c;即获…

正弦实时数据库(SinRTDB)的使用(9)-有损压缩

前文已经将正弦实时数据库的使用进行了介绍&#xff0c;需要了解的可以先看下面的博客&#xff1a; 正弦实时数据库(SinRTDB)的安装 正弦实时数据库(SinRTDB)的使用(1)-使用数据发生器写入数据 正弦实时数据库(SinRTDB)的使用(2)-接入OPC DA的数据 正弦实时数据库(SinRTDB)…

Kafka学习之:mac 上基础使用 python 来使用 kafka 的生产者和消费者进行数据处理

文章目录 前提python 环境配置Kafka 生产消费者模型生产者 producer检查当前存在的所有 topic / 是否自动创建 topic为什么 producer 要通过 key, value 来发布数据键&#xff08;Key&#xff09;值&#xff08;Value&#xff09; 消费者 consumerconsumer 得到的 message 有哪…

MES系统怎么解决车间生产调度难的问题?

MES系统三个层次 1、MES决定了生产什么&#xff0c;何时生产&#xff0c;也就是说它使公司保证按照订单规定日期交付准确的产品&#xff1b; 2、MES决定谁通过什么方式&#xff08;流程&#xff09;生产&#xff0c;即通过优化资源配置&#xff0c;最有效运用资源&#xff1b; …

关于SVG格式图片实现室内地图

SVG格式图片 可缩放矢量图形(Scalable Vector Graphics,SVG)基于 XML 标记语言,用于描述二维的矢量图形。 作为一个基于文本的开放网络标准,SVG 能够优雅而简洁地渲染不同大小的图形,并和 CSS、DOM、JavaScript 和 SMIL 等其他网络标准无缝衔接。本质上,SVG 相对于图像…

react学习总结(二)之案例分享

一.项目框架的搭建 1./src/pages下建不同的页面Header.jsx&#xff0c;About.jsx&#xff0c;Home.jsx&#xff0c; Message.jsx&#xff0c;News.jsx&#xff0c;Detail.jsx Header.jsx import React from react import { useNavigate } from react-router-domexport defau…

C++多线程:线程的创建、join、detach、joinable方法(二)

1、线程的开始与结束 程序运行起来&#xff0c;生成一个进程&#xff0c;该进程所持有的主线程开始自动运行&#xff0c;main主线程运行完所有的代码从main函数中返回表示整个进程运行完毕&#xff0c;标志着主线程和进程的死亡&#xff0c;等待操作系统回收资源&#xff0c;因…

nginx如何清理页面缓存

在 Nginx 中&#xff0c;清理页面缓存通常涉及配置缓存头以控制缓存行为&#xff0c;或者使用外部工具或机制来清除缓存。以下是一些建议来管理和清理 Nginx 的页面缓存&#xff1a; 配置缓存头&#xff1a; Nginx 本身不直接提供缓存机制&#xff0c;但可以通过配置 proxy_cac…

安全算法 - 国密算法

国密算法是中国自主研发的密码算法体系&#xff0c;包括对称加密算法、非对称加密算法和哈希算法。其中&#xff0c;国密算法采用SM4作为对称加密算法&#xff0c;SM2作为非对称加密算法&#xff0c;以及SM3作为哈希算法。国密算法在信息安全领域具有重要意义和广泛应用&#x…

Cocos Creator 常见问题记录

目录 问题1、精灵图九宫格&#xff0c;角度不拉伸 问题2、BlockInputEvents 防止透屏 问题1、精灵图九宫格&#xff0c;角度不拉伸 点击编辑&#xff0c;拖拽到可变区域 问题2、BlockInputEvents 防止透屏

【独立开发前线】Vol.26 【独立开发产品】吉光卡片-让你的文字变得酷炫起来

今天给大家分享一下 独立开发前线 社区成员张小吉 的作品 吉光卡片&#xff1b; 这是一款iOS的APP&#xff0c;下载&#xff1a;吉光卡片&#xff0c;主要功能是帮你制作酷炫的文字卡片&#xff0c;用精美的卡片让你的文字生动起来。 展示效果如下&#xff1a; 你可以用它制作…

【公示】2023年度青岛市级科技企业孵化器拟认定名单

根据《青岛市科技企业孵化器管理办法》&#xff08;青科规〔2023〕1号&#xff09;&#xff08;以下简称《管理办法》&#xff09;、《关于开展2023年度市级科技企业孵化器认定申报工作的通知》&#xff0c;经申报受理、区市推荐、形式审查、专家评审及现场核查等程序&#xff…

为何keil编译信息显示data使用量不是整数

在使用Keil软件进行嵌入式系统开发时&#xff0c;编译后显示的数据使用量&#xff08;Data Usage&#xff09;可能会以小数形式显示。这种情况通常是由以下几个原因造成的&#xff1a; 1.内存对齐&#xff1a;为了提高内存访问效率&#xff0c;编译器会对数据进行对齐处理。例…

【笔记】动⼿学深度学习(花书)|| Aston Zhang Mu Li Zachary C. LiptonAlexander J. Smola

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 前言 第一章 深度学习简介 第二章 P 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 系列文章目录前言本书…