webentwicklung-frage-antwort-db.com.de

Kann ich den Namen der Methodenparameter mithilfe von Java Reflection erhalten?

Wenn ich eine Klasse wie diese habe:

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

gibt es eine Möglichkeit zu wissen, dass aMethod einen Parameter mit dem Namen aParam verwendet, dh vom Typ int?

107
Geo

Zusammenfassen:

  • abrufen von Parameternamen ist möglich, wenn während der Kompilierung Debug-Informationen enthalten sind. Siehe diese Antwort für weitere Details
  • andernfalls ist das Abrufen von Parameternamen nicht möglich
  • das Abrufen des Parametertyps ist mit method.getParameterTypes() möglich.

Um Autovervollständigungsfunktionen für einen Editor zu schreiben (wie Sie in einem der Kommentare angegeben haben), gibt es einige Optionen:

  • verwenden Sie arg0, arg1, arg2 usw.
  • verwenden Sie intParam, stringParam, objectTypeParam usw.
  • verwenden Sie eine Kombination der oben genannten Optionen - erstere für nicht primitive Typen und letztere für primitive Typen.
  • zeige keine Argumentnamen - nur die Typen.
79
Bozho

In Java 8 können Sie Folgendes tun:

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

Für Ihre Klasse Whatever können wir also einen manuellen Test durchführen:

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

welches [aParam] drucken soll, wenn Sie das -parameters-Argument an Ihren Java 8-Compiler übergeben haben.

Für Maven-Benutzer:

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

Weitere Informationen finden Sie unter folgenden Links:

  1. Offizielles Java-Tutorial: Benennen von Methodenparametern
  2. JEP 118: Zugriff auf Parameternamen zur Laufzeit
  3. Javadoc für Parameterklasse
83
lpandzic

Die Paranamer-Bibliothek wurde erstellt, um dieses Problem zu lösen.

Es wird versucht, Methodennamen auf verschiedene Arten zu ermitteln. Wenn die Klasse mit Debugging kompiliert wurde, kann sie die Informationen durch Lesen des Bytecodes der Klasse extrahieren.

Eine andere Möglichkeit besteht darin, ein privates statisches Member in den Bytecode der Klasse einzuspeisen, nachdem sie kompiliert wurde, aber bevor sie in ein Glas gestellt wird. Anschließend werden diese Informationen mithilfe von Reflektion zur Laufzeit aus der Klasse extrahiert.

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

Ich hatte Probleme mit dieser Bibliothek, aber am Ende habe ich sie zum Laufen gebracht. Ich hoffe, dass ich die Probleme dem Betreuer melden kann.

14
Sarel Botha

Ja.
Code muss mit einem Java 8-kompatiblen Compiler kompiliert werden mit der Option zum Speichern von formalen Parameternamen ( -parameters -Option).
.__ Dann sollte dieses Code-Snippet funktionieren:

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

siehe org.springframework.core.DefaultParameterNameDiscoverer-Klasse

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

Sie können die Methode mit Reflektion abrufen und deren Argumenttypen ermitteln. Check http://Java.Sun.com/j2se/1.4.2/docs/api/Java/lang/reflect/Method.html#getParameterTypes%28%29

Sie können jedoch nicht den Namen des verwendeten Arguments angeben.

5
Johnco

Es ist möglich und Spring MVC 3 macht es, aber ich habe mir nicht die Zeit genommen, genau zu sehen, wie.

Die Übereinstimmung der Methodenparameternamen zu URI-Template-Variablennamen können Nur wenn der Code kompiliert ist mit aktiviertem Debugging. Wenn Sie haben nicht debuggen aktiviert, müssen Sie Geben Sie den Namen der URI-Vorlage an Variablenname in der @PathVariable Anmerkung, um die .__ zu binden. aufgelöster Wert des Variablennamens in ein Methodenparameter. Zum Beispiel:

Aus der Frühlingsdokumentation

3
flybywire

Während es nicht möglich ist (wie andere gezeigt haben), können Sie könnte eine Anmerkung verwenden, um den Parameternamen zu übernehmen und diese durch Reflektion zu erhalten.

Nicht die sauberste Lösung, aber sie erledigt die Arbeit. Einige Webservices tun dies tatsächlich, um Parameternamen beizubehalten (dh: Bereitstellen von WSs mit glassfish).

3
WhyNotHugo

Siehe Java.beans.ConstructorProperties . Dies ist eine Annotation, die genau dafür gedacht ist.

3
Markus Jevring

wenn Sie die Eclipse verwenden, sehen Sie sich das folgende Bild an, damit der Compiler die Informationen zu Methodenparametern speichern kann

enter image description here

2
Hazim

Sie sollten also in der Lage sein:

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

Aber Sie erhalten wahrscheinlich eine Liste wie folgt:

["int : arg0"]

Ich glaube, dass dies in Groovy 2.5+ behoben wird

Die Antwort lautet also aktuell:

  • Wenn es sich um eine Groovy-Klasse handelt, können Sie den Namen nicht erhalten, aber Sie sollten dies in der Zukunft tun können.
  • Wenn es sich um eine Java-Klasse handelt, die unter Java 8 kompiliert wurde, sollten Sie dies können.

Siehe auch:


Für jede Methode dann etwas wie:

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

Um meine 2 Cent hinzuzufügen; Parameterinformationen sind in einer Klassendatei "zum Debuggen" verfügbar, wenn Sie die Quelle mit javac -g kompilieren. Und es steht APT zur Verfügung, aber Sie benötigen eine Anmerkung, so dass Sie keine Verwendung finden. (Jemand hat hier vor 4-5 Jahren etwas Ähnliches diskutiert: http://forums.Java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Alles in allem können Sie es nicht bekommen, wenn Sie nicht direkt mit Quelldateien arbeiten (ähnlich wie APT zur Kompilierzeit).

0
Elister

Wie @Bozho ausgeführt hat, ist es möglich, dies zu tun, wenn während des Kompilierens Debug-Informationen enthalten sind. Hier gibt es eine gute Antwort ...

Wie erhalte ich die Parameternamen der Konstruktoren eines Objekts (Reflektion)? von @ AdamPaynter 

... mit der ASM-Bibliothek. Ich habe ein Beispiel zusammengestellt, das zeigt, wie Sie Ihr Ziel erreichen können.

Beginnen Sie zunächst mit einer pom.xml mit diesen Abhängigkeiten.

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

Dann sollte diese Klasse tun, was Sie wollen. Rufen Sie einfach die statische Methode getParameterNames() auf.

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

Hier ist ein Beispiel mit einem Komponententest.

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

    }
}

Das vollständige Beispiel finden Sie auf GitHub

Vorsichtsmaßnahmen

  • Ich habe die ursprüngliche Lösung von @AdamPaynter leicht geändert, damit sie für Methods funktioniert. Wenn ich es richtig verstanden habe, funktioniert seine Lösung nur mit Konstrukteuren.
  • Diese Lösung funktioniert nicht mit static-Methoden. Dies ist darauf zurückzuführen, dass in diesem Fall die Anzahl der von ASM zurückgegebenen Argumente unterschiedlich ist, dies jedoch leicht behoben werden kann.
0
danidemi

Parameternamen sind nur für den Compiler nützlich. Wenn der Compiler eine Klassendatei generiert, werden die Parameternamen nicht eingeschlossen. Die Argumentliste einer Methode besteht nur aus der Anzahl und den Typen ihrer Argumente. Es ist also unmöglich, den Parameternamen mithilfe von Reflection (wie in Ihrer Frage markiert) abzurufen - er existiert nirgendwo.

Wenn die Verwendung von Reflektionen jedoch keine harte Anforderung ist, können Sie diese Informationen direkt aus dem Quellcode abrufen (vorausgesetzt, Sie haben sie).

0
danben