Java Reflection API를 사용하여 런타임에 메소드를 호출하는 방법을 간략하게 살펴보겠다.
예제 클래스 생성
public class Operations {
public double publicSum(int a, double b) {
return a + b;
}
public static double publicStaticMultiply(float a, long b) {
return a * b;
}
private boolean privateAnd(boolean a, boolean b) {
return a && b;
}
protected int protectedMax(int a, int b) {
return a > b ? a : b;
}
}
메소드 객체 획득
먼저 호출하려는 메서드를 반영하는 Method 객체를 가져와야 한다 . 메소드 가 정의된 유형을 나타내는 Class 객체는 이를 수행하는 두 가지 방법을 제공한다.
getMethod()
getMethod()를 사용하여 클래스나 해당 슈퍼클래스의 공개 메서드를 찾을 수 있다. 기본적으로 메소드 이름을 첫 번째 인수로 받고 그 뒤에 메소드 인수 유형을 받는다.
Method sumInstanceMethod
= Operations.class.getMethod("publicSum", int.class, double.class);
Method multiplyStaticMethod
= Operations.class.getMethod(
"publicStaticMultiply", float.class, long.class);
getDeclaredMethod()
getDeclaredMethod()를 사용하여 모든 종류의 메소드를 얻을 수 있다. 여기에는 공개, 보호, 기본 액세스 및 비공개 메소드가 포함되지만 상속된 메소드는 제외된다. getMethod() 와 동일한 매개변수를 받는다.
Method andPrivateMethod
= Operations.class.getDeclaredMethod(
"privateAnd", boolean.class, boolean.class);
Method maxProtectedMethod
= Operations.class.getDeclaredMethod("protectedMax", int.class, int.class);
Instance Methods
인스턴스 메서드를 호출하려면 호출() 에 대한 첫 번째 인수는 호출되는 메서드를 반영하는 Method 의 인스턴스여야 한다.
@Test
public void givenObject_whenInvokePublicMethod_thenCorrect() {
Method sumInstanceMethod
= Operations.class.getMethod("publicSum", int.class, double.class);
Operations operationsInstance = new Operations();
Double result
= (Double) sumInstanceMethod.invoke(operationsInstance, 1, 3);
assertThat(result, equalTo(4.0));
}
Static Methods
이러한 메서드는 인스턴스 호출을 요구하지 않으므로 null을 첫 번째 인수로 전달할 수 있다.
@Test
public void givenObject_whenInvokeStaticMethod_thenCorrect() {
Method multiplyStaticMethod
= Operations.class.getDeclaredMethod(
"publicStaticMultiply", float.class, long.class);
Double result
= (Double) multiplyStaticMethod.invoke(null, 3.5f, 2);
assertThat(result, equalTo(7.0));
}
Method Accessibility
기본적으로 반영된 모든 메서드에 액세스할 수 있는 것은 아니다. 이는 JVM이 호출 시 액세스 제어 검사를 시행함을 의미한다. 예를 들어 정의 클래스 외부에서 비공개 메서드를 호출하거나 하위 클래스나 클래스 패키지 외부에서 보호 메서드를 호출하려고 하면 IllegalAccessException이 발생 한다.
@Test(expected = IllegalAccessException.class)
public void givenObject_whenInvokePrivateMethod_thenFail() {
Method andPrivateMethod
= Operations.class.getDeclaredMethod(
"privateAnd", boolean.class, boolean.class);
Operations operationsInstance = new Operations();
Boolean result
= (Boolean) andPrivateMethod.invoke(operationsInstance, true, false);
assertFalse(result);
}
@Test(expected = IllegalAccessException.class)
public void givenObject_whenInvokeProtectedMethod_thenFail() {
Method maxProtectedMethod
= Operations.class.getDeclaredMethod(
"protectedMax", int.class, int.class);
Operations operationsInstance = new Operations();
Integer result
= (Integer) maxProtectedMethod.invoke(operationsInstance, 2, 4);
assertThat(result, equalTo(4));
}
AccessibleObject # setAccessible
반영된 메소드 객체에 대해 setAccesible(true)를 호출함으로써 JVM은 액세스 제어 검사를 억제 하고 예외를 발생시키지 않고 메소드를 호출할 수 있도록 한다.
@Test
public void givenObject_whenInvokePrivateMethod_thenCorrect() throws Exception {
Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
andPrivatedMethod.setAccessible(true);
Operations operationsInstance = new Operations();
Boolean result = (Boolean) andPrivatedMethod.invoke(operationsInstance, true, false);
assertFalse(result);
}
AccessibleObject#canAccess
Java 9에는 호출자가 반영된 메소드 객체에 액세스할 수 있는지 확인하는 새로운 방법이 제공된다. 이를 위해 더 이상 사용되지 않는 isAccessible 메소드를 대체하여 canAccess를 제공한다.
@Test
public void givenObject_whenInvokePrivateMethod_thenCheckAccess() throws Exception {
Operations operationsInstance = new Operations();
Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
boolean isAccessEnabled = andPrivatedMethod.canAccess(operationsInstance);
assertFalse(isAccessEnabled);
}
setAccessible(true) 를 사용하여 액세스 가능 플래그를 true 로 설정하기 전에 canAccess를 사용하여 호출자가 이미 반영된 메서드에 액세스할 수 있는지 확인할 수 있다 .
AccessibleObject#trySetAccessible
trySetAccessible은 리플렉션된 객체에 접근 가능하도록 만드는 데 사용할 수 있는 또 다른 편리한 메서드다. 이 새로운 메소드의 좋은 점은 액세스를 활성화할 수 없는 경우 false를반환한다는 것이다. 그러나 이전 메서드 setAccessible(true)는 실패할 경우 InaccessibleObjectException을 발생시킨다. trySetAccessible 메소드 의 사용을 예시해 보겠습니다 .
@Test
public void givenObject_whenInvokePublicMethod_thenEnableAccess() throws Exception {
Operations operationsInstance = new Operations();
Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
andPrivatedMethod.trySetAccessible();
boolean isAccessEnabled = andPrivatedMethod.canAccess(operationsInstance);
assertTrue(isAccessEnabled);
}
리플렉션을 통해 런타임에 클래스의 인스턴스 및 정적 메서드를 호출하는 방법을 살펴보았다. 또한 비공개 및 보호 메서드를 호출할 때 Java 액세스 제어 검사를 억제하기 위해 반영된 메서드 개체의 액세스 가능 플래그를 변경하는 방법도 보여주고 있다. 예제 코드는 Github에서 찾아볼 수 있다 .