为了不耽误看客的时间,文长图多也为了不让你搞混,这里先声明本文的总结要点,如下:
IDE不开启生成debugging info数据
到class字节码
文件时,IDE原生
的编译
和启动
不会为@RequestParam
等HTTP请求注解生成name属性
,即使用这些注解不标注name属性时,会报错。
凡是Maven
编译的class字节码文件,默认均有debugging info数据
,所以项目即使不为@RequestParam注解标注name属性,也能正常使用。
Spring
有两种策略来获取方法参数名称
JDK8或以上
,通过编译选项-parameters
,在class文件中写入可供原生Java API反射调用
来获取方法参数名称的元数据
JDK7或以下
,通过第三方工具包ASM
[ASM项目主页],来解析class
文件,获取方法参数名称。注意该工具需要开启通用的编译选项-g
,表示生成调试信息
到class文件-parameters
与-g
有区别,不是功能冗余的实现,-parameters使得原生Java API能读取class文件的方法参数名称,这在JDK7及之前是没办法用原生Java API实现的
为了不乱了方向,提示一下,本文将出现Java原生Javac
,SpringBoot代码
,Maven使用
,和IDEA开发工具
共四者关系
本文基本上不是为了解决问题,因为平常能遇到这样的情况,机会很小很小,更多的是希望能为你带来一些小小知识!多图慎入
项目里面使用了SpringBoot(版本是2.0.0.M2,不是2.0.0.RELEASE
),有一次在倒腾了开发软件IDEA
之后,发现IDEA启动的项目运行失败了(记住这个IDEA启动~
),原本正常的Controller接口的某个方法,突然在请求时,报出如下错误:
项目代码没动,就只是倒腾了IDEA,重装了一下,你就给我看这个?
搜某度,得其解,仅需开启某配置项(generate debugging info
,Eclipse也有对应的配置,名字不同)如下图所示即可(这配置模式的确是开启的,只是重装IDEA后发现是关的了~
)
按道理来说,项目能否正常,跟IDE是不能有关系的,确实,能出现这种问题,本身代码就是有问题了,经排查,使用SpringWeb的@PathVariable
注解时,没有赋予name
属性,即写法如下
相信有不少人在接触到一些教程的时候,看到了不少说name
属性是可写可不写的,这个实际上不对,有Spring官方文档如下:
文档说明了,在Spring能获取到方法参数名称的情况下,这个name
就可以不写,Spring自动匹配。实际上不只是@PathVariable
注解,Controller处理HTTP传参的注解如@RequestParam
等都要注意,Spring源码中获取不到参数名称,而且又没有指定的情况下,就会在下图位置抛出异常
而实现了该接口的参数解析类,有如下图所示,这其实表示我们常用的@MatrixVariable
、@RequestHeader
都是需要注意name
属性给值的。
那如何判断注解里面的name
加不加有没有影响呢?最简单的方法,直接反编译class文件,如果反编译后的java代码,你能看到参数名称是对的,那说明你的项目和配置里面,name
属性基本可以不加的
Spring的文档写的是JDK8的时候可以通过参数
-parameters
来给字节码文件加上参数名称,实际上,这个参数是JDK8及之后才有的,难道JDK7就不行吗?JDK7也是可以的,用的参数-g
,而且这个参数更加通用,是用来生成调试信息的。后面会聊到
突然一想不太对,-parameters是JDK8及以上才有的参数,难道JDK7就不能自动解析方法参数名称了吗?
可以的!
两种方法变量名收集方式,ASM
对比JDK8
.
说明一下,
java.lang.reflect.Executable
是JDK8及以上
才新增的类,因此Spring通过这种方式来判断当前使用的JDK是哪个版本
(仅判断是否是JDK8或以上
)。但要注意一点,根据这段代码来看,是只有JDK8或以上版本时,才新增加Java reflect包的方法
获取参数名称的数据,否则统一都是ASM工具包获取方法参数名称的,并不是说JDK8就不用ASM
。
为什么SpringBoot的这个方法要这样写呢?猜测如下:
2.0.0.RELEASE或以上
在POM默认加上了-parameters
参数(限JDK8及以上使用
),所以如果Maven编译时被去掉了-g
选项,那么也可以通过这个-parameters
来获取方法参数,即StandardReflect方式(参数加在哪里,后面会聊到
)通用
获取方法参数名称的实现,不限制JDK版本,只需要编译时生成调试信息
,因此该方法作为必选方案为什么Maven编译的,统统都行?
实践发现,只要是用maven编译了项目,那不管什么JDK版本,name属性加不加都行的!
很好解释,可以在maven的compile命令加上-X
选项,表示输出debug信息,这时候就可以看到,maven编译的时候,默认是加上-g
选项的,也就是默认生成调试信息,那这时候,Spring的ASM
工具包就一定能获取到方法参数名称了。(但千万注意,Maven只是默认加上-g,要去掉还是可以的
)
多余之举?
有个显微地方,估计使用SpringBoot的开发很少注意到,就是SpringBoot的2.0.0.RELEASE版本及以上
,在引用的spring-boot-starter-parent-2.0.0.RELEASE.pom
文件里面,有如下配置:
即SpringBoot的2.0.0.RELEASE版本开始,已经默认会在maven编译时加上-parameters
了,那问题来了,JDK7不支持-parameters
呀~,是的!SpringBoot的2.0.0.RELEASE也不支持JDK7
了
但是这个并不是多余之举。考虑到Maven只是默认加上-g
,也可以去掉该参数,这时候就可以发现,加上-parameters
是个明智之举了!
-parameters跟-g有啥子区别?
-parameters是JDK8及以后新增的编译器选项,根据JEP-118
交付的功能,如下描述
而-g是javac各版本均有的编译器选项,-g是用来生成调试信息的,所以很明显,-parameters肯定是提供了一些新的功能实现,而这个功能实现就是原生Java API能正常通过reflect包的工具来读取class文件的方法参数名称,而调试信息却没办法
。
idea迷惑行为之-g不识别,-g:vars就识别
。在提出了前面的结论后,急需实践验证,为了方便,使用IDEA的编译来完成实践。有以下几点需要提前说明:
javac
命令,而是用的Java Compiler API
,但即使如此,也可以通过build.log
来查看编译代码时候的参数/Users/这里是用户名/Library/Logs/JetBrains/这里是IDEA目录/build-log
默认则可以通过菜单:帮助->Show Log in Finder/ Explorer,这个目录下能找到build-log
修改后需要重启IDEA
): log4j.rootLogger=debug, file
tail -f build.log | grep --line-buffer Compiling
2020-07-09 10:44:34,914 [ 1010] DEBUG - s.incremental.java.JavaBuilder -
Compiling chunk [debuggerinfotest] with options:
"-deprecation -g:vars -encoding UTF-8 -source 8 -target 8 -s /Users/newcih/Programs/IdeaProjects/debuggerinfotest/target/generated-sources/annotations",
mode=in-process
IDEA的迷惑行为就在于,如果你添加了编译器选项为-g
,那实际上编译时是不会使用的,而添加了-g:vars
等等部分调试信息的选项,就能正常加入到编译中。
考虑到IDEA有
generate debugging info
的选项,可以猜测IDEA是把-g
选项统一管控了,因此自定义加上-g或不加-g并不生效,而是只能通过该界面开关来控制
如何给IDEA添加编译选项?打开菜单:Preference -> 构建,执行,部署 -> 编译器 -> Java编译器即可,如下图:
如果你顺利看到这里,没有被绕晕,那其实挺祝贺你的,因为这篇文章只是用来标记一些少见的情况,且基本是开发过程中才会遇到的,但最重要的是,希望能给你带来一些开发方面的小小知识!