SpringMVC的调用过程:
DispatcherServlet ——> 根据url找到相应的Controller,反射方式调用Controller相应的方法。
研究下面两种调用方式:
@RequestMapping("/baseType")
public User baseType(int count, long id) {
User user = new User();
user.setId(id);
user.setName("zhang");
return user;
}
@RequestMapping("/object")
public User object(User user) {
return user;
}
上述代码中,基本类型参数以及Java Bean参数,spring都能进行自动数据绑定。
对于第一种基本数据类型的,假如请求url为http://localhost:8080/user/baseType?count=2&id=5
,url参数能自动绑定到后端方法相应名字的参数,正如上面所说的,Controller的方法是反射调用的,因此通过反射取得的方法是不会保存方法参数的参数名的,那么在反射调用的时候是怎么能够对应参数名进行传递的呢?
通过debug方式阅读spring的源码(请求过程中debug,以及容器启动过程中debug),重点查看了 DefaultParameterNameDiscoverer
, ConstructorResolver
, AspectJAdviceParameterNameDiscoverer
, LocalVariableTableParameterNameDiscoverer
, PrioritizedParameterNameDiscoverer
这几个类发现,spring在启动的时候通过类似asm的方式(没采用asm库)从class文件中读取了方法的参数名,并保存到相应的对象缓存中。这里我还没有仔细研究,我的猜测是class文件是有保存方法参数的参数名的,另外我记得javaassist也是通过读取class文件获取方法的参数名的(但是自己用javac生成的class文件貌似没保存参数名)。后续需要研究一下原生java如何解析class文件内容。
在idea的java compiler中加入 -g:none ,再跑起来,调用url,会报错:
java.lang.IllegalArgumentException: Name for argument type [int] not available, and parameter name information not found in class file either.
由此说明,使用maven编译打包时回把方法的参数名信息加入class文件的,也就是说asm读取参数名字时从class文件获取的。
spring-core中有个ParameterNameDiscoverer就是用来获取参数名的,底层用的是asm解析,但是接口方法的参数名无法得到,即只能是非接口类的方法参数名可以
ParameterNameDiscoverer pnd=new DefaultParameterNameDiscoverer();
String[] parameterNames=pnd.getParameterNames(用反射获取到的方法对象);//返回的就是方法中的参数名列表了
Java运行时通过asm读取方法参数名:
asm获取参数名的原理??
java如何获取方法参数名:
/**
* javac -g:none ,加上这个参数就没有了
* —
* javac -g:vars
* 以及
* mvn clean install 都可以
*/
反射是取不到的,如果在编译时使用默认选项的话。javac带有-g:vars编译参数的话,局部变量及方法形式参数名会在字节码中保存着,但是Java代码是访问不到的。
如果需要抽取字节码中的形式参数名称需要自己去了解JVM字节码规范自己去解析!
对于第二种Java Bean参数自动绑定的,因为是是对象,并不需要上述的参数名,但是需要通过反射方式设值。通过查看源码,发现确实是通过反射方式设值的,但是,正如大家一直所强调的,反射的效率较低,能不用反射尽量别用,但是spring为什么频繁用反射,而且现在每一次请求都是通过反射来赋值,那岂不是效率很低?
通过进一步查看代码发现,虽然第一次请求同过反射方式生成方法,但是所生成的方法会被保存到相应的对象缓存,当请求再次发生,调用相同的方法时,方法并不需要重新通过反射生成,而是直接从缓存中取(其实就是Map或List),不过还是通过此方法代理调用。
其实无论是DispatcherServlet通过反射调用Controller的方法,还是JavaBean参数反射调用赋值,需要反射调用的方法都是只生成一次(之后保存在缓存),而反射调用才是多次重复的。所以我的理解是生成反射方法才是效率低的,而方法的代理调用并不会太影响性能。