零、前言
java8 推出来的时候,新上了一个反射类Parameter
,可以通过这个类拿到方法的具体参数名称。但是,这个功能默认是关闭的,在使用javac 编译的时候,使用-parameters
参数,才能在getName()
的时候返回具体参数的名称,而不是类似arg0
…之类的。很多java框架都使用了该类的新特性,让组件使用起来更便利。但是,如果我们不了解这些,没有配置对应开关参数,或者不小心关闭了开关,可能会影响到框架正常使用,从而导致奇奇怪怪的未遇到过的问题。
这篇文章,主要是在一次重构过程中,对于原始代码的调整,导致mybatis框架使用时候的异常case分析,而产出的。
一、背景
最近在历史系统代码上做重构的时候,单元测试部分,使用junit5和jacoco来做单测覆盖。由于历史配置的问题,导致junit5的单测用例,在maven test
后都没有执行,后来发现是jacoco-maven-plugin
插件配置问题,然后stackoverflow
copy 了一份,单测运行ok了。
如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<dataFile>target/jacoco.exec</dataFile>
<!-- Sets the output directory for the code coverage report. -->
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
然后部署,测试功能,发现之前基于mybatis
的Dao interface
执行报错。如下:
1 | Caused by: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'namesCode' not found. Available parameters are [arg1, arg0, param1, param2] |
从上面的报错信息,可以明确知道,binding过程想从上下文拿到对应的namesCode
参数值,但是上下文的参数列表中,只有[arg1, arg0, param1, param2]
,这种没有语义含义的参数名。
回想整个过程中,除了增加各种单测用例之外,本次只是对pom文件中插件对了调整。为了先不影响提测,先人工加了mybatis @param
注解来解决,然后再来查问题。
二、发现&解决问题
先从 mybatis @param
注解入手,一般这种,应该要么是maven依赖版本降了不支持,要么就是其他pom配置改了。
然后,网上查了资料,mybatis 发现不使用 @param
的方式,就是用maven-compiler-plugin
,参考:https://blog.csdn.net/tangyaya8/article/details/90300554 。发现版本3.1比较低,然后改成3.8.0
,发现问题解决了。
因此,总结一下,就是对于java8而言,新增了Parameter
反射类,可以拿到方法参数名。但是,获取参数名默认是不打开的,需要设置打开下,例如下javac
时,新增 -parameters
参数,对于maven项目,可以通过maven-compiler-plugin
配置来完成。spring boot 的parent pom 中,已经把这个配置打开了,所以一般我们使用spring boot时,没有注意这个。如下:
1 | <plugin> |
具体插件信息:https://blog.csdn.net/ystyaoshengting/article/details/104038230 。从这份资料可以看到,插件版本在3.6.2
以上,才支持。
所以,将版本升级到3.8.0
之后,就支持了 spring-boot-starter-parent
中设置的默认打开parameters 开关。
三、java Parameter 反射类
接下来,我们看看Parameter
类。
先看看下面的使用示例:
1 | public class Test { |
javac 如果使用-parameters
,则打印的就是'ch' 'fromIndex'
,否则,就是'arg0' 'arg1'
。
针对maven项目,则是可以参考上面的maven插件设置。java 8 提供的Parameter
反射类,拿到方法入参名称,让很多工具的使用变得非常简单,不需要再通过注解指定名称,来映射了。
例如,前面说的 mybatis,此外,spring mvc中也有使用,例如我们用的PathVariable
,RequestParam
等。
四、mybatis 使用源码示例
从上面的mybatis报错信息,可以看到,主要的sql处理逻辑在MapperMethod
这个类中,找到对应的代码段,如下:
1 | public Object execute(SqlSession sqlSession, Object[] args) { |
具体逻辑在convertArgsToSqlCommandParam
方法中,主要是ParamNameResolver
类负责实现。看,该类的构造函数,负责解析:
1 | public ParamNameResolver(Configuration config, Method method) { |
下面就是最终的实现工具类,如果我们要使用的话,其实也可以通过这个工具类拿到名称列表,注意,只支持java8及以上:
1 |
|
五、总结
一个由于无意将maven-compiler-plugin
版本降低,导致mybatis 无法使用 Parameter
的反射特性,最终功能受影响的问题,这里就分析结束了。
在我们使用spring boot 框架之后,很多的细节,尤其是配置、依赖等,都被框架给隐藏了,因此,在使用的时候,还是需要不断的去倒腾,发现问题后,最终需要找到原因解决它。