java 反射方法 参数类型转换_反射获取一个方法中的参数名(不是类型)(转)

Spring框架能够直接绑定HTTP请求参数到方法的参数名上,这得益于其内部的HandlerMethod和MethodParameter类。Spring通过DefaultParameterNameDiscoverer在Java8及以上版本使用标准反射获取参数名,否则使用ASM解析字节码。在反射获取参数名时,如果无法通过反射,它会尝试从class文件的局部变量表获取信息。
摘要由CSDN通过智能技术生成

一般来说,通过反射是很难获得参数名的,只能取到参数类型,因为在编译时,参数名有可能是会改变的,需要在编译时加入参数才不会改变。

使用注解是可以实现取类型名(或者叫注解名)的,但是要写注解,并不方便。

观察Spring mvc框架中的数据绑定,发现是可以直接把http请求中对应参数绑定到对应的参数名上的,他是怎么实现的呢?

在getMethodArgumentValues方法中,MethodParameter[] parameters = getMethodParameters();这一句取到方法的所有参数,MethodParameter类型中有方法名的属性,这个是什么类呢?

是spring核心中的一个类,org.springframework.core.MethodParameter,并不是通过反射实现的。

方法getMethodParameters()是在HandlerMethod的类中

publicMethodParameter[] getMethodParameters() {

return this.parameters;

}

this.parameters则是在构造方法中初始化的:

48304ba5e6f9fe08f3fa1abda7d326ab.png

publicHandlerMethod(Object bean, Method method) {

Assert.notNull(bean, "Bean is required");

Assert.notNull(method, "Method is required");

this.bean =bean;

this.beanFactory = null;

this.method =method;

this.bridgedMethod =BridgeMethodResolver.findBridgedMethod(method);

this.parameters =initMethodParameters();

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

initMethodParameters()生成了参数列表。

48304ba5e6f9fe08f3fa1abda7d326ab.png

privateMethodParameter[] initMethodParameters() {

int count = this.bridgedMethod.getParameterTypes().length;

MethodParameter[] result = newMethodParameter[count];

for (int i = 0; i < count; i++) {

result[i] = newHandlerMethodParameter(i);

}

returnresult;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

HandlerMethodParameter(i)是HandlerMethod的内部类,继承自MethodParameter

构造方法调用:

public HandlerMethodParameter(intindex) {

super(HandlerMethod.this.bridgedMethod, index);

}

再调用MethodParameter类的构造方法:

48304ba5e6f9fe08f3fa1abda7d326ab.png

public MethodParameter(Method method, int parameterIndex, intnestingLevel) {

Assert.notNull(method, "Method must not be null");

this.method =method;

this.parameterIndex =parameterIndex;

this.nestingLevel =nestingLevel;

this.constructor = null;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

MethodParameter类中有private String parameterName;储存的就是参数名,但是构造方法中并没有设置他的值,真正设置值是在:

48304ba5e6f9fe08f3fa1abda7d326ab.png

publicString getParameterName() {

if (this.parameterNameDiscoverer != null) {

String[] parameterNames = (this.method != null ?

this.parameterNameDiscoverer.getParameterNames(this.method) :

this.parameterNameDiscoverer.getParameterNames(this.constructor));

if (parameterNames != null) {

this.parameterName = parameterNames[this.parameterIndex];

}

this.parameterNameDiscoverer = null;

}

return this.parameterName;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

而parameterNameDiscoverer就是用来查找名称的,他在哪里设置的值呢?

public voidinitParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) {

this.parameterNameDiscoverer =parameterNameDiscoverer;

}

这是个public方法,哪里调用了这个方法呢?有六七个地方吧,但是主要明显的是这里:

48304ba5e6f9fe08f3fa1abda7d326ab.png

privateObject[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,

Object... providedArgs) throwsException {

MethodParameter[] parameters =getMethodParameters();

Object[] args = newObject[parameters.length];

for (int i = 0; i < parameters.length; i++) {

MethodParameter parameter =parameters[i];

parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);

GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());

args[i] =resolveProvidedArgument(parameter, providedArgs);

if (args[i] != null) {

continue;

}

if (this.argumentResolvers.supportsParameter(parameter)) {

try{

args[i] = this.argumentResolvers.resolveArgument(

parameter, mavContainer, request, this.dataBinderFactory);

continue;

}

catch(Exception ex) {

if(logger.isTraceEnabled()) {

logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);

}

throwex;

}

}

if (args[i] == null) {

String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);

throw newIllegalStateException(msg);

}

}

returnargs;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

又回到了初始方法,这里面对ParameterNameDiscovery初始化,用来查找参数名:

methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);

this.parameterNameDiscoverer又是什么呢?

private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

通过DefaultParameterNameDiscoverer类的实例来查找参数名。

48304ba5e6f9fe08f3fa1abda7d326ab.png

/*** Default implementation of the {@linkParameterNameDiscoverer} strategy interface,

* using the Java 8 standard reflection mechanism (if available), and falling back

* to the ASM-based {@linkLocalVariableTableParameterNameDiscoverer} for checking

* debug information in the class file.

*

*

Further discoverers may be added through {@link#addDiscoverer(ParameterNameDiscoverer)}.

*

* @authorJuergen Hoeller

* @since4.0

* @seeStandardReflectionParameterNameDiscoverer

* @seeLocalVariableTableParameterNameDiscoverer

*/

public class DefaultParameterNameDiscoverer extendsPrioritizedParameterNameDiscoverer {

private static final boolean standardReflectionAvailable =(JdkVersion.getMajorJavaVersion() >=JdkVersion.JAVA_18);

publicDefaultParameterNameDiscoverer() {

if(standardReflectionAvailable) {

addDiscoverer(newStandardReflectionParameterNameDiscoverer());

}

addDiscoverer(newLocalVariableTableParameterNameDiscoverer());

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

这个是类声明,因为java1.8是支持从反射获取参数名的(具体参考网络)

低于1.8时使用new LocalVariableTableParameterNameDiscoverer()来解析参数名。

其中有方法:

48304ba5e6f9fe08f3fa1abda7d326ab.png

publicString[] getParameterNames(Method method) {

Method originalMethod =BridgeMethodResolver.findBridgedMethod(method);

Class> declaringClass =originalMethod.getDeclaringClass();

Map map = this.parameterNamesCache.get(declaringClass);

if (map == null) {

map =inspectClass(declaringClass);

this.parameterNamesCache.put(declaringClass, map);

}

if (map !=NO_DEBUG_INFO_MAP) {

returnmap.get(originalMethod);

}

return null;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

通过map = inspectClass(declaringClass);获取名称map。

48304ba5e6f9fe08f3fa1abda7d326ab.png

private Map inspectClass(Class>clazz) {

InputStream is =clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));

if (is == null) {

//We couldn't load the class file, which is not fatal as it

//simply means this method of discovering parameter names won't work.

if(logger.isDebugEnabled()) {

logger.debug("Cannot find '.class' file for class [" +clazz

+ "] - unable to determine constructors/methods parameter names");

}

returnNO_DEBUG_INFO_MAP;

}

try{

ClassReader classReader = newClassReader(is);

Map map = new ConcurrentHashMap(32);

classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);

returnmap;

}

catch(IOException ex) {

if(logger.isDebugEnabled()) {

logger.debug("Exception thrown while reading '.class' file for class [" + clazz +

"] - unable to determine constructors/methods parameter names", ex);

}

}

catch(IllegalArgumentException ex) {

if(logger.isDebugEnabled()) {

logger.debug("ASM ClassReader failed to parse class file [" + clazz +

"], probably due to a new Java class file version that isn't supported yet " +

"- unable to determine constructors/methods parameter names", ex);

}

}

finally{

try{

is.close();

}

catch(IOException ex) {

//ignore

}

}

returnNO_DEBUG_INFO_MAP;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

这是方法。。。由此可见,spring是直接读取class文件来读取参数名的。。。。。。。。。。。。真累

反射的method类型为public java.lang.String com.guangshan.data.DataPoolController.addData(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.String,org.springframework.ui.Model)

所以需要通过类型查找参数名。

调试过程:反向调用过程:

1、

a616041b5445d6e27dd74547c0e73c63.png

classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);

classReader位于org.springframework.asm包中,是spring用于反编译的包,读取class信息,class信息中是包含参数名的(可以用文本编辑器打开一个class文件查看,虽然有乱码,但是方法的参数名还在)

通过accept填充map对象,map的键为成员名(方法名或者参数名),值为参数列表(字符串数组)。

2、

a497b5e9cdc93e70773efbe487cd04e6.png

生成map之后,添加至参数名缓存,parameterNamesCache是以所在类的class为键,第一步的map为值的map。

3、

baa30029c39b44b870239005903acf3a.png

通过第一步的map获取方法中的参数名数组。

4、

5de8dd0f53d33ecf8a1ac2488ae5e8c4.png

通过调用本类parameterNameDiscoverer,再获取参数名的列表。

5、

caafa5340919c3e5442ca3e31f8f803d.png

6、

cb7357a4bb6a037c3be32b8d118f2344.png

7、

65505d984ec7c59522a6f1631616de09.png

最终回到数据绑定的方法

2016年6月6日11:45:59补充:

@Aspect的注解里面,参数有一个叫做JoinPoint的,这个JoinPoint里面也可以获取参数名:

Signature signature = joinPoint.getSignature();

MethodSignature methodSignature = (MethodSignature) signature;

MethodSignature有方法:public String[] getParameterNames()

实现在:MethodInvocationProceedingJoinPoint类的MethodSignatureImpl类中:

this.parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());

parameterNameDiscoverer是:DefaultParameterNameDiscoverer:

48304ba5e6f9fe08f3fa1abda7d326ab.png

public class DefaultParameterNameDiscoverer extendsPrioritizedParameterNameDiscoverer {

private static final boolean standardReflectionAvailable =(JdkVersion.getMajorJavaVersion() >=JdkVersion.JAVA_18);

publicDefaultParameterNameDiscoverer() {

if(standardReflectionAvailable) {

addDiscoverer(newStandardReflectionParameterNameDiscoverer());

}

addDiscoverer(newLocalVariableTableParameterNameDiscoverer());

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

判断版本,因为java8可以通过反射获取参数名,但是需要使用-parameters参数开启这个功能

可以看到有两个StandardReflectionParameterNameDiscoverer、LocalVariableTableParameterNameDiscoverer

一个是通过标准反射来获取,一个是通过解析字节码文件的本地变量表来获取的。

Parameter对象是新的反射对象,param.isNamePresent()表示是否编译了参数名。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值