我可以使用Javareflection获取方法参数名称吗?
如果我有这样的课程:
public class Whatever { public void aMethod(int aParam); }
有什么办法知道aMethod
使用一个名为aParam
,这是一个int
types的参数?
总结:
- 如果在编译期间包含debugging信息, 则可以获取参数名称。 看到这个答案的更多细节
- 否则获取参数名称是不可能的
- 获取参数types是可能的,使用
method.getParameterTypes()
为了编写编辑器的自动完成function(如您在其中一个注释中所述),有几个选项:
- 使用
arg0
,arg1
,arg2
等 - 使用
intParam
,stringParam
,objectTypeParam
等 - 使用上述的组合 – 前者用于非原始types,后者用于原始types。
- 根本不显示参数名称 – 只是types。
在Java 8中,您可以执行以下操作:
import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.List; public final class Methods { public static List<String> getParameterNames(Method method) { Parameter[] parameters = method.getParameters(); List<String> parameterNames = new ArrayList<>(); for (Parameter parameter : parameters) { if(!parameter.isNamePresent()) { throw new IllegalArgumentException("Parameter names are not present!"); } String parameterName = parameter.getName(); parameterNames.add(parameterName); } return parameterNames; } private Methods(){} }
所以对于你的class级我们可以做什么手动testing:
import java.lang.reflect.Method; public class ManualTest { public static void main(String[] args) { Method[] declaredMethods = Whatever.class.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { if (declaredMethod.getName().equals("aMethod")) { System.out.println(Methods.getParameterNames(declaredMethod)); break; } } } }
如果你已经向你的Java 8编译器传递了-parameters
参数,应该打印[aParam]
。
对于Maven用户:
<properties> <!-- PLUGIN VERSIONS --> <maven-compiler-plugin.version>3.1</maven-compiler-plugin.version> <!-- OTHER PROPERTIES --> <java.version>1.8</java.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin.version}</version> <configuration> <compilerArgument>-parameters</compilerArgument> <testCompilerArgument>-parameters</testCompilerArgument> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build>
有关更多信息,请参阅以下链接
- 官方Java教程:获取方法参数的名称
- JEP 118:运行时访问参数名称
- Javadoc参数类
Paranamer库被创build来解决这个相同的问题。
它尝试以几种不同的方式确定方法名称。 如果该类是通过debugging编译的,则可以通过读取该类的字节码来提取信息。
另一种方法是在编译之后,在将其放入jar之前,将私有静态成员注入到类的字节码中。 然后在运行时使用reflection来从类中提取这些信息。
https://github.com/paul-hammant/paranamer
我在使用这个库时遇到了问题,但最终我确实得到了它的工作。 我希望向维护者报告问题。
你可以用reflection检索方法,并检测它的参数types。 检查http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29
但是,你不能说出使用的参数的名字。
是。
代码必须使用符合Java 8的编译器进行编译,并带有选项以存储打开的正式参数名称( -parameters选项)。
那么这个代码片段应该工作:
Class<String> clz = String.class; for (Method m : clz.getDeclaredMethods()) { System.err.println(m.getName()); for (Parameter p : m.getParameters()) { System.err.println(" " + p.getName()); } }
虽然这是不可能的(正如其他人已经说明的那样),但是您可以使用注释来inheritance参数名称,并通过reflection来获得该名称。
不是最干净的解决scheme,但它完成了工作。 一些Web服务实际上这样做是为了保留参数名称(即:用glassfish部署WSs)。
这是可能的和Spring MVC 3做到这一点,但我没有花时间看看究竟如何。
方法参数名称与URI模板variables名称的匹配只能在代码编译时启用debugging的情况下完成。 如果没有启用debugging,则必须在@PathVariable注释中指定URI模板variables名称,以便将已parsing的variables名称的值绑定到方法参数。 例如:
采取从spring的文件
请参见java.beans.ConstructorProperties ,它是专门为此而devise的注释。
所以你应该能够做到:
Whatever.declaredMethods .find { it.name == 'aMethod' } .parameters .collect { "$it.type : $it.name" }
但是你可能会得到这样的列表:
["int : arg0"]
我相信这将在Groovy 2.5+中得到修复
所以目前的答案是:
- 如果这是一个Groovy类,那么不,你不能得到这个名字,但是你将来应该可以。
- 如果它是在Java 8下编译的Java类,则应该能够。
也可以看看:
- http://openjdk.java.net/jeps/118
- https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html
对于每种方法,然后是这样的:
Whatever.declaredMethods .findAll { !it.synthetic } .collect { method -> println method method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';') } .each { println it }
请参阅org.springframework.core.DefaultParameterNameDiscoverer类
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));
参数名只对编译器有用。 当编译器生成一个类文件时,不包含参数名称 – 一个方法的参数列表只包含其参数的数量和types。 所以这是不可能的使用reflection检索参数名称(如在你的问题标记) – 它不存在任何地方。
但是,如果reflection的使用不是一个困难的要求,你可以从源代码直接检索这个信息(假设你有这个)。
增加我的2美分; 当使用javac -g编译源文件时,参数信息在“debugging”类文件中可用。 它可以提供给APT,但你需要一个注释,所以对你没有用处。 (有人在4-5年前讨论过类似的东西: http : //forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )
总的来说,除非您直接在源文件上工作(类似于APT在编译时所做的工作),否则无法得到它。
如果使用eclipse,请参阅下图以允许编译器存储有关方法参数的信息
正如@Bozho所说,如果在编译过程中包含debugging信息,就可以这样做。 这里有个很好的答案
如何获取对象的构造函数(reflection)的参数名称? 由@AdamPaynter
使用ASM库 我举了一个例子来说明你如何实现你的目标。
首先,从具有这些依赖关系的pom.xml开始。
<dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-all</artifactId> <version>5.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
那么,这个class级应该做你想做的。 只需调用静态方法getParameterNames()
。
import org.objectweb.asm.ClassReader; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; public class ArgumentReflection { /** * Returns a list containing one parameter name for each argument accepted * by the given constructor. If the class was compiled with debugging * symbols, the parameter names will match those provided in the Java source * code. Otherwise, a generic "arg" parameter name is generated ("arg0" for * the first argument, "arg1" for the second...). * * This method relies on the constructor's class loader to locate the * bytecode resource that defined its class. * * @param theMethod * @return * @throws IOException */ public static List<String> getParameterNames(Method theMethod) throws IOException { Class<?> declaringClass = theMethod.getDeclaringClass(); ClassLoader declaringClassLoader = declaringClass.getClassLoader(); Type declaringType = Type.getType(declaringClass); String constructorDescriptor = Type.getMethodDescriptor(theMethod); String url = declaringType.getInternalName() + ".class"; InputStream classFileInputStream = declaringClassLoader.getResourceAsStream(url); if (classFileInputStream == null) { throw new IllegalArgumentException( "The constructor's class loader cannot find the bytecode that defined the constructor's class (URL: " + url + ")"); } ClassNode classNode; try { classNode = new ClassNode(); ClassReader classReader = new ClassReader(classFileInputStream); classReader.accept(classNode, 0); } finally { classFileInputStream.close(); } @SuppressWarnings("unchecked") List<MethodNode> methods = classNode.methods; for (MethodNode method : methods) { if (method.name.equals(theMethod.getName()) && method.desc.equals(constructorDescriptor)) { Type[] argumentTypes = Type.getArgumentTypes(method.desc); List<String> parameterNames = new ArrayList<String>(argumentTypes.length); @SuppressWarnings("unchecked") List<LocalVariableNode> localVariables = method.localVariables; for (int i = 1; i <= argumentTypes.length; i++) { // The first local variable actually represents the "this" // object if the method is not static! parameterNames.add(localVariables.get(i).name); } return parameterNames; } } return null; } }
这是一个unit testing的例子。
public class ArgumentReflectionTest { @Test public void shouldExtractTheNamesOfTheParameters3() throws NoSuchMethodException, SecurityException, IOException { List<String> parameterNames = ArgumentReflection .getParameterNames(Clazz.class.getMethod("callMe", String.class, String.class)); assertEquals("firstName", parameterNames.get(0)); assertEquals("lastName", parameterNames.get(1)); assertEquals(2, parameterNames.size()); } public static final class Clazz { public void callMe(String firstName, String lastName) { } } }
你可以在GitHub上find完整的例子
注意事项
- 我从@AdamPaynter稍微改变了原来的解决scheme,使它适用于方法。 如果我正确理解,他的解决scheme只适用于构造函数。
- 此解决scheme不适用于
static
方法。 这是因为在这种情况下,由ASM返回的参数数目是不同的,但它可以很容易地修复。