通常來講,經過反射是很難得到參數名的,只能取到參數類型,由於在編譯時,參數名有多是會改變的,須要在編譯時加入參數纔不會改變。html
使用註解是能夠實現取類型名(或者叫註解名)的,可是要寫註解,並不方便。java
觀察Spring mvc框架中的數據綁定,發現是能夠直接把http請求中對應參數綁定到對應的參數名上的,他是怎麼實現的呢?spring
先參考一下自動綁定的原理:Spring源碼研究:數據綁定數組
在getMethodArgumentValues方法中,MethodParameter[] parameters = getMethodParameters();這一句取到方法的全部參數,MethodParameter類型中有方法名的屬性,這個是什麼類呢?緩存
是spring核心中的一個類,org.springframework.core.MethodParameter,並非經過反射實現的。網絡
方法getMethodParameters()是在HandlerMethod的類中mvc
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的內部類,繼承自MethodParameteride
構造方法調用:
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)
因此須要經過類型查找參數名。
調試過程:反向調用過程:
一、
classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);
classReader位於org.springframework.asm包中,是spring用於反編譯的包,讀取class信息,class信息中是包含參數名的(能夠用文本編輯器打開一個class文件查看,雖然有亂碼,可是方法的參數名還在)
經過accept填充map對象,map的鍵爲成員名(方法名或者參數名),值爲參數列表(字符串數組)。
二、
生成map以後,添加至參數名緩存,parameterNamesCache是以所在類的class爲鍵,第一步的map爲值的map。
三、
經過第一步的map獲取方法中的參數名數組。
四、
經過調用本類parameterNameDiscoverer,再獲取參數名的列表。
五、
六、
七、
最終回到數據綁定的方法
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()表示是否編譯了參數名。