Czy można uzyskać nazwę parametru metody za pomocą odbicia Java?


Gdybym miał taką klasę:
public class Whatever
{
public void aMethod(int aParam);
}

czy istnieje sposób, aby dowiedzieć się, że
aMethod
używa parametru o nazwie
aParam
, czyli jest to typ
int
?
Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

W Javie 8 możesz wykonać następujące czynności:
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(){}
}

Dlatego dla Twojej klasy
Cokolwiek
możemy przeprowadzić test ręczny:
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;
}
}
}
}

co powinno dać wynik
[aParam]
, jeśli przekazałeś argument
-parameters
do kompilatora Java 8.
Dla użytkowników 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>

Więcej informacji można znaleźć pod następującymi linkami:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Podsumować:
  • pobieranie nazw parametrów prawdopodobnie jeśli informacje debugowania są włączone w czasie kompilacji. Widzieć ta odpowiedź https://coderoad.ru/6759880/po więcej szczegółów
  • w przeciwnym razie pobranie nazw parametrów nie jest możliwe
  • możesz pobrać typ parametru za pomocą
    method.getParameterTypes ()

Aby napisać funkcję autouzupełniania dla edytora (jak powiedziałeś w jednym z komentarzy), jest kilka opcji:
  • użyj
    arg0
    ,
    arg1
    ,
    arg2
    itd.
  • użyj
    intParam
    ,
    stringParam
    ,
    objectTypeParam
    itp.
  • użyj kombinacji powyższych - pierwszej dla typów innych niż pierwotne, a drugiej dla typów pierwotnych.
  • nie pokazuj w ogóle nazw argumentów - tylko typy.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Biblioteka Paranamer została stworzona, aby rozwiązać ten sam problem.
Próbuje zdefiniować nazwy metod na kilka różnych sposobów. Jeśli klasa została skompilowana za pomocą debugowania, może pobrać informacje, odczytując kod bajtowy klasy.
Innym sposobem jest wstrzyknięcie prywatnego statycznego elementu członkowskiego do kodu bajtowego klasy po jego skompilowaniu, ale przed umieszczeniem go w jar. Następnie używa odbicia, aby pobrać te informacje z klasy w czasie wykonywania.
https://github.com/paul-hammant/paranamer
https://github.com/paul-hammant/paranamer
Miałem problemy z używaniem tej biblioteki, ale w końcu udało mi się ją uruchomić. Mam nadzieję zgłosić opiekunowi problemy.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

zobacz klasę org.springframework.core.DefaultParameterNameDiscoverer
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Tak.

Kod

musi być skompilowany za pomocą kompilatora kompatybilnego z Javą 8

z włączoną opcją przechowywania formalnych nazw parametrów (opcja

-parameters

).

Wtedy ten fragment kodu powinien działać:
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());
}
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Możesz pobrać metodę z odbiciem i zdefiniować jej typy argumentów. Czek

http://java.sun.com/j2se/1.4.2 ... %2529
http://java.sun.com/j2se/1.4.2 ... %2529
Nie możesz jednak podać nazwy użytego argumentu.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:


Jest to możliwe i Spring MVC 3 to robi, ale nie znalazłem czasu, aby dokładnie zobaczyć, jak.
Dopasowywanie nazw parametrów metod
do nazw URI zmiennych szablonu, które możesz
można to zrobić tylko wtedy, gdy Twój kod jest skompilowany
z włączonym debugowaniem. Jeśli masz
jeśli debugowanie nie jest włączone, powinieneś
Podaj nazwę szablonu URI
nazwa zmiennej w polu @PathVariable
adnotacje do oprawy
dozwolona wartość nazwy do zmiennej
parametr metody. Na przykład:

Pochodzi z

dokumentacja wiosenna
http://static.springsource.org ... pping
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Chociaż nie jest to możliwe (jak pokazały inne przykłady), ty

czy możesz

użyj adnotacji, aby zawinąć nazwę parametru i uzyskać ją przez odbicie.
Nie jest to najczystsze rozwiązanie, ale spełnia swoje zadanie. Niektóre usługi sieciowe faktycznie robią to, aby zachować nazwy parametrów (np. Wdrażanie WS z Glassfish).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Widzieć

java.beans.ConstructorProperties
http://docs.oracle.com/javase/ ... .html, to jest adnotacja przeznaczona właśnie do tego.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Więc powinieneś być w stanie zrobić:
Whatever.declaredMethods
.find { it.name == 'aMethod' }
.parameters
.collect { "$it.type : $it.name" }

Ale prawdopodobnie skończysz z taką listą:
["int : arg0"]

Myślę, że zostanie to naprawione w Groovy 2.5+
https://issues.apache.org/jira/browse/GROOVY-7423
Tak więc obecna odpowiedź brzmi:
  • Jeśli jest to klasa Groovy, to nie, nie możesz uzyskać nazwy, ale powinieneś móc to zrobić w przyszłości.
  • Jeśli jest to klasa Java skompilowana dla Javy 8, powinieneś być w stanie to zrobić.

Zobacz też:

Dla każdej metody wtedy coś takiego:
Whatever.declaredMethods
.findAll { !it.synthetic }
.collect { method ->
println method
method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
}
.each {
println it
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

jeśli używasz eclipse, zobacz poniższy obrazek, aby kompilator mógł przechowywać informacje o parametrach metody
https://i.stack.imgur.com/RSLra.png
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jak zauważył @Bozho, można to zrobić, jeśli informacje debugowania są włączone w czasie kompilacji.
Tutaj jest dobra odpowiedź ...
Jak uzyskać nazwy parametrów konstruktorów obiektu (odbicie)?
https://coderoad.ru/2729580/
Blisko
@AdamPaynter..
.
przy użyciu biblioteki ASM. Podałem przykład, aby pokazać, jak możesz osiągnąć swój cel.
Przede wszystkim zacznij od pom.xml z tymi zależnościami.
<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>

Wtedy ta klasa powinna robić, co chcesz. Po prostu wywołaj statyczną metodę
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;
}
}

Oto przykład z testem jednostkowym.
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) {
} }
}

Pełny przykład można znaleźć pod adresem

GitHub
https://github.com/danidemi/po ... d-asm

Ostrzeżenia
>
  • Zmodyfikowałem nieco moje oryginalne rozwiązanie z @AdamPaynter, aby działało dla metod. Jeśli dobrze zrozumiałem, jego rozwiązanie działa tylko z konstruktorami.
  • To rozwiązanie nie działa z metodami
    static
    . Dzieje się tak, ponieważ w tym przypadku liczba argumentów zwracanych przez ASM jest różna, ale jest to coś, co można łatwo naprawić.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Aby dodać moje 2 centy; Informacje o parametrach są dostępne w pliku klasy „do debugowania”, gdy do kompilacji źródła używany jest javac-g. Jest dostępny dla APT, ale potrzebujesz adnotacji, więc nie potrzebujesz tego. (Ktoś omawiał coś podobnego 4-5 lat temu tutaj:

http://forums.java.net/jive/th ... t%3D0
http://forums.java.net/jive/th ... t%3D0
)
Krótko mówiąc, nie możesz go zdobyć, jeśli nie pracujesz bezpośrednio z plikami źródłowymi (podobnie do tego, co robi APT w czasie kompilacji).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nazwy parametrów są przydatne tylko dla kompilatora. Gdy kompilator generuje plik klasy, nazwy parametrów nie są uwzględniane - lista argumentów metody składa się tylko z liczby i typów jej argumentów. Nie byłoby więc możliwe uzyskanie nazwy parametru za pomocą refleksji (jak wskazałeś w swoim pytaniu) - nigdzie nie istnieje.
Jeśli jednak użycie refleksji nie jest rygorystycznym wymogiem, możesz uzyskać te informacje bezpośrednio z kodu źródłowego (o ile go masz).

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się