【方向盘】为何Spring MVC可获取到方法参数名,而MyBatis却不行?(中)

简介: 【方向盘】为何Spring MVC可获取到方法参数名,而MyBatis却不行?(中)

获取方法参数名的3种方式介绍


虽然Java编译器默认情况下会抹去方法的参数名,但有上面介绍了字节码的相关知识可知,我们还是有方法来得到方法的参数名的。下面介绍3个方案,供以参考。


方法一:使用-parameters


最为简单直接的方式,Java8源生支持:直接从java.lang.reflect.Parameter就能获取到,形如这样:


public class MainTest2 {
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = MainTest2.class.getMethod("testArgName", String.class, Integer.class);
        System.out.println("paramCount:" + method.getParameterCount());
        for (Parameter parameter : method.getParameters()) {
            System.out.println(parameter.getType().getName() + "-->" + parameter.getName());
        }
    }
    public String testArgName(String name, Integer age) {
        return null;
    }
}


输出:

paramCount:2
java.lang.String-->name
java.lang.Integer-->age


当然,它有两个最大的弊端:

  1. 必须Java8或以上(由于java8已经普及率非常高了,所以这个还好)
  2. 编译参数必须有-parameters(由于依赖编译参数,所以对迁移是不太友好的,这点比较致命)


指定-parameters编译参数的方式:


  1. 手动命令方式编译:javac -parameters XXX.java
  2. IDE(以Idea为例)编译:

image.png


3.Maven编译:通过编译插件指定,保证项目迁移的正确性(推荐)

<!-- 编译环境在1.8编译 且附加编译参数:-parameters-->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <compilerArgs>
            <arg>-parameters</arg>
        </compilerArgs>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <compilerVersion>${java.version}</compilerVersion>
        <encoding>${project.build.sourceEncoding}</encoding>
    </configuration>
</plugin>


优点:简单方便

缺点:需要特别的指定-parameters,不太方便(当然使用maven编辑插件来指定是相对靠谱的方案且推荐使用)

方案二:使用-g + javap命令


如上例子可以使用javac -g编译后,再使用javap获取到字节码信息,然后自己根据信息的格式把参数名提取出来(自己做、自己做、自己做)


这无异于让你自己解析http协议一般,你愿意做吗???所以此办法虽为一种办法,但是显然不便采用

方案三:借助ASM(推荐)


说到ASM,小伙伴们至少对这个名字应该是不陌生的。它是一个Java字节码操控框架,它能被用来动态生成类或者增强既有类的功能,它能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。


对于ASM来说,Java class被描述为一棵树;使用 “Visitor”模式(导游模式)遍历整个二进制结构;事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分(比如本文只关注方法参数,其它的不关心),而不必了解 Java 类文件格式的所有细节 。


ASM方式,它仍旧还是基于编译后的字节码做事的,正所谓巧妇难为无米之炊,所以它仍旧必须依赖于编译时的LocalVariableTable(-g 参数)。看这里~


你可能会发问:我使用idea编译/maven编译都没有自己去指定-g参数啊,为什么也好使呢?你的疑问同样也是我的疑问,我至今也还没弄清楚更根本的原因,但是我可以说如下两个现象:


  1. idea默认使用的是javac编译器,编译出来的字节码是带有LocalVariableTable的。但你也可以关闭它,如下图:


image.png


2.maven默认使用的也是javac编译,字节码也带有LocalVariableTable(但是maven编译时候的编译命令、参数等,我无法获知。恳请精通maven的同学指点~)


小插曲:关于代理的科普(Proxy、CGLIB、Javassist、ASM ):


  1. ASM:Java字节码开源操控框架。操纵的级别是底层JVM的汇编指令级别,这就要求使用者对class组织结构和JVM汇编指令有一定的了解,要求颇高。
  2. Javassist:效果同上。相较于ASM它的特点是操作简单,并且速度还可以(当然没有ASM快)。重要的是:它并不要求你了解JVM指令/汇编指令~
  3. Proxy动态代理:动态生成(非提前编译好)代理类:$Proxy0 extends Proxy implements MyInterface{ ... },这就决定了它只能对接口(或者实现接口的类)进行代理,单继承机制也决定了它不能对(抽象)类进行代理~
  4. CGLIB:是一个基于ASM的强大的,高性能,高质量的字节码生成库。它可以在运行期扩展Java类与实现Java接口。


Spring AOP以及Hibernate对代理对象的创建中都使用了CGLIB



前面文章有介绍过了直接使用CGLIB的API来操作字节码/生成代理对象,本文将简单演示一下直接使用ASM框架来操作的示例


ASM使用示例


首先导入asm依赖包:

<!-- https://mvnrepository.com/artifact/asm/asm -->
<dependency>
    <groupId>asm</groupId>
    <artifactId>asm</artifactId>
    <version>3.3.1</version>
</dependency>


说明:asm现已升级到7.x版本了,并且GAV已变化。由于我对3.x熟悉点,所以此处我还是守旧吧~


基于ASM提供工具方法getMethodParamNames(Method),获取到任何一个Method的入参名称:

public class MainTest2 {
    // 拿到指定的Method的入参名们(返回数组,按照顺序返回)
    public static String[] getMethodParamNames(Method method) throws IOException {
        String methodName = method.getName();
        Class<?>[] methodParameterTypes = method.getParameterTypes();
        int methodParameterCount = methodParameterTypes.length;
        String className = method.getDeclaringClass().getName();
        boolean isStatic = Modifier.isStatic(method.getModifiers());
        String[] methodParametersNames = new String[methodParameterCount];
        // 使用org.objectweb.asm.ClassReader来读取到此方法
        ClassReader cr = new ClassReader(className);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        // 这一步是最红要的,开始visitor浏览了
        // ClassAdapter是org.objectweb.asm.ClassVisitor的子类~~~~
        cr.accept(new ClassAdapter(cw) {
            // 因为此处我们只关心对方法的浏览,因此此处只需要复写此方法即可
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                final Type[] argTypes = Type.getArgumentTypes(desc);
                // 只visitor方法名相同和参数类型都相同的方法~~~
                if (!methodName.equals(name) || !matchTypes(argTypes, methodParameterTypes)) {
                    return mv;
                }
                // 构造一个MethodVisitor返回 重写我们关心的方法visitLocalVariable~~~
                return new MethodAdapter(mv) {
                    //特别注意:如果是静态方法,第一个参数就是方法参数,非静态方法,则第一个参数是 this ,然后才是方法的参数
                    @Override
                    public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
                        // 处理静态方法与否~~
                        int methodParameterIndex = isStatic ? index : index - 1;
                        if (0 <= methodParameterIndex && methodParameterIndex < methodParameterCount) {
                            methodParametersNames[methodParameterIndex] = name;
                        }
                        super.visitLocalVariable(name, desc, signature, start, end, index);
                    }
                };
            }
        }, 0);
        return methodParametersNames;
    }
    /**
     * 比较参数是否一致
     */
    private static boolean matchTypes(Type[] types, Class<?>[] parameterTypes) {
        if (types.length != parameterTypes.length) {
            return false;
        }
        for (int i = 0; i < types.length; i++) {
            if (!Type.getType(parameterTypes[i]).equals(types[i])) {
                return false;
            }
        }
        return true;
    }
}


运行案例:

public class MainTest2 {
  // 使用工具方法获取Method的入参名字~~~
    public static void main(String[] args) throws SecurityException, NoSuchMethodException, IOException {
        Method method = MainTest2.class.getDeclaredMethod("testArgName", String.class, Integer.class);
        String[] methodParamNames = getMethodParamNames(method);
        // 打印输出
        System.out.println(StringUtils.arrayToCommaDelimitedString(methodParamNames));
    }
    private String testArgName(String name, Integer age) {
        return null;
    }
}


输出:

name,age


效果复合预期,使用ASM拿到了我们期望的真实的方法参数名(没有指定任何编译参数哦)。使用基于ASM的方式,即使你是Java8以下的版本,都是能够正常获取到的,因为它并不依赖编译参数~~~




相关文章
|
3月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
10月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
592 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
1021 0
|
10月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
612 0
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
532 0
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
429 0
|
6月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
461 0
|
6月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
197 0
|
6月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
303 0
|
9月前
|
缓存 安全 Java
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
913 5