【方向盘】为何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以下的版本,都是能够正常获取到的,因为它并不依赖编译参数~~~




相关文章
|
4天前
|
Java 数据库连接 数据库
spring和Mybatis的逆向工程
通过本文的介绍,我们了解了如何使用Spring和MyBatis进行逆向工程,包括环境配置、MyBatis Generator配置、Spring和MyBatis整合以及业务逻辑的编写。逆向工程极大地提高了开发效率,减少了重复劳动,保证了代码的一致性和可维护性。希望这篇文章能帮助你在项目中高效地使用Spring和MyBatis。
7 1
|
27天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
31 1
|
1月前
|
存储 安全 Java
|
28天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
27 1
|
1月前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
353 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
Java 关系型数据库 MySQL
springboot学习五:springboot整合Mybatis 连接 mysql数据库
这篇文章是关于如何使用Spring Boot整合MyBatis来连接MySQL数据库,并进行基本的增删改查操作的教程。
98 0
springboot学习五:springboot整合Mybatis 连接 mysql数据库
|
1月前
|
Java 数据库连接 API
springBoot:后端解决跨域&Mybatis-Plus&SwaggerUI&代码生成器 (四)
本文介绍了后端解决跨域问题的方法及Mybatis-Plus的配置与使用。首先通过创建`CorsConfig`类并设置相关参数来实现跨域请求处理。接着,详细描述了如何引入Mybatis-Plus插件,包括配置`MybatisPlusConfig`类、定义Mapper接口以及Service层。此外,还展示了如何配置分页查询功能,并引入SwaggerUI进行API文档生成。最后,提供了代码生成器的配置示例,帮助快速生成项目所需的基础代码。
|
24天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
22 0
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
57 2
下一篇
无影云桌面