java反射获取Method的参数名称(不是类型)

 一般来说,通过反射是很难获得参数名的,只能取到参数类型,因为在编译时,参数名有可能是会改变的,需要在编译时加入参数才不会改变。

  使用注解是可以实现取类型名(或者叫注解名)的,但是要写注解,并不方便。

  观察Spring mvc框架中的数据绑定,发现是可以直接把http请求中对应参数绑定到对应的参数名上的,他是怎么实现的呢?

先参考一下自动绑定的原理:Spring源码研究:数据绑定

  在getMethodArgumentValues方法中,MethodParameter[] parameters = getMethodParameters();这一句取到方法的所有参数,MethodParameter类型中有方法名的属性,这个是什么类呢?

  是spring核心中的一个类,org.springframework.core.MethodParameter,并不是通过反射实现的。

  方法getMethodParameters()是在HandlerMethod的类中

public MethodParameter[] getMethodParameters() {

        returnthis.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

  构造方法调用:

publicHandlerMethodParameter(int index) {

            super(HandlerMethod.this.bridgedMethod, index);

}

  再调用MethodParameter类的构造方法:

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;储存的就是参数名,但是构造方法中并没有设置他的值,真正设置值是在:

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就是用来查找名称的,他在哪里设置的值呢?

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(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初始化,用来查找参数名:

  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());

    }

}

低于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、

    classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);

    classReader位于org.springframework.asm包中,是spring用于反编译的包,读取class信息,class信息中是包含参数名的(可以用文本编辑器打开一个class文件查看,虽然有乱码,但是方法的参数名还在)

    通过accept填充map对象,map的键为成员名(方法名或者参数名),值为参数列表(字符串数组)。

2、

    生成map之后,添加至参数名缓存,parameterNamesCache是以所在类的class为键,第一步的map为值的map。

3、

    通过第一步的map获取方法中的参数名数组。

4、

    通过调用本类parameterNameDiscoverer,再获取参数名的列表。

5、

6、

7、

最终回到数据绑定的方法

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

对于外部使用,spring又提供了DefaultParameterNameDiscoverer来兼容调用

一个是通过标准反射来获取,一个是通过解析字节码文件的本地变量表来获取的。

Parameter对象是新的反射对象,param.isNamePresent()表示是否编译了参数名。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354