ejemplo definicion anotaciones java reflection

definicion - ¿Puedo obtener el nombre del parámetro del método utilizando la reflexión de Java?



jtextpane definicion (14)

Si tengo una clase como esta:

public class Whatever { public void aMethod(int aParam); }

¿Hay alguna forma de saber que aMethod usa un parámetro llamado aParam , que es de tipo int ?


Como @Bozho declaró, es posible hacerlo si se incluye información de depuración durante la compilación. Hay una buena respuesta aquí ...

¿Cómo obtener los nombres de los parámetros de los constructores de un objeto (reflejo)? por @AdamPaynter

... usando la biblioteca ASM. Puse un ejemplo que muestra cómo puedes lograr tu objetivo.

En primer lugar, comience con un pom.xml con estas dependencias.

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

Entonces, esta clase debe hacer lo que quieras. Solo invoque el método estático 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; } }

Aquí hay un ejemplo con una prueba de unidad.

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) { } } }

Puedes encontrar el ejemplo completo en GitHub

Advertencias

  • Cambié ligeramente la solución original de @AdamPaynter para que funcione en Métodos. Si entendí correctamente, su solución solo funciona con constructores.
  • Esta solución no funciona con métodos static . Esto se debe a que en este caso el número de argumentos devueltos por ASM es diferente, pero es algo que se puede arreglar fácilmente.

En Java 8 puedes hacer lo siguiente:

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(){} }

Entonces, para su clase, Whatever que Whatever que podamos hacer una prueba manual:

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; } } } }

que debería imprimir [aParam] si ha pasado el argumento -parameters al compilador de Java 8.

Para los usuarios de 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> <!-- Original answer --> <compilerArgument>-parameters</compilerArgument> <!-- Or, if you use the plugin version >= 3.6.2 --> <parameters>true</parameters> <testCompilerArgument>-parameters</testCompilerArgument> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build>

Para obtener más información, consulte los siguientes enlaces:

  1. Tutorial oficial de Java: obtención de nombres de los parámetros del método
  2. JEP 118: Acceso a nombres de parámetros en tiempo de ejecución
  3. Javadoc para la clase de parámetro

Entonces deberías poder hacer:

Whatever.declaredMethods .find { it.name == ''aMethod'' } .parameters .collect { "$it.type : $it.name" }

Pero probablemente obtendrá una lista como esta:

["int : arg0"]

Creo que esto se solucionará en Groovy 2.5+

Entonces, actualmente, la respuesta es:

  • Si se trata de una clase de Groovy, entonces no, no puedes obtener el nombre, pero deberías poder hacerlo en el futuro.
  • Si se trata de una clase Java compilada en Java 8, debería poder hacerlo.

Ver también:

Para cada método, algo como:

Whatever.declaredMethods .findAll { !it.synthetic } .collect { method -> println method method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join('';'') } .each { println it }


La biblioteca de Paranamer fue creada para resolver este mismo problema.

Intenta determinar los nombres de los métodos de diferentes maneras. Si la clase se compiló con la depuración, puede extraer la información leyendo el bytecode de la clase.

Otra forma es inyectar un miembro estático privado en el bytecode de la clase una vez compilado, pero antes de colocarlo en un jar. A continuación, utiliza la reflexión para extraer esta información de la clase en tiempo de ejecución.

https://github.com/paul-hammant/paranamer

Tuve problemas para usar esta biblioteca, pero finalmente funcioné. Espero informar los problemas al mantenedor.


Los nombres de los parámetros solo son útiles para el compilador. Cuando el compilador genera un archivo de clase, los nombres de los parámetros no están incluidos; la lista de argumentos de un método solo consiste en el número y los tipos de sus argumentos. Por lo tanto, sería imposible recuperar el nombre del parámetro mediante la reflexión (como se indica en la pregunta); no existe en ningún lado.

Sin embargo, si el uso de la reflexión no es un requisito difícil, puede recuperar esta información directamente del código fuente (suponiendo que lo tenga).


Para agregar mis 2 centavos; la información del parámetro está disponible en un archivo de clase "para la depuración" cuando utiliza javac -g para compilar el origen. Y está disponible para APT, pero necesitará una anotación para que no le sirva. (Alguien discutió algo similar hace 4 o 5 años aquí: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

En resumen, no puede obtenerlo a menos que trabaje directamente en archivos de origen (similar a lo que hace APT en tiempo de compilación).


Para resumir:

  • obtener nombres de parámetros es posible si se incluye información de depuración durante la compilación. Vea esta respuesta para más detalles
  • de lo contrario, obtener los nombres de los parámetros no es posible
  • obtener el tipo de parámetro es posible, usando method.getParameterTypes()

Para poder escribir la funcionalidad de autocompletar para un editor (como dijiste en uno de los comentarios) hay algunas opciones:

  • use arg0 , arg1 , arg2 , etc.
  • use intParam , stringParam , objectTypeParam , etc.
  • utilice una combinación de los anteriores: el primero para los tipos no primitivos y el último para los tipos primitivos.
  • no muestre nombres de argumento en absoluto, solo los tipos.


Sí.
El código debe compilarse con el compilador compatible con Java 8 con la opción de almacenar nombres de parámetros formales activados (opción -parameters ).
Entonces este fragmento de código debería funcionar:

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


Si bien no es posible (como otros han ilustrado), puede usar una anotación para llevar el nombre del parámetro y obtener esa reflexión.

No es la solución más limpia, pero hace el trabajo bien. Algunos servicios web realmente hacen esto para mantener los nombres de los parámetros (es decir, implementar WS con glassfish).



si usa el eclipse, consulte la imagen siguiente para permitir que el compilador almacene la información sobre los parámetros del método


vea la clase org.springframework.core.DefaultParameterNameDiscoverer

DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));


Es posible y Spring MVC 3 lo hace, pero no me tomé el tiempo para ver exactamente cómo.

La coincidencia de nombres de parámetros de métodos con nombres de variables de plantillas de URI solo se puede realizar si el código está compilado con la depuración habilitada. Si no tiene habilitada la depuración, debe especificar el nombre del nombre de la variable de plantilla de URI en la anotación @PathVariable para vincular el valor resuelto del nombre de la variable a un parámetro de método. Por ejemplo:

Tomado de la documentación de primavera