program tip

Java 리플렉션을 사용하여 메소드 매개 변수 이름을 얻을 수 있습니까?

radiobox 2020. 7. 25. 10:47
반응형

Java 리플렉션을 사용하여 메소드 매개 변수 이름을 얻을 수 있습니까?


이런 수업이 있다면 :

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

그 알 수있는 방법이있다 aMethod라는 이름의 매개 변수를 사용하는 aParam유형이다 int?


요약:

  • 매개 변수 이름을 얻는 것은 이다 디버그 정보를 컴파일하는 동안 포함 된 경우 가능합니다. 자세한 내용은 이 답변 을 참조하십시오
  • 그렇지 않으면 매개 변수 이름을 가져올없습니다
  • 매개 변수 유형을 얻는 것이 가능합니다. method.getParameterTypes()

편집자에 대한 자동 완성 기능을 작성하기 위해 (설명 중 하나에서 언급 한대로) 몇 가지 옵션이 있습니다.

  • 사용 arg0, arg1, arg2
  • 사용 intParam, stringParam, objectTypeParam, 등
  • 위의 유형-비 기본 유형의 경우 전자와 기본 유형의 후자를 조합하여 사용하십시오.
  • 인수 이름을 전혀 표시하지 않고 유형 만 표시하십시오.

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

따라서 수업 Whatever을 위해 수동 테스트를 수행 할 수 있습니다.

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 컴파일러에 인수를 [aParam]전달한 경우 인쇄 해야합니다 -parameters.

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>

자세한 내용은 다음 링크를 참조하십시오.

  1. 공식 자바 튜토리얼 : 메소드 파라미터의 이름 얻기
  2. JEP 118 : 런타임시 매개 변수 이름에 액세스
  3. 매개 변수 클래스 용 Javadoc

Paranamer 라이브러리는 이와 동일한 문제를 해결하기 위해 만들어졌습니다.

몇 가지 다른 방법으로 메소드 이름을 판별하려고 시도합니다. 클래스가 디버깅으로 컴파일 된 경우 클래스의 바이트 코드를 읽어 정보를 추출 할 수 있습니다.

또 다른 방법은 클래스가 컴파일 된 후 그러나 jar에 배치되기 전에 클래스의 바이트 코드에 개인 정적 멤버를 삽입하는 것입니다. 그런 다음 리플렉션을 사용하여 런타임에 클래스에서이 정보를 추출합니다.

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

이 라이브러리를 사용하는 데 문제가 있었지만 결국에는 작동하게되었습니다. 관리자에게 문제를보고하고 싶습니다.


예. 공식 매개 변수 이름을 저장하는 옵션 ( -parameters 옵션) 있는 Java 8 호환 컴파일러
코드 를 컴파일해야합니다 . 그런 다음이 코드 스 니펫이 작동해야합니다.

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

org.springframework.core.DefaultParameterNameDiscoverer 클래스를 참조하십시오.

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

리플렉션을 사용하여 메소드를 검색하고 인수 유형을 감지 할 수 있습니다. http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29 확인

그러나 사용 된 인수의 이름을 알 수 없습니다.


가능하고 Spring MVC 3이 그렇게하지만 정확히 어떻게 시간을 보지 못했습니다.

메소드 매개 변수 이름과 URI 템플리트 변수 이름의 일치는 코드가 디버깅을 사용하여 컴파일 된 경우에만 수행 할 수 있습니다. 디버깅을 활성화하지 않은 경우 변수 이름의 확인 된 값을 메서드 매개 변수에 바인딩하려면 @PathVariable 주석에 URI 템플릿 변수 이름의 이름을 지정해야합니다. 예를 들면 다음과 같습니다.

스프링 문서 에서 가져온


가능하지는 않지만 (다른 사람들이 설명했듯이) 주석을 사용하여 매개 변수 이름을 가져 와서 반영 할 있습니다.

가장 깨끗한 솔루션은 아니지만 작업이 완료됩니다. 일부 웹 서비스는 실제로 매개 변수 이름을 유지하기 위해이를 수행합니다 (예 : glassfish로 WS 배포).


java.beans.ConstructorProperties 참조하십시오 .이 작업은 정확하게 수행하도록 설계된 주석입니다.


따라서 다음을 수행 할 수 있어야합니다.

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

그러나 아마도 다음과 같은 목록을 얻을 수 있습니다.

["int : arg0"]

나는 이것이 Groovy 2.5 이상에서 해결 될 것이라고 믿습니다.

따라서 현재 답변은 다음과 같습니다.

  • 그것이 Groovy 클래스라면 아니오, 이름을 얻을 수는 없지만 앞으로는 가능해야합니다.
  • Java 8에서 컴파일 된 Java 클래스라면 가능할 것입니다.

또한보십시오:


모든 방법마다 다음과 같은 것이 있습니다.

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

이클립스를 사용하는 경우 다음 이미지를 참조하여 컴파일러가 메소드 매개 변수에 대한 정보를 저장할 수 있도록하십시오.

여기에 이미지 설명을 입력하십시오


매개 변수 이름은 컴파일러에만 유용합니다. 컴파일러가 클래스 파일을 생성 할 때 매개 변수 이름은 포함되지 않습니다. 메소드의 인수 목록은 인수의 수와 유형으로 만 구성됩니다. 따라서 리플렉션을 사용하여 매개 변수 이름을 검색하는 것은 불가능합니다 (질문에 태그 된 것처럼)-어디에도 존재하지 않습니다.

그러나 리플렉션 사용이 어려운 요구 사항이 아닌 경우 소스 코드에서 직접이 정보를 검색 할 수 있습니다 (있는 경우).


To add my 2 cents; parameter info is available in a class file "for debugging" when you use javac -g to compile the source. And it is available to APT but you'll need an annotation so no use to you. (Somebody discussed something similar 4-5 years ago here: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Overall in-short you can't get it unless you work on Source files directly (similar to what APT does at compile time).


As @Bozho stated, it is possible to do it if debug information is included during compilation. There's a good answer here...

How to get the parameter names of an object's constructors (reflection)? by @AdamPaynter

...using the ASM library. I put together an example showing how you can achieve your goal.

우선, 이러한 종속성으로 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>

그런 다음이 수업은 원하는 것을해야합니다. 정적 메소드를 호출하십시오 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;
    }
}

다음은 단위 테스트의 예입니다.

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 에서 완전한 예제를 찾을 수 있습니다

경고

  • @AdamPaynter의 원래 솔루션을 약간 변경하여 Methods에서 작동하도록했습니다. 내가 올바르게 이해하면 그의 솔루션은 생성자와 만 작동합니다.
  • 이 솔루션은 static메소드에서 작동하지 않습니다 . 이 경우 ASM에 의해 반환되는 인수의 수가 다르기 때문에 쉽게 고칠 수 있습니다.

참고 URL : https://stackoverflow.com/questions/2237803/can-i-obtain-method-parameter-name-using-java-reflection

반응형