美文网首页
java反射获取Method的参数名称(不是类型)

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

作者: Saxon_323e | 来源:发表于2019-04-14 13:34 被阅读0次

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

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

      观察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()表示是否编译了参数名。

    相关文章

      网友评论

          本文标题:java反射获取Method的参数名称(不是类型)

          本文链接:https://www.haomeiwen.com/subject/mpgjwqtx.html