反射獲取一個方法中的參數名(不是類型)

  通常來講,經過反射是很難得到參數名的,只能取到參數類型,由於在編譯時,參數名有多是會改變的,須要在編譯時加入參數纔不會改變。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()表示是否編譯了參數名。

相關文章
相關標籤/搜索