본문 바로가기

JAVA

[JAVA] Reflection 을 이용하여 Method 호출하기

2개의 DB Connection Pool 을 사용하는 서비스를 개발하던 중 분리된 DB 의 Mapper 를 구분해서 호출해야 하는 이슈가 있었습니다.

 

문제는 2개의 Mapper 형태가 거의 유사하다는 점이었습니다.

매 서비스가 호출될 때 마다 if 문으로 서비스문을 분기하여 Mapper 를 호출해야될 정도 였습니다.

하지만, 그렇게 되면 중복코드의 양이 너무 많아지기 때문에 차선책이 필요했습니다.

그래서 생각한 것이 java 에서 제공하는 API 인 Reflection 이었습니다.

Reflection 을 검색하면 다음과 같은 정의가 나옵니다.

리플렉션은 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API

 

이 말은 처음 본 사람에겐 의아할 수 있습니다. 실제로 사용해보기전까진 저도 이해하기 어려웠습니다.

하지만, 이 정의를 이해하기 위해서는 개발자 가 아닌 컴퓨터 입장에서 생각하셔야 됩니다.

 

개발하고자 하는 사람은 달리 말해서 전지적 작가 시점 입니다. 설계와 구상을 통해 어떤 것을 어떻게, 무엇을 개발해야 할지 알고 있습니다.

하지만 컴퓨터, 즉 Code 입장에서는 구체적인 클래스 타입을 알지 못하기 때문에 어떤 것을 실행해야 할지 모르는 것입니다. 하지만, 개발자 는 구체적인 클래스 타입을 알지 못해도 해당 클래스에 선언되어있는 Method 나 Class 의 이름이나 Parameter 의 타입을 알고 있기 때문에 그러한 정보를 토대로 구체적인 클래스 타입을 유도해서 호출하는 것입니다.

 

실제로 자바는 정적인 언어라 구체적인 클래스 타입을 알지 못하면 호출하여 실행하기 어려운 부분이 많은데, 이러한 동적인 문제를 해결해주기 위한 것이 Reflection 입니다.

주로, 프레임워크나 IDE 나 Library 에서 정의되어있는 메서드나 클래스의 종료를 가져오거나, 호출하기 위해서 사용됩니다.

대표적인 사용 예로는 스프링의 DI (dependency Injection) 이나 Proxy, IDE 에서 정의된 메서드를 가져오거나 할 때 사용됩니다.

간단한 예시로 설명드리겠습니다.

 

메서드 이름으로 Class 에 존재하는 메서드를 호출하는 예시입니다.

 

1. Object instance = FirstService firstService;
  2. Class<?> firstClass = instance.getClass();
  3. Method method = firstClass.getDeclaredMethod(methodName, firstDto.getClass());
  4. Object result = method.invoke(instance, parameter);
  
  1. Object instance = FirstService firstService;
    • 실제 호출하고자 하는 instance 를 가져옵니다.
  2. Class<?> firstClass = instance.getClass();
    • instance 의 Class 를 getClass() 메서드를 통해 추출합니다.
  3. Method method = firstClass.getDeclaredMethod(methodName, firstDto.getClass());
    • getDeclaredMethod 를 이용하여 firstClass 의 선언되어있는 methodName 과 firstDto Class 의 parameter 를 가진 method 를 가져옵니다.

    reflection 을 이용하여 method 를 가져오는 방법은 2가지가 있습니다. getMethod() 와 getDeclaredMethod() 가 있습니다.

    이 2가지의 차이점은

    getMethod() 는 public method 뿐만아니라 base class 의 상속되어있는 superclasses 와 superinterface 의 메서드 전부를 가져오고,

    getDeclaredMethod() 는 해당 클래스에만 선언된 method 를 가져옵니다.

    따라서, 필요에 따라 구분해서 사용해야 합니다.

  4. Object result = method.invoke(instance, parameter);
    • 추출한 method 의 invoke() 를 사용하여 실행시킵니다. Object 를 return 하기 때문에 return 타입에 맞춰 형변환하여 사용하면 됩니다.

 

 


간단한 예제를 통해 Reflection 을 구현해봤습니다.

 

실제로 구현을 할 때에는 Interface 를 주입한 후 호출하고자하는 Service Name 에 따라 Interface 를 구현해 놓은 Service 를 Reflection 을 통해 호출하도록 사용하였습니다.

 

Reflection 은 이런 구체적인 클래스 타입을 알지 못해도 동적으로 Class 및 Method 를 실행시킬 수 있 다는 큰 장점이 존재하지만 다음과 같은 단점도 존재합니다.

  1. 지나친 사용은 성능이슈가 발생할 수 있습니다.
    • 따라서, 성능에 대한 손이익을 확실히 따져 사용해야 합니다.
  2. 컴파일 타임에 확인되지 않고 런타임 시에만 발생하는 문제를 만들 가능성이 있습니다.
    • Reflection 을 사용하면서 메서드를 찾지 못하는 문제나 Class 를 찾지 못하는 문제를 런타임 시에만 확인할 수 있기 때문에 예외처리에 대한 작업을 충분히 해줘야 할 필요가 있습니다.
    • getMethod() 나 getClass() 를 사용하면 superclass, superinterface 의 메서드를 호출하려고 시도하기 때문에 컴파일시에서 알 수 없었던 접근제어자 문제가 발생할 수 있습니다. 따라서, 접근할 수 없는 접근제어자를 호출하는경우 SecurityException 을 발생시킬 수 있기 떄문에 checkPackageAccess() 메서드를 통해 검사를 우선적으로 해줘야합니다.

 

 

Reflection 은 필수적인 API 이지만 분명한 단점이 존재하기 때문에 충분한 고민을 통해 필요한 경우 사용해야하며, 사용한다면 많은 테스트를 통해 예외처리를 충분히 해야할 필요가 있습니다.

따라서 충분한 예외에 대한 테스트코드를 작성하여 구현한다면 유용한 코드를 구현할 수 있습니다.

 

 

 

Reference

공식문서 : https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getMethods--

Reflection 사용에 대한 자세한 예시 :

https://www.baeldung.com/java-reflection

https://www.baeldung.com/reflections-library

참고사이트 : https://velog.io/@dpudpu/7

'JAVA' 카테고리의 다른 글

[Java] POJO 와 Beans 의 차이점  (0) 2019.08.24
JAVA의 정석 - Thread  (0) 2019.01.13
JAVA의 정석 - Generics  (0) 2019.01.13
JAVA의 정석 - 열거형(enums)  (0) 2019.01.13