본문 바로가기
Old Posts/Java

[Java] 자바 리플렉션(Reflection) 예외들 : IllegalAccessException,IllegalArgumentException, InvocationTargetException

by A6K 2021. 7. 22.

자바 리플렉션을 이용해서 자바 클래스 정보에 접근해 이런저런 작업을 하는 경우가 있다. 예를 들어 클래스에 있는 모든 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 들의 의미를 잘 이해하고 사용하자.

댓글