Spring Framework/Reflection / / 2024. 8. 19. 10:01

리플렉션: 멤버 탐구 - 메서드(Methods)

[튜토리얼]

 

Methods

메서드에는 호출될 수 있는 실행 가능한 코드가 들어 있습니다. 메서드는 상속되고 넌-리플렉티브한 코드에서는 오버로딩, 오버라이딩, 하이딩[hiding]과 같은 동작이 컴파일러에 의해 적용됩니다. 반면, 리플렉티브한 코드는 슈퍼클래스를 고려하지 않고도 메서드 선택을 특정 클래스로 제한할 수 있습니다. 슈퍼클래스 메서드에 액세스할 수 있지만 메서드를 선언한 클래스를 확인할 수 있습니다. 이는 리플렉션 없이는 프로그래밍 방식으로 발견하는 것이 불가능하며 많은 미묘한 버그의 원인입니다. 

java.lang.reflect.Method 클래스는 메서드의 제어자, 리턴 타입, 파라미터, 어노테이션 및 throw된 예외에 대한 정보에 액세스하는 API를 제공합니다. 또한 메서드를 호출하는 데 사용할 수도 있습니다. 이러한 주제는 다음 섹션에서 다룹니다.

 

Obtaining Method Type Information

메서드 선언에는 이름, 제어자, 파라미터, 리턴 타입 및 throw 가능한 예외 목록이 포함됩니다. java.lang.reflect.Method 클래스는 이 정보를 얻는 방법을 제공합니다.

MethodSpy 예제는 주어진 클래스에 선언된 모든 메서드를 열거하고 주어진 이름의 모든 메서드에 대한 리턴 타입, 파라미터 타입, 예외 타입을 검색하는 방법을 보여줍니다.

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import static java.lang.System.out;

public class MethodSpy {
    private static final String  fmt = "%24s: %s%n";

    // for the morbidly curious
    <E extends RuntimeException> void genericThrow() throws E {}

    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName(args[0]);
	    Method[] allMethods = c.getDeclaredMethods();
	    for (Method m : allMethods) {
		if (!m.getName().equals(args[1])) {
		    continue;
		}
		out.format("%s%n", m.toGenericString());

		out.format(fmt, "ReturnType", m.getReturnType());
		out.format(fmt, "GenericReturnType", m.getGenericReturnType());

		Class<?>[] pType  = m.getParameterTypes();
		Type[] gpType = m.getGenericParameterTypes();
		for (int i = 0; i < pType.length; i++) {
		    out.format(fmt,"ParameterType", pType[i]);
		    out.format(fmt,"GenericParameterType", gpType[i]);
		}

		Class<?>[] xType  = m.getExceptionTypes();
		Type[] gxType = m.getGenericExceptionTypes();
		for (int i = 0; i < xType.length; i++) {
		    out.format(fmt,"ExceptionType", xType[i]);
		    out.format(fmt,"GenericExceptionType", gxType[i]);
		}
	    }

        // production code should handle these exceptions more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	}
    }
}

 

다음은 파라미터화된 타입과 가변 개수의 파라미터를 갖는 메서드의 예인 Class.getConstructor()에 대한 출력입니다.

$ java MethodSpy java.lang.Class getConstructor 
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor
(java.lang.Class<?>[]) throws java.lang.NoSuchMethodException, 
java.lang.SecurityException 
ReturnType: class java.lang.reflect.Constructor 
GenericReturnType: java.lang.reflect.Constructor<T> 
ParameterType: class [Ljava.lang.Class; 
GenericParameterType: java.lang.Class<?>[] 
ExceptionType: class java.lang.NoSuchMethodException 
GenericExceptionType: class java.lang.NoSuchMethodException 
ExceptionType: class java.lang.SecurityException 
GenericExceptionType: class java.lang.SecurityException

 

다음은 소스 코드에서 메서드의 실제 선언입니다.

public Constructor<T> getConstructor(Class<?>... parameterTypes)

 

먼저 리턴 및 파라미터 타입이 제너릭이라는 점에 유의하세요. Method.getGenericReturnType()은 클래스 파일에 Signature 속성이 있는 경우 이를 참조합니다. 속성을 사용할 수 없는 경우 제네릭 도입으로 변경되지 않은 Method.getReturnType()으로 대체합니다. Reflection에서 Foo의 일부 값에 대한 getGenericFoo()라는 이름을 가진 다른 메서드는 비슷하게 구현됩니다. 

다음으로 마지막(그리고 유일한) 파라미터인 parametersType은 java.lang.Class 유형의 가변 arity(파라미터 개수가 가변적)입니다. 이는 java.lang.Class 유형의 1차원 배열로 표현됩니다. 이는 Method.isVarArgs()를 호출하여 명시적으로 java.lang.Class의 배열인 파라미터와 구별할 수 있습니다. Method.get*Types()의 리턴 값에 대한 구문은 Class.getName()에 설명되어 있습니다.

 

다음 예제는 일반적인 리턴 타입을 갖는 메서드를 보여줍니다.

$ java MethodSpy java.lang.Class cast 
public T java.lang.Class.cast(java.lang.Object) 
ReturnType: class java.lang.Object 
GenericReturnType: T 
ParameterType: class java.lang.Object 
GenericParameterType: class java.lang.Object

 

Class.cast() 메서드의 제너릭 리턴 타입은 java.lang.Object로 보고됩니다. 이는 제네릭이 컴파일 중에 모든 제네릭  타입 정보를 소거하는 타입 소거를 통해 구현되기 때문입니다. T의 삭제는 Class의 선언에 의해 정의됩니다.

public final class Class<T> implements ...

 

따라서 T는 타입 파라미터의 상한값으로 대체되며, 이 경우 java.lang.Object입니다. 

마지막 예는 여러 오버로드가 있는 메서드의 출력을 보여줍니다.

$ java MethodSpy java.io.PrintStream format 
public java.io.PrintStream java.io.PrintStream.format(java.lang.String,java.lang.Object...)
              ReturnType: class java.io.PrintStream
       GenericReturnType: class java.io.PrintStream
           ParameterType: class java.lang.String
    GenericParameterType: class java.lang.String
           ParameterType: class [Ljava.lang.Object;
    GenericParameterType: class [Ljava.lang.Object;
public java.io.PrintStream java.io.PrintStream.format(java.util.Locale,java.lang.String,java.lang.Object...)
              ReturnType: class java.io.PrintStream
       GenericReturnType: class java.io.PrintStream
           ParameterType: class java.util.Locale
    GenericParameterType: class java.util.Locale
           ParameterType: class java.lang.String
    GenericParameterType: class java.lang.String
           ParameterType: class [Ljava.lang.Object;
    GenericParameterType: class [Ljava.lang.Object;

 

동일한 메서드 이름의 오버로드가 여러 개 발견되면 Class.getDeclaredMethods()에서 모두 반환합니다. format()에는 두 개의 오버로드(Locale이 있는 오버로드와 없는 오버로드)가 있으므로 둘 다 MethodSpy에서 표시됩니다.


참고: Method.getGenericExceptionTypes()는 실제로 제네릭 예외 타입으로 메서드를 선언할 수 있기 때문에 존재합니다. 그러나 제네릭 예외 타입을 캐치할 수 없기 때문에 이것은 거의 사용되지 않습니다.


 

Obtaining Names of Method Parameters

메서드나 생성자의 정규 파라미터 이름을 얻으려면 java.lang.reflect.Executable.getParameters 메서드를 사용할 수 있습니다. ( Method  Constructor 클래스는 Executable 클래스를 상속받아 Executable.getParameters 메서드를 상속합니다.) 하지만 .class 파일은 기본적으로 정규 파라미터 이름을 저장하지 않습니다. 이는 .class 파일을 생성하고 사용하는 많은 도구들이 파라미터 이름이 포함된 .class 파일의 더 큰 정적 및 동적 메모리 사용을 예상하지 못할 수 있기 때문입니다. 특히, 이러한 도구들은 더 큰 .class 파일을 처리해야 하며, 자바 가상 머신(JVM)은 더 많은 메모리를 사용하게 됩니다. 또한, secret나 password와 같은 일부 파라미터 이름은 보안에 민감한 메서드에 대한 정보를 노출할 수 있습니다. 

특정 .class 파일에 정규 파라미터 이름을 저장하고, 이를 통해 Reflection API가 정규 파라미터 이름을 가져올 수 있도록 하려면, 소스 파일을 javac 컴파일러의 -parameters 옵션을 사용하여 컴파일해야 합니다. 

MethodParameterSpy  예제는 주어진 클래스의 모든 생성자와 메서드의 정규 파라미터 이름을 검색하는 방법을 보여줍니다. 이 예제는 또한 각 매개변수에 대한 다른 정보도 출력합니다. 

다음 명령은 `ExampleMethods` 클래스의 생성자와 메서드의 정규 파라미터 이름을 출력합니다. 참고: -parameters 컴파일러 옵션을 사용하여 ExampleMethods 예제를 컴파일하는 것을 잊지 마세요:

 

java MethodParameterSpy ExampleMethods

 

이 명령은 다음과 같이 출력됩니다:

Number of constructors: 1

Constructor #1
public ExampleMethods()

Number of declared constructors: 1

Declared constructor #1
public ExampleMethods()

Number of methods: 4

Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
             Return type: boolean
     Generic return type: boolean
         Parameter class: class java.lang.String
          Parameter name: stringParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: int
          Parameter name: intParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #2
public int ExampleMethods.varArgsMethod(java.lang.String...)
             Return type: int
     Generic return type: int
         Parameter class: class [Ljava.lang.String;
          Parameter name: manyStrings
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #3
public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>)
             Return type: boolean
     Generic return type: boolean
         Parameter class: interface java.util.List
          Parameter name: listParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #4
public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>)
             Return type: void
     Generic return type: void
         Parameter class: class [Ljava.lang.Object;
          Parameter name: a
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: interface java.util.Collection
          Parameter name: c
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

 

MethodParameterSpy 예제는 Parameter 클래스에서 다음 메서드를 사용합니다:

  • getModifiers : 정규 파라미터가 소유한 다양한 특성을 나타내는 정수를 반환합니다. 이 값은 정규 파라미터에 적용 가능한 경우 다음 값의 합계입니다. 
    10진수 16진수 설명
    16 0x0010 The formal parameter is declared final
    4096 0x1000 The formal parameter is synthetic. Alternatively, you can invoke the method isSynthetic.
    32768 0x8000 The parameter is implicitly declared in source code. Alternatively, you can invoke the method isImplicit
  • isImplicit: 이 파라미터가 소스 코드에서 암묵적으로 선언된 경우 true를 반환합니다. 자세한 내용은 Implicit and Synthetic Parameters 섹션을 참조하세요.
  • isNamePresent: .class 파일에 따라 파라미터에 이름이 있는 경우 true를 반환합니다.
  • isSynthetic: 이 파라미터가 소스 코드에서 암시적으로도 명시적으로도 선언되지 않은 경우 true를 반환합니다. 자세한 내용은 Implicit and Synthetic Parameters 섹션을 참조하십시오.

 

Implicit and Synthetic Parameters

일부 구조물(constructs)은 명시적으로 작성되지 않은 경우 소스 코드에서 암시적으로 선언됩니다. 예를 들어, ExampleMethods 예제에는 생성자가 포함되어 있지 않습니다. 디폴트 생성자가 암시적으로 선언됩니다. MethodParameterSpy 예제는 ExampleMethods의 암시적으로 선언된 생성자에 대한 정보를 출력합니다: 

Number of declared constructors: 1
public ExampleMethods()

 

construct
위 설명에서 "구조물"이란, 자바 프로그램 내에서 컴파일러가 생성하거나 처리하는 코드 요소나 구성 요소를 의미합니다. 이 구조물에는 클래스, 메서드, 필드, 파라미터와 같은 프로그래밍 요소들이 포함될 수 있습니다. "구조물"은 주로 프로그램의 소스 코드에서 명시적으로 작성되거나, 자바 컴파일러가 암시적으로 생성하는 코드를 가리킵니다. 
예를 들어, 합성 구조물(synthetic construct)은 컴파일러가 소스 코드에 명시적으로 작성되지 않은 코드를 생성할 때 나타나는 구조물을 의미합니다. 이는 컴파일러가 특정 기능을 지원하기 위해 추가하는 코드 요소들을 말합니다.


다음은 MethodParameterExamples의 발췌 예시입니다: 

public class MethodParameterExamples {
    public class InnerClass { }
}


InnerClass는 비정적 중첩 클래스 또는 inner 클래스입니다. 내부 클래스의 경우 생성자도 암시적으로 선언됩니다. 그러나 이 생성자에는 파라미터가 포함됩니다. 자바 컴파일러가 InnerClass를 컴파일할 때, 다음과 유사한 코드를 나타내는 .class 파일을 생성합니다: 

public class MethodParameterExamples {
    public class InnerClass {
        final MethodParameterExamples parent;
        InnerClass(final MethodParameterExamples this$0) {
            parent = this$0; 
        }
    }
}


InnerClass 생성자에는 InnerClass를 감싸고 있는 클래스인 MethodParameterExamples 타입의 파라미터가 포함됩니다. 따라서 MethodParameterExamples 예제는 다음을 출력합니다: 

public MethodParameterExamples$InnerClass(MethodParameterExamples)
         Parameter class: class MethodParameterExamples
          Parameter name: this$0
               Modifiers: 32784
            Is implicit?: true
        Is name present?: true
           Is synthetic?: false


InnerClass 클래스의 생성자가 암시적으로 선언되었기 때문에, 그 파라미터도 암시적입니다.

 

Note:

  • Java 컴파일러는 내부 클래스의 생성자에 대한 정규 파라미터를 생성하여 컴파일러가 생성 표현식에서 내부 클래스의 생성자로 참조(outer 클래스의 인스턴스에 대한 참조)를 전달할 수 있도록 합니다.
  • 값 32784는 InnerClass 생성자의 파라미터가 final(16)과 implicit(32768) 모두임을 의미합니다.
  • The Java programming language allows variable names with dollar signs ($); however, by convention, dollar signs are not used in variable names.자바 프로그래밍 언어에서는 달러 기호($)를 변수 이름에 사용할 수 있습니다. 그러나 관례적으로 변수 이름에 달러 기호는 사용하지 않습니다.

자바 컴파일러에 의해 생성된 구조물이 명시적 또는 암시적으로 소스 코드에서 선언된 구조물에 대응하지 않는 경우, 해당 구조물은 합성(synthetic)으로 표시됩니다. 단, 클래스 초기화 메서드는 예외입니다. 합성 구조물은 컴파일러가 생성하는 산출물로, 다양한 구현에서 다르게 나타날 수 있습니다. 다음은 MethodParameterExamples 에서 발췌한 예시입니다:

public class MethodParameterExamples {
    enum Colors {
        RED, WHITE;
    }
}

 

자바 컴파일러가 열거형(enum) 구문을 만나면, .class 파일 구조와 호환되고 열거형 구문의 예상 기능을 제공하는 여러 메서드를 생성합니다. 예를 들어, 자바 컴파일러는 열거형 구문 Colors에 대해 다음과 유사한 코드를 나타내는 .class 파일을 생성합니다: 

final class Colors extends java.lang.Enum<Colors> {
    public final static Colors RED = new Colors("RED", 0);
    public final static Colors BLUE = new Colors("BLUE", 1);
 
    private final static Colors[] values = new Colors[]{ RED, BLUE };
 
    private Colors(String name, int ordinal) {
        super(name, ordinal);
    }
 
    public static Colors[] values(){
        return values;
    }
 
    public static Colors valueOf(String name){
        return (Colors)java.lang.Enum.valueOf(Colors.class, name);
    }
}


자바 컴파일러는 이 열거형 구문에 대해 세 가지 생성자와 메서드를 생성합니다: Colors(String name, int ordinal), Colors[] values(), 그리고 Colors valueOf(String name). values valueOf 메서드는 암시적으로 선언됩니다. 따라서, 이들의 정규 파라미터 이름도 암시적으로 선언됩니다. 

Colors(String name, int ordinal) 열거형 생성자는 디폴트 생성자이며 암시적으로 선언됩니다. 그러나 이 생성자의 정규 파라미터(name과 ordinal)는 암시적으로 선언되지 않습니다. 이러한 정규 파라미터는 명시적이거나 암시적으로 선언되지 않았기 때문에, 합성(synthetic) 파라미터입니다. (열거형 구문의 디폴트 생성자의 정규 파라미터는 암시적으로 선언되지 않습니다. 이는 다른 자바 컴파일러가 이 생성자에 대해 다른 정규 파라미터를 지정할 수 있기 때문입니다. 컴파일러가 열거형 상수를 사용하는 표현식을 컴파일할 때, 암시적으로 선언된 공용(static) 필드에만 의존하며, 생성자나 이러한 상수가 초기화되는 방식에는 의존하지 않습니다.) 

결과적으로, MethodParameterExample 예제는 `Colors` 열거형 구문에 대해 다음과 같이 출력합니다: 

enum Colors:

Number of constructors: 0

Number of declared constructors: 1

Declared constructor #1
private MethodParameterExamples$Colors()
         Parameter class: class java.lang.String
          Parameter name: $enum$name
               Modifiers: 4096
            Is implicit?: false
        Is name present?: true
           Is synthetic?: true
         Parameter class: int
          Parameter name: $enum$ordinal
               Modifiers: 4096
            Is implicit?: false
        Is name present?: true
           Is synthetic?: true

Number of methods: 2

Method #1
public static MethodParameterExamples$Colors[]
    MethodParameterExamples$Colors.values()
             Return type: class [LMethodParameterExamples$Colors;
     Generic return type: class [LMethodParameterExamples$Colors;

Method #2
public static MethodParameterExamples$Colors
    MethodParameterExamples$Colors.valueOf(java.lang.String)
             Return type: class MethodParameterExamples$Colors
     Generic return type: class MethodParameterExamples$Colors
         Parameter class: class java.lang.String
          Parameter name: name
               Modifiers: 32768
            Is implicit?: true
        Is name present?: true
           Is synthetic?: false


암시적으로 선언된 구조물에 대한 자세한 내용, 특히 Reflection API에서 암시적으로 나타나는 파라미터를 포함한 정보는 자바 언어 명세를 참조하십시오.

 

Retrieving and Parsing Method Modifiers

메서드 선언에는 다음과 같은 몇 가지 제어자가 포함될 수 있습니다.

  • Access modifiers: public, protected, and private
  • Modifier restricting to one instance: static
  • Modifier prohibiting value modification: final
  • Modifier requiring override: abstract
  • Modifier preventing reentrancy: synchronized
  • Modifier indicating implementation in another programming language: native
  • Modifier forcing strict floating point behavior: strictfp
  • Annotations

MethodModifierSpy 예제는 주어진 이름을 가진 메서드의 제어자를 나열합니다. 또한, 해당 메서드가 합성 메서드(컴파일러가 생성한 메서드), 가변 아규먼트 메서드, 또는 제네릭 인터페이스를 지원하기 위해 컴파일러가 생성한 브리지 메서드인지 여부를 표시합니다.

 

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import static java.lang.System.out;

public class MethodModifierSpy {

    private static int count;
    private static synchronized void inc() { count++; }
    private static synchronized int cnt() { return count; }

    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName(args[0]);
	    Method[] allMethods = c.getDeclaredMethods();
	    for (Method m : allMethods) {
		if (!m.getName().equals(args[1])) {
		    continue;
		}
		out.format("%s%n", m.toGenericString());
		out.format("  Modifiers:  %s%n",
			   Modifier.toString(m.getModifiers()));
		out.format("  [ synthetic=%-5b var_args=%-5b bridge=%-5b ]%n",
			   m.isSynthetic(), m.isVarArgs(), m.isBridge());
		inc();
	    }
	    out.format("%d matching overload%s found%n", cnt(),
		       (cnt() == 1 ? "" : "s"));

        // production code should handle this exception more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	}
    }
}
 

MethodModifierSpy가 생성하는 출력의 몇 가지 예시는 다음과 같습니다.

$ java MethodModifierSpy java.lang.Object wait
public final void java.lang.Object.wait() throws java.lang.InterruptedException
  Modifiers:  public final
  [ synthetic=false var_args=false bridge=false ]
public final void java.lang.Object.wait(long,int)
  throws java.lang.InterruptedException
  Modifiers:  public final
  [ synthetic=false var_args=false bridge=false ]
public final native void java.lang.Object.wait(long)
  throws java.lang.InterruptedException
  Modifiers:  public final native
  [ synthetic=false var_args=false bridge=false ]
3 matching overloads found
$ java MethodModifierSpy java.lang.StrictMath toRadians
public static double java.lang.StrictMath.toRadians(double)
  Modifiers:  public static strictfp
  [ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ java MethodModifierSpy MethodModifierSpy inc
private synchronized void MethodModifierSpy.inc()
  Modifiers: private synchronized
  [ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ java MethodModifierSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor
  (java.lang.Class<T>[]) throws java.lang.NoSuchMethodException,
  java.lang.SecurityException
  Modifiers: public transient
  [ synthetic=false var_args=true bridge=false ]
1 matching overload found
$ java MethodModifierSpy java.lang.String compareTo
public int java.lang.String.compareTo(java.lang.String)
  Modifiers: public
  [ synthetic=false var_args=false bridge=false ]
public int java.lang.String.compareTo(java.lang.Object)
  Modifiers: public volatile
  [ synthetic=true  var_args=false bridge=true  ]
2 matching overloads found

 

Method.isVarArgs()  Class.getConstructor() 에 대해 true를 반환합니다. 이는 메서드 선언이 다음과 같다는 것을 나타냅니다.

public Constructor<T> getConstructor(Class<?>... parameterTypes)

 

이렇게는 안됨:

public Constructor<T> getConstructor(Class<?> [] parameterTypes)

 

String.compareTo() 의 출력에 두 개의 메서드가 포함되어 있는 점에 주목하십시오. 하나는 String.java에 선언된 메서드입니다:

public int compareTo(String anotherString);


그리고 두 번째는 합성 메서드 또는 컴파일러가 생성한 브리지 메서드입니다. 이는 String이 파라미터화된 인터페이스 Comparable을 구현하기 때문에 발생합니다. 타입 소거 동안, 상속된 메서드 Comparable.compareTo() 의 아규먼트 타입이 java.lang.Object에서 java.lang.String으로 변경됩니다. 소거 후 Comparable과 String의 compareTo 메서드의 파라미터 타입이 더 이상 일치하지 않으므로, 오버라이딩이 발생할 수 없습니다. 다른 모든 상황에서는 인터페이스가 구현되지 않았기 때문에 컴파일 타임 오류가 발생하지만, 브리지 메서드의 추가로 이 문제를 방지할 수 있습니다. 

Method  java.lang.reflect.AnnotatedElement 를 구현합니다. 따라서 java.lang.annotation.RetentionPolicy.RUNTIME 을 가진 모든 런타임 애노테이션을 가져올 수 있습니다. 애노테이션을 얻는 예시는 Examining Class Modifiers and Types 섹션을 참조하십시오.

 

Invoking Methods

리플렉션은 클래스의 메서드를 호출할 수 있는 방법을 제공합니다. 일반적으로 이는 non-reflective 코드에서 클래스 인스턴스를 원하는 타입으로 캐스팅할 수 없는 경우에만 필요합니다. 메서드는 java.lang.reflect.Method.invoke() 를 사용하여 호출됩니다. 첫 번째 인자는 이 특정 메서드가 호출될 객체 인스턴스입니다. (메서드가 정적(static)인 경우 첫 번째 인자는 null이어야 합니다.) 이후의 아규먼트들은 이 특정 메서드의 파라미터들입니다. 만약 이 특정 메서드가 예외를 던지면, 이 예외는 java.lang.reflect.InvocationTargetException 으로 래핑됩니다. 메서드의 원래 예외는 예외 체이닝 메커니즘의 InvocationTargetException.getCause() 메서드를 사용하여 가져올 수 있습니다.

 

Finding and Invoking a Method with a Specific Declaration

특정 클래스에서 private 테스트 메서드를 리플렉션을 사용해 호출하는 테스트 스위트를 고려해보십시오. Deet 예제는 "test"라는 문자열로 시작하고, 리턴 타입이 boolean이며, 단일 Locale 파라미터를 가지는 클래스의 public 메서드를 검색한 다음, 일치하는 각 메서드를 호출합니다.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Locale;
import static java.lang.System.out;
import static java.lang.System.err;

public class Deet<T> {
    private boolean testDeet(Locale l) {
	// getISO3Language() may throw a MissingResourceException
	out.format("Locale = %s, ISO Language Code = %s%n", l.getDisplayName(), l.getISO3Language());
	return true;
    }

    private int testFoo(Locale l) { return 0; }
    private boolean testBar() { return true; }

    public static void main(String... args) {
	if (args.length != 4) {
	    err.format("Usage: java Deet <classname> <langauge> <country> <variant>%n");
	    return;
	}

	try {
	    Class<?> c = Class.forName(args[0]);
	    Object t = c.newInstance();

	    Method[] allMethods = c.getDeclaredMethods();
	    for (Method m : allMethods) {
		String mname = m.getName();
		if (!mname.startsWith("test")
		    || (m.getGenericReturnType() != boolean.class)) {
		    continue;
		}
 		Type[] pType = m.getGenericParameterTypes();
 		if ((pType.length != 1)
		    || Locale.class.isAssignableFrom(pType[0].getClass())) {
 		    continue;
 		}

		out.format("invoking %s()%n", mname);
		try {
		    m.setAccessible(true);
		    Object o = m.invoke(t, new Locale(args[1], args[2], args[3]));
		    out.format("%s() returned %b%n", mname, (Boolean) o);

		// Handle any exceptions thrown by method to be invoked.
		} catch (InvocationTargetException x) {
		    Throwable cause = x.getCause();
		    err.format("invocation of %s failed: %s%n",
			       mname, cause.getMessage());
		}
	    }

        // production code should handle these exceptions more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	} catch (InstantiationException x) {
	    x.printStackTrace();
	} catch (IllegalAccessException x) {
	    x.printStackTrace();
	}
    }
}


Deet는 클래스에 명시적으로 선언된 모든 메서드를 반환하는 getDeclaredMethods()를 호출합니다. 또한, Class.isAssignableFrom()을 사용하여 찾은 메서드의 파라미터가 원하는 호출과 호환되는지 확인합니다. 기술적으로는 Locale이 final이므로 다음 문장이 참인지 테스트할 수 있었습니다: 

Locale.class == pType[0].getClass()


그러나 Class.isAssignableFrom()이 더 일반적입니다. 

$ java Deet Deet ja JP JP
invoking testDeet()
Locale = Japanese (Japan,JP), 
ISO Language Code = jpn
testDeet() returned true
$ java Deet Deet xx XX XX
invoking testDeet()
invocation of testDeet failed: 
Couldn't find 3-letter language code for xx


먼저, testDeet()만이 코드에서 적용된 선언 제한을 충족함을 주목하십시오. 다음으로, testDeet()에 잘못된 아규먼트가 전달되면, unchecked java.util.MissingResourceException을 던집니다. 리플렉션에서는 checked 예외와 uncheckied 예외의 처리에 차이가 없습니다. 모두 InvocationTargetException으로 래핑됩니다. 


Invoking Methods with a Variable Number of Arguments

Method.invoke()를 사용하여 메서드에 가변 아규먼트를 전달할 수 있습니다. 이해해야 할 핵심 개념은 가변 아규먼트 메서드가 마치 가변 아규먼트가 배열에 패킹된 것처럼 구현된다는 점입니다. 

InvokeMain 예제는 특정 클래스에서 main() 진입점을 호출하고 런타임에 결정된 인수 집합을 전달하는 방법을 보여줍니다. 

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class InvokeMain {
    public static void main(String... args) {
        try {
            Class<?> c = Class.forName(args[0]);
            Class[] argTypes = new Class[] { String[].class };
            Method main = c.getDeclaredMethod("main", argTypes);
            String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
            Systehttp://m.out.format("invoking %s.main()%n", c.getName());
            main.invoke(null, (Object)mainArgs);

            // 실제 코드에서는 이러한 예외를 더 우아하게 처리해야 합니다.
        } catch (ClassNotFoundException x) {
            x.printStackTrace();
        } catch (NoSuchMethodException x) {
            x.printStackTrace();
        } catch (IllegalAccessException x) {
            x.printStackTrace();
        } catch (InvocationTargetException x) {
            x.printStackTrace();
        }
    }
}


먼저, main() 메서드를 찾기 위해 코드는 String 배열을 단일 매개변수로 가지는 "main"이라는 이름의 메서드를 검색합니다. main()이 정적(static)이므로, null이 Method.invoke()의 첫 번째 인자입니다. 두 번째 인자는 전달할 인수 배열입니다. 

$ java InvokeMain Deet Deet ja JP JP
invoking Deet.main()
invoking testDeet()
Locale = Japanese (Japan,JP), 
ISO Language Code = jpn
testDeet() returned true

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유