spring:反射获取一个方法中的参数名(不是类型)。

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

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

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

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

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

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

方法getMethodParameters()是在HandlerMethod的类中

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的内部类,继承自MethodParameter

  构造方法调用:

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)

  所以需要通过类型查找参数名。

调试过程

反向调用过程:

  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、

最终回到数据绑定的方法。

补充

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

转载:https://www.cnblogs.com/guangshan/p/4660564.html

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring,定时任务可以使用注解或XML配置来实现。当定时任务执行时,Spring会根据配置信息来查找对应的bean,并执行bean方法。然而,在某些情况下,当定时任务执行时可能会出现反射异常方法未找到的问题。 反射异常方法未找到的原因可能有多种,例如定时任务的配置信息错误、bean的称或方法称错误、bean未被正确定义或加载等等。当定时任务执行时,Spring无法找到对应的bean或方法,就会抛出反射异常方法未找到的错误。 解决该问题的方法有以下几个步骤: 1. 首先,检查定时任务的配置信息是否正确,确保时间表达式以及所执行的bean称和方法称正确无误。 2. 检查对应的bean是否已经正确定义并被Spring容器加载。可以通过注解或XML配置来定义bean,并确保其在Spring容器被正确加载。 3. 确保所执行的方法在bean正确定义,并且方法称与配置信息称一致。检查方法参数和返回值是否与配置信息的要求一致。 4. 如果定时任务是通过注解方式配置的,可以尝试使用XML配置方式来进行配置,以确保配置的正确性。 5. 如果仍然无法解决问题,可以使用日志调试工具来获取详细的错误信息,帮助定位具体的问题所在。 总之,出现定时任务Spring bean反射异常方法未找到的问题,通常是因为配置信息或代码定义的错误所导致的。通过仔细检查配置信息、bean的定义和加载情况以及方法的正确性,可以解决该问题并正常执行定时任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值