反射获取一个方法中的参数名(不是类型) 傷城~ 2021-06-11 15:12 436阅读 0赞 [https://www.cnblogs.com/guangshan/p/4660564.html][https_www.cnblogs.com_guangshan_p_4660564.html] 一般来说,通过反射是很难获得参数名的,只能取到参数类型,因为在编译时,参数名有可能是会改变的,需要在编译时加入参数才不会改变。 使用注解是可以实现取类型名(或者叫注解名)的,但是要写注解,并不方便。 观察Spring mvc框架中的数据绑定,发现是可以直接把http请求中对应参数绑定到对应的参数名上的,他是怎么实现的呢? 先参考一下自动绑定的原理:[Spring源码研究:数据绑定][Spring] 在getMethodArgumentValues方法中,MethodParameter\[\] parameters = getMethodParameters();这一句取到方法的所有参数,MethodParameter类型中有方法名的属性,这个是什么类呢? 是spring核心中的一个类,org.springframework.core.MethodParameter,并不是通过反射实现的。 方法getMethodParameters()是在HandlerMethod的类中 public MethodParameter[] getMethodParameters() { return this.parameters; } this.parameters则是在构造方法中初始化的: public HandlerMethod(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(); } initMethodParameters()生成了参数列表。 private MethodParameter[] initMethodParameters() { int count = this.bridgedMethod.getParameterTypes().length; MethodParameter[] result = new MethodParameter[count]; for (int i = 0; i < count; i++) { result[i] = new HandlerMethodParameter(i); } return result; } HandlerMethodParameter(i)是HandlerMethod的内部类,继承自MethodParameter 构造方法调用: public HandlerMethodParameter(int index) { super(HandlerMethod.this.bridgedMethod, index); } 再调用MethodParameter类的构造方法: public MethodParameter(Method method, int parameterIndex, int nestingLevel) { Assert.notNull(method, "Method must not be null"); this.method = method; this.parameterIndex = parameterIndex; this.nestingLevel = nestingLevel; this.constructor = null; } MethodParameter类中有private String parameterName;储存的就是参数名,但是构造方法中并没有设置他的值,真正设置值是在: public String 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; } 而parameterNameDiscoverer就是用来查找名称的,他在哪里设置的值呢? public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) { this.parameterNameDiscoverer = parameterNameDiscoverer; } 这是个public方法,哪里调用了这个方法呢?有六七个地方吧,但是主要明显的是这里: private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[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); } throw ex; } } if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args; } 又回到了初始方法,这里面对ParameterNameDiscovery初始化,用来查找参数名: methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); this.parameterNameDiscoverer又是什么呢? private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 通过DefaultParameterNameDiscoverer类的实例来查找参数名。 /** * Default implementation of the {@link ParameterNameDiscoverer} strategy interface, * using the Java 8 standard reflection mechanism (if available), and falling back * to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking * debug information in the class file. * * <p>Further discoverers may be added through {@link #addDiscoverer(ParameterNameDiscoverer)}. * * @author Juergen Hoeller * @since 4.0 * @see StandardReflectionParameterNameDiscoverer * @see LocalVariableTableParameterNameDiscoverer */ public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { private static final boolean standardReflectionAvailable = (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_18); public DefaultParameterNameDiscoverer() { if (standardReflectionAvailable) { addDiscoverer(new StandardReflectionParameterNameDiscoverer()); } addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); } } 这个是类声明,因为java1.8是支持从反射获取参数名的(具体参考网络) 低于1.8时使用new LocalVariableTableParameterNameDiscoverer()来解析参数名。 其中有方法: public String[] getParameterNames(Method method) { Method originalMethod = BridgeMethodResolver.findBridgedMethod(method); Class<?> declaringClass = originalMethod.getDeclaringClass(); Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass); if (map == null) { map = inspectClass(declaringClass); this.parameterNamesCache.put(declaringClass, map); } if (map != NO_DEBUG_INFO_MAP) { return map.get(originalMethod); } return null; } 通过map = inspectClass(declaringClass);获取名称map。 private Map<Member, String[]> 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"); } return NO_DEBUG_INFO_MAP; } try { ClassReader classReader = new ClassReader(is); Map<Member, String[]> map = new ConcurrentHashMap<Member, String[]>(32); classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0); return map; } 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 } } return NO_DEBUG_INFO_MAP; } 这是方法。。。由此可见,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、![201333178327914.png][] classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0); classReader位于org.springframework.asm包中,是spring用于反编译的包,读取class信息,class信息中是包含参数名的(可以用文本编辑器打开一个class文件查看,虽然有乱码,但是方法的参数名还在) 通过accept填充map对象,map的键为成员名(方法名或者参数名),值为参数列表(字符串数组)。 2、![201336557235881.png][] 生成map之后,添加至参数名缓存,parameterNamesCache是以所在类的class为键,第一步的map为值的map。 3、![201341267852736.png][] 通过第一步的map获取方法中的参数名数组。 4、![201350449737607.png][] 通过调用本类parameterNameDiscoverer,再获取参数名的列表。 5、![201352165667825.png][] 6、![201352491443973.png][] 7、![201354131441510.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: public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { private static final boolean standardReflectionAvailable = (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_18); public DefaultParameterNameDiscoverer() { if (standardReflectionAvailable) { addDiscoverer(new StandardReflectionParameterNameDiscoverer()); } addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); } } 判断版本,因为java8可以通过反射获取参数名,但是需要使用-parameters参数开启这个功能 可以看到有两个StandardReflectionParameterNameDiscoverer、LocalVariableTableParameterNameDiscoverer 一个是通过标准反射来获取,一个是通过解析字节码文件的本地变量表来获取的。 Parameter对象是新的反射对象,param.isNamePresent()表示是否编译了参数名。 [https_www.cnblogs.com_guangshan_p_4660564.html]: https://www.cnblogs.com/guangshan/p/4660564.html [Spring]: http://www.cnblogs.com/guangshan/p/4431800.html [201333178327914.png]: /images/20210527/9f2b6eece44b4e47949502deb39011eb.png [201336557235881.png]: /images/20210527/118f330a5e3445ad9681da0c3305d4a9.png [201341267852736.png]: /images/20210527/f17c4704efd14a67aea59505887825c4.png [201350449737607.png]: /images/20210527/7d16762adbed4c7aa480dc67fb56f52a.png [201352165667825.png]: /images/20210527/1a7d1034247b49d99c5559dec87bace7.png [201352491443973.png]: /images/20210527/c21dd145f9ab4575be38c32a728e3420.png [201354131441510.png]: /images/20210527/c1d174c2d10d45bfb067edddc78eff07.png
相关 Java反射机制:获取类名、方法调用及参数检查 Java反射机制是一种强大的功能,它允许程序在运行时访问、检查和修改它自己的结构,包括类、接口、字段和方法。以下是如何使用Java反射机制来获取类名、调用方法以及检查方法参数的 男娘i/ 2024年11月06日 10:36/ 0 赞/ 3 阅读
相关 Java反射调用方法,参数类型不匹配的示例 在Java中,反射机制可以让你动态地获取类的信息,包括方法。但如果参数类型不匹配,反射调用方法会抛出`IllegalArgumentException`。 下面是一个简单的示 港控/mmm°/ 2024年09月11日 01:30/ 0 赞/ 56 阅读
相关 java 反射 获取方法参数_java 通过反射怎么获取方法中参数值 public T invokeMethod(String beanName, String methodName, Class>\[\] paramTypes, Object 淡淡的烟草味﹌/ 2022年11月02日 03:55/ 0 赞/ 356 阅读
相关 获取某个类的方法名及参数类型java import java.lang.reflect.Method; public class Test1 { public s 冷不防/ 2022年10月01日 06:41/ 0 赞/ 184 阅读
相关 intellij idea java8反射获取方法参数名 java8中的新特性可以直接通过反射获取方法的参数名, //通过类名获取class Class clazz=Class.forName(className) 逃离我推掉我的手/ 2022年07月16日 23:29/ 0 赞/ 206 阅读
相关 spring:反射获取一个方法中的参数名(不是类型)。 一般来说,通过反射是很难获得参数名的,只能取到参数类型,因为在编译时,参数名有可能是会改变的,需要在编译时加入参数才不会改变。 使用注解是可以实现取类型名(或者叫注解名)的, 古城微笑少年丶/ 2022年04月25日 01:24/ 0 赞/ 623 阅读
相关 springboot获取方法的参数名 在springboot中获取方法的参数名有两种方式 用来验证的方法,可以看到参数名有两个,一个test,一个num public String getString(Stri 向右看齐/ 2021年12月14日 01:57/ 0 赞/ 673 阅读
相关 Spring LocalVariableTableParameterNameDiscoverer获取方法的参数名 问题:Java.lang.reflect 包中提供了很多方法,获取所有的方法,获取所有的参数类型等,但是却没有一个方法能够帮助我们获取方法的参数名列表。 解决办法: 朱雀/ 2021年06月24日 15:59/ 0 赞/ 461 阅读
相关 反射获取一个方法中的参数名(不是类型) [https://www.cnblogs.com/guangshan/p/4660564.html][https_www.cnblogs.com_guangshan_p_466 傷城~/ 2021年06月11日 15:12/ 0 赞/ 437 阅读
还没有评论,来说两句吧...