web-dev-qa-db-fra.com

Puis-je obtenir le nom du paramètre de méthode en utilisant la réflexion Java?

Si j'ai un cours comme celui-ci:

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

est-il possible de savoir que aMethod utilise un paramètre nommé aParam, qui est de type int?

107
Geo

Résumer:

  • obtenir les noms de paramètre est possible si des informations de débogage sont incluses lors de la compilation. Voir cette réponse pour plus de détails
  • sinon, obtenir les noms de paramètre est pas possible
  • obtenir le type de paramètre est possible, en utilisant method.getParameterTypes()

Pour pouvoir écrire des fonctionnalités de saisie semi-automatique pour un éditeur (comme vous l'avez indiqué dans l'un des commentaires), il existe quelques options:

  • utilisez arg0, arg1, arg2 etc.
  • utilisez intParam, stringParam, objectTypeParam, etc.
  • utilisez une combinaison de ce qui précède - le premier pour les types non primitifs et le second pour les types primitifs.
  • ne montre pas du tout les noms d'arguments - seulement les types.
79
Bozho

En Java 8, vous pouvez effectuer les opérations suivantes:

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

Donc pour votre classe Whatever nous pouvons faire un test manuel:

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

qui devrait imprimer [aParam] si vous avez passé l'argument -parameters à votre compilateur Java 8.

Pour les utilisateurs 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>

Pour plus d'informations, voir les liens suivants:

  1. Tutoriel officiel Java: Obtention des noms de paramètres de méthode
  2. PEC 118: Accès aux noms de paramètres au moment de l'exécution
  3. Javadoc pour la classe Parameter
83
lpandzic

La bibliothèque Paranamer a été créée pour résoudre ce même problème.

Il essaie de déterminer les noms de méthodes de différentes manières. Si la classe a été compilée avec le débogage, elle peut extraire les informations en lisant le bytecode de la classe.

Une autre méthode consiste à injecter un membre statique privé dans le bytecode de la classe après sa compilation, mais avant son placement dans un fichier jar. Il utilise ensuite la réflexion pour extraire ces informations de la classe lors de l'exécution.

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

J'avais des problèmes pour utiliser cette bibliothèque, mais je l'ai finalement fait fonctionner. J'espère signaler les problèmes au responsable.

14
Sarel Botha

Oui.
Code doit être compilé avec un compilateur conforme à Java 8 avec option permettant de stocker les noms de paramètres formels activés ( -parameters option).
Ensuite, cet extrait de code devrait fonctionner:

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());
   }
}
8
Karol Król

voir la classe org.springframework.core.DefaultParameterNameDiscoverer

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

Vous pouvez récupérer la méthode avec réflexion et détecter ses types d'argument. Vérifiez http://Java.Sun.com/j2se/1.4.2/docs/api/Java/lang/reflect/Method.html#getParameterTypes%28%29

Cependant, vous ne pouvez pas dire le nom de l'argument utilisé.

5
Johnco

C'est possible et Spring MVC 3 le fait, mais je n'ai pas pris le temps de voir exactement comment.

La correspondance des noms de paramètre de méthode to URI Les noms de variable de modèle peuvent. être fait que si votre code est compilé avec le débogage activé. Si vous avez pas de débogage activé, vous devez spécifiez le nom du modèle d'URI nom de variable dans la variable @PathVariable annotation afin de lier le valeur résolue du nom de la variable à un paramètre de méthode. Par exemple:

Tiré de la documentation printanière

3
flybywire

Bien que cela ne soit pas possible (comme d’autres l’ont illustré), vous pourriez utiliser une annotation pour reporter le nom du paramètre et obtenir ce résultat par réflexion.

Ce n’est pas la solution la plus propre, mais le travail est fait. Certains services Web le font en fait pour conserver les noms de paramètres (par exemple, le déploiement de WS avec Glassfish).

3
WhyNotHugo

Voir Java.beans.ConstructorProperties , c'est une annotation conçue pour faire exactement cela.

3
Markus Jevring

Donc, vous devriez pouvoir faire:

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

Mais vous obtiendrez probablement une liste comme celle-ci:

["int : arg0"]

Je pense que cela sera corrigé dans Groovy 2.5+

Donc, actuellement, la réponse est:

  • Si c'est une classe Groovy, alors non, vous ne pouvez pas obtenir le nom, mais vous devriez pouvoir le faire à l'avenir.
  • S'il s'agit d'une classe Java compilée sous Java 8, vous devriez pouvoir le faire.

Voir également:


Pour chaque méthode, quelque chose comme:

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

Pour ajouter mes 2 cents; Les informations de paramètre sont disponibles dans un fichier de classe "pour le débogage" lorsque vous utilisez javac -g pour compiler la source. Et il est disponible pour APT = mais vous aurez besoin d'une annotation, donc aucune utilisation pour vous. (Quelqu'un a discuté d'un sujet similaire il y a 4-5 ans ici: http://forums.Java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Globalement, vous ne pouvez l'obtenir que si vous travaillez directement sur les fichiers source (comme ce que APT fait au moment de la compilation).

0
Elister

Comme @Bozho l'a déclaré, il est possible de le faire si des informations de débogage sont incluses lors de la compilation ..__ Il y a une bonne réponse ici ...

Comment obtenir les noms de paramètres des constructeurs d'un objet (réflexion)? par @ AdamPaynter 

... en utilisant la bibliothèque ASM. J'ai rassemblé un exemple montrant comment vous pouvez atteindre votre objectif.

Tout d’abord, commencez par pom.xml avec ces dépendances.

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

Ensuite, cette classe devrait faire ce que vous voulez. Invoquez simplement la méthode statique 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;
    }
}

Voici un exemple avec un test unitaire.

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

    }
}

Vous pouvez trouver l'exemple complet sur GitHub

Mises en garde

  • J'ai légèrement modifié la solution originale de @AdamPaynter pour la faire fonctionner pour Méthodes. Si j'ai bien compris, sa solution ne fonctionne qu'avec les constructeurs.
  • Cette solution ne fonctionne pas avec les méthodes static. C’est parce que dans ce cas, le nombre d’arguments renvoyés par ASM est différent, mais c’est quelque chose qui peut être facilement corrigé.
0
danidemi

Les noms de paramètres ne sont utiles qu'au compilateur. Lorsque le compilateur génère un fichier de classe, les noms de paramètre ne sont pas inclus - la liste d'arguments d'une méthode ne comprend que le nombre et les types de ses arguments. Donc, il serait impossible de récupérer le nom du paramètre en utilisant la réflexion (comme indiqué dans votre question) - il n'existe nulle part.

Cependant, si l'utilisation de la réflexion n'est pas une exigence absolue, vous pouvez récupérer cette information directement à partir du code source (en supposant que vous l'ayez).

0
danben