java反射獲取Method的參數(shù)名稱(不是類型)

 一般來說,通過反射是很難獲得參數(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ù)名。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容