跳转至

SpringMVC 为什么可以解析方法参数名称,MyBatis 不行?

结论

SpringMVC 能用 -parameters 编译标志(或 ASM 读取 LocalVariableTable)拿到方法参数名;MyBatis 默认拿不到,必须加 @Param 或开启编译参数保留。 根本原因是:Java 编译后默认不保留局部变量名,拿不拿得到取决于框架是否主动去 class 字节码里读。

一、Java 字节码里的参数名

Java 编译后的 class 文件,方法参数名默认被擦除(变成 arg0arg1)。要保留真实参数名,有两种方式:

方式 1:编译时加 -parameters

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <parameters>true</parameters>
    </configuration>
</plugin>

这样参数名写入 MethodParameters 属性(JDK 8+)。

方式 2:保留调试信息(LocalVariableTable)

编译加 -g,class 文件会带 LocalVariableTable,ASM 可以解析出参数名。IDEA 默认开启。

二、SpringMVC 怎么拿到参数名

SpringMVC 用 DefaultParameterNameDiscoverer,它聚合了多个发现器:

  1. StandardReflectionParameterNameDiscoverer:用 -parameters 编译后的 MethodParameters
  2. LocalVariableTableParameterNameDiscoverer:用 ASM 读 LocalVariableTable。

所以即使你不加 -parameters,只要 IDE 编译时带调试信息,SpringMVC 也能拿到。

@GetMapping("/user")
public User get(@RequestParam String name) { ... }
//              不加 value 也能识别 name,因为参数名被读取

三、MyBatis 为什么默认拿不到

MyBatis 3.4.1 之前不读取 LocalVariableTable,只看 @Param 注解。3.4.1 之后引入了 ParamNameResolver,但默认行为是:

  • 如果方法参数没加 @Param,且没开 -parameters,MyBatis 会用 arg0/arg1param1/param2
  • 在 IDE(带 -g)运行时,有时能拿到名字;打 jar 用 Maven 默认编译就拿不到。
// Mapper 接口
User selectBy(String name, Integer age);

<!-- XML  -->
<select id="selectBy" resultType="User">
    SELECT * FROM user WHERE name = #{name} AND age = #{age}
</select>
<!-- 报错找不到 name 属性要用 #{arg0} / #{param1} -->

四、解决方法

  1. @Param(最稳):
    User selectBy(@Param("name") String name, @Param("age") Integer age);
    
  2. 编译加 -parameters(全局生效)。
  3. MyBatis 配置:
    mybatis.configuration.use-actual-parameter-name=true
    

本质

不是 SpringMVC "魔法",而是它更早、更完整地封装了"读参数名"的逻辑;MyBatis 把这个责任推给了 @Param。理解到"字节码默认不保留参数名"这一层,答案就完整了。