자바 리플렉션을 이용해서 자바 클래스 정보에 접근해 이런저런 작업을 하는 경우가 있다. 예를 들어 클래스에 있는 모든 getter 메소드를 호출해서 멤버 값들을 가져오는 코드를 다음과 같이 작성해볼 수 있다.
import java.lang.reflect.Method;
public class Test {
private String name;
private String address;
public Test(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
private static boolean isGetter(Method method) {
if (!method.getName().startsWith("get")) return false;
if (method.getParameterTypes().length != 0) return false;
return !(void.class.equals(method.getReturnType()));
}
public static void main(String []args) throws Exception {
Test test = new Test("Dave", "Seoul");
Method[] methods = Test.class.getMethods();
for (Method method : methods) {
if (isGetter(method)) {
Object result = method.invoke(test);
System.out.println(method.getName() + ":" + result);
}
}
}
}
이 코드를 실행하면 다음 결과를 얻게 된다.
getAddress:Seoul
getName:Dave
getClass:class Test
자바 리플렉션으로 메소드를 호출하는 코드는 'method.invoke(test)' 부분이다. 이 코드는 'test.method()' 코드와 동일한 동작을 수행한다. 중요한건 invoke() 메소드가 IllegalAccessException, IllegalArgumentException, InvocationTargetException 이렇게 3가지 Exception을 던진다는 것이다. 이번 포스트에서는 이 3가지 예외가 어떤 경우에 발생하는지 정리를 해보겠다.
IllegalAccessException
IllegalAccessException은리플렉션으로 접근하려는 클래스에 접근 제어가 걸려있어 접근할 수 없을 경우걸려있을 때 발생한다.
다음 코드를 보자.
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
Set set = new HashSet();
set.add("A");
set.add("B");
set.add("C");
Method method = set.iterator().getClass().getMethod("hasNext");
Object obj = method.invoke(set.iterator());
System.out.println(obj);
}
}
얼핏보면 문제가 없어보인다. Iterator의 클래스를 얻어 온 다음 hasNext() 메소드를 가져와서 실행하는 코드다.
이 코드를 실행하면,
Exception in thread "main" java.lang.IllegalAccessException: Class Test can not access a member of class java.util.HashMap$HashIterator with modifiers "public final"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:110)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:262)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:254)
at java.lang.reflect.Method.invoke(Method.java:599)
at Test.main(Test.java:15)
이런 Exception을 얻게 된다.
위 코드를 다음과 같이 고치면 정상적으로 수행된다.
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
Set set = new HashSet();
set.add("A");
set.add("B");
set.add("C");
Method method = Iterator.class.getMethod("hasNext");
Object obj = method.invoke(set.iterator());
System.out.println(obj);
}
}
두 코드의 차이점을 살펴보면, set.iterator.getClass()의 결과는 "class java.util.HashMap$KeyIterator"이고, Iterator.class의 결과는 "interface java.util.Iterator"다. 전자의 경우 KeyIterator 클래스의 접근 제어자가 'private'이기 때문에 invoke() 메소드가 수행되는 코드 부분에서 접근할 수 없고, IllegalAcceessException이 발생한 것이다. invoke() 호출시 이런 예외를 잘 처리해줘야한다.
InvocationTargetException
InvocationTargetException은 리플렉션으로 호출한 메소드에서 발생한 예외를 Wrapping 한 예외 클래스다.
다음 코드를 실행시켜 보면,
import java.lang.reflect.Method;
public class Test {
private String name;
private String address;
public Test(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
throw new RuntimeException();
}
public String getAddress() {
return address;
}
public static void main(String []args) throws Exception {
Test test = new Test("Dave", "Seoul");
Method method = Test.class.getMethod("getName");
method.invoke(test);
}
}
리플렉션을 이용해서 getName() 메소드를 호출했다. 이 과정에서 RunTimeException이 발생했고, invoke() 메소드에서 이 예외를 래핑(Wrapping)해서 InvocationTargetException으로 던져준다.
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at Test.main(Test.java:30)
Caused by: java.lang.RuntimeException
at Test.getName(Test.java:17)
... 5 more
리플렉션으로 호출한 메소드에서 발생한 실제 예외가 무엇인지는 던져진 InvocationTargetException을 통해 알 수 있다. InvocationTargetException의 '.getTargetException()' 메소드를 호출하면 리플렉션 수행중 발생한 예외 객체를 얻어올 수 있다.
자바 리플렉션에서 발생한 예외를 InvocationTargetException으로 래핑한 이유는 리플렉션 호출 자체의 실패와 호출된 메소드의 Exception을 구분하기 위해서라고 보면 된다.
IllegalArgumentException
IllegalArgumentException은 invoke() 로 실행할 메소드에 필요한 인자(Argument)가 제대로 설정되지 않았을 경우 발생한다.
다음 코드를 살펴보자
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
private String name;
private String address;
public Test(String name, String address) {
this.name = name;
this.address = address;
}
public String getName(String arg) {
return this.name + arg;
}
public String getAddress() {
return address;
}
public static void main(String []args) throws Exception {
Test test = new Test("Dave", "Seoul");
Method method = Test.class.getMethod("getName", String.class);
method.invoke(test);
}
}
getName() 이라는 메소드를 리플렉션으로 가져왔다. 이 때, getName() 메소드는 String 타입의 Argument 하나를 받는다.
이후 invoke() 메소드를 이용해서 메소드를 실행시키는데, Argument를 주지 않고 실행했다. 이 경우 필요한 Argument를 명시하지 않았으므로 IllegalArgumentException이 발생하게 된다. (필요한 인자가 전달되지 않았으므로 실행할 수 없다.)
리플렉션을 사용할 때 발생할 수 있는 3가지 Exception을 알아봤다. 각 Exception 들의 의미를 잘 이해하고 사용하자.
댓글