一般來說,通過反射是很難獲得參數(shù)名的,只能取到參數(shù)類型,因為在編譯時,參數(shù)名有可能是會改變的,需要在編譯時加入?yún)?shù)才不會改變。
使用注解是可以實現(xiàn)取類型名(或者叫注解名)的,但是要寫注解,并不方便。
觀察Spring mvc框架中的數(shù)據(jù)綁定,發(fā)現(xiàn)是可以直接把http請求中對應(yīng)參數(shù)綁定到對應(yīng)的參數(shù)名上的,他是怎么實現(xiàn)的呢?
先參考一下自動綁定的原理:Spring源碼研究:數(shù)據(jù)綁定
在getMethodArgumentValues方法中,MethodParameter[] parameters = getMethodParameters();這一句取到方法的所有參數(shù),MethodParameter類型中有方法名的屬性,這個是什么類呢?
是spring核心中的一個類,org.springframework.core.MethodParameter,并不是通過反射實現(xiàn)的。
方法getMethodParameters()是在HandlerMethod的類中
public MethodParameter[] getMethodParameters() {
? ? ? ? returnthis.parameters;
? ? }
this.parameters則是在構(gòu)造方法中初始化的:
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()生成了參數(shù)列表。
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的內(nèi)部類,繼承自MethodParameter
構(gòu)造方法調(diào)用:
publicHandlerMethodParameter(int index) {
? ? ? ? ? ? super(HandlerMethod.this.bridgedMethod, index);
}
再調(diào)用MethodParameter類的構(gòu)造方法:
publicMethodParameter(Method method,intparameterIndex,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;儲存的就是參數(shù)名,但是構(gòu)造方法中并沒有設(shè)置他的值,真正設(shè)置值是在:
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;
? ? ? ? }
? ? ? ? returnthis.parameterName;
? ? }
? 而parameterNameDiscoverer就是用來查找名稱的,他在哪里設(shè)置的值呢?
public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) {
? ? ? ? this.parameterNameDiscoverer = parameterNameDiscoverer;
}
這是個public方法,哪里調(diào)用了這個方法呢?有六七個地方吧,但是主要明顯的是這里:
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
? ? ? ? ? ? Object... providedArgs) throws Exception {
? ? ? ? MethodParameter[] parameters = getMethodParameters();
? ? ? ? Object[] args =new Object[parameters.length];
? ? ? ? for(inti = 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);
? ? ? ? ? ? ? ? thrownew IllegalStateException(msg);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return args;
? ? }
又回到了初始方法,這里面對ParameterNameDiscovery初始化,用來查找參數(shù)名:
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
this.parameterNameDiscoverer又是什么呢?
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
通過DefaultParameterNameDiscoverer類的實例來查找參數(shù)名。
/**
* 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());
? ? }
}
低于1.8時使用new LocalVariableTableParameterNameDiscoverer()來解析參數(shù)名。
其中有方法:
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文件來讀取參數(shù)名的。。。。。。。。。。。。真累
反射的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)
所以需要通過類型查找參數(shù)名。
調(diào)試過程:反向調(diào)用過程:
1、

classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);
classReader位于org.springframework.asm包中,是spring用于反編譯的包,讀取class信息,class信息中是包含參數(shù)名的(可以用文本編輯器打開一個class文件查看,雖然有亂碼,但是方法的參數(shù)名還在)
通過accept填充map對象,map的鍵為成員名(方法名或者參數(shù)名),值為參數(shù)列表(字符串?dāng)?shù)組)。
2、

生成map之后,添加至參數(shù)名緩存,parameterNamesCache是以所在類的class為鍵,第一步的map為值的map。
3、

通過第一步的map獲取方法中的參數(shù)名數(shù)組。
4、

通過調(diào)用本類parameterNameDiscoverer,再獲取參數(shù)名的列表。
5、

6、

7、

最終回到數(shù)據(jù)綁定的方法
2016年6月6日11:45:59補充:
@Aspect的注解里面,參數(shù)有一個叫做JoinPoint的,這個JoinPoint里面也可以獲取參數(shù)名:
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
MethodSignature有方法:public String[] getParameterNames()
實現(xiàn)在: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可以通過反射獲取參數(shù)名,但是需要使用-parameters參數(shù)開啟這個功能
可以看到有兩個StandardReflectionParameterNameDiscoverer、LocalVariableTableParameterNameDiscoverer
對于外部使用,spring又提供了DefaultParameterNameDiscoverer來兼容調(diào)用
一個是通過標(biāo)準(zhǔn)反射來獲取,一個是通過解析字節(jié)碼文件的本地變量表來獲取的。
Parameter對象是新的反射對象,param.isNamePresent()表示是否編譯了參數(shù)名。