리플렉션(Reflection)으로 동적 메서드 호출하기

리플렉션(Reflection)은 프로그램 실행 중에 클래스의 메타데이터를 동적으로 분석하고 활용할 수 있게 해주는 강력한 도구이다.
HootJem's avatar
Aug 16, 2024
리플렉션(Reflection)으로 동적 메서드 호출하기
 

리플렉션이란?

  • 리플렉션
    • → 속을 비추다 (코드의 내부를 들여다보는 것)
보통 런타임에 리플렉션이 일어난다. (모든걸 탐색하게 함)
코드 실행전 컴파일시 분석하게 하기 위해서는 어노테이션을 활용하게 할 수 있음.
어노테이션을 활용하기 위해서는 행위를 정의해야한다. 다만, 사람이 나무를 수리한다고 했을 때 나무의 높이에 따라 수리한다 를 제외한 행위는 다르게 정의될 수 있다.
사람에게 깃발이 있는 나무만 수리하라고 명령할 것. (깃발= 어노테이션)
사람에게 깃발이 있는 나무만 수리하라고 명령할 것. (깃발= 어노테이션)
💡
어노테이션이란? JVM 이 보는 힌트. 주석같은 역할을 한다.

1. 리플렉션 예제

리플렉션을 사용하지 않는 코드
notion image
 

1.1 컨트롤러 생성

먼저, UserController라는 클래스를 만들어 로그인과 회원가입 기능을 제공하는 메서드를 정의합니다.
package ex01; public class UserController { public void login(){ System.out.println("login 호출됨"); } public void join(){ System.out.println("join 호출됨"); } }
 

1.2 App 클래스 생성

이제, App 클래스에서 사용자가 입력한 URL에 따라 해당 메서드를 호출하도록 코드를 작성해봅시다.
package ex01; public class App { public static void main(String[] args) { String path = "/login"; UserController uc = new UserController(); if(path.equals("/login")) { uc.login(); }else if (path.equals("/join")) { uc.join(); } } }
 

1.3 실행

notion image
 
String path = "/login"; 를 join 으로 바꾸면
notion image
 
🎈 이 코드는 단순히 path 변수를 통해 /login 또는 /join 경로에 맞춰 UserController의 메서드를 호출합니다. 그러나 이런 방식은 확장성이 떨어집니다. 새로운 메서드가 추가될 때마다 App 클래스도 수정해야 하기 때문입니다.

2. 리플렉션 활용

🎈 참고로 리플렉션은 순서 보장이 되지 않는다.
아래 코드는 UserController 클래스의 메서드를 리플렉션을 사용해 탐색하고, 그 이름을 출력합니다.

2.1 UserController 생성

public class UserController { public void login(){ System.out.println("login 호출됨"); } public void join(){ System.out.println("join 호출됨"); } }

2.2 App 클래스 생성

import java.lang.reflect.Method; public class App { public static void main(String[] args) { UserController uc = new UserController(); Method[] methods = uc.getClass().getDeclaredMethods(); for (Method mt : methods) { System.out.println(mt.getName()); } } }
Method[] methods = uc.getClass().getDeclaredMethods();
를 통해 UserController 안에 있는 메서드의 이름을 조회할 수 있다. (실행도 가능하다!)
  • 실행하는 코드
notion image
mt.invoke(uc); 를 사용하기 위해서는 Exception 이 필요함.
(매개변수로 넣은 uc 안에 메서드가 존재하지 않을 수 있기 때문에)
 

2.3 어노테이션 정의

어노테이션을 사용하면 메서드 이름 대신 어노테이션 값을 기준으로 동적으로 메서드를 호출할 수 있다.
먼저 RequestMapping 이라는 어노테이션을 정의해 본다.
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) //메서드 위에만 붙일 수 있다 @Retention(RetentionPolicy.RUNTIME) // 해당 깃발은 런타임시 실행됨. public @interface RequestMapping { String uri(); // String 타입의 uri 를 넣을 수 있음. }
notion image
소스 : 컴파일시 , 런타임 : 런타임시
notion image
이 엘레멘트가 붙을 수 있는 위치를 의미한다.
 

2.4 어노테이션 적용 및 최종 코드

public class UserController { @RequestMapping(uri = "/login") public void login(){ System.out.println("login 호출됨"); } @RequestMapping(uri = "/join") public void join(){ System.out.println("join 호출됨"); } }
import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class App { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException { String path = "/login"; UserController uc = new UserController(); Method[] methods = uc.getClass().getDeclaredMethods(); for (Method mt : methods) { Annotation anno = mt.getDeclaredAnnotation(RequestMapping.class); RequestMapping rm = (RequestMapping) anno; // 이렇게 통합할 수 있음 // RequestMapping rm = mt.getAnnotation(RequestMapping.class); if (rm == null) break; if(rm.uri().equals(path)){ mt.invoke(uc); } } } }
위 코드에서는 어노테이션을 사용해 각 메서드가 어떤 URL 경로와 매핑되는지를 정의한 후, 런타임에 이 매핑 정보를 바탕으로 메서드를 호출한다. path 변수만 바꾸면 어떤 메서드든 호출할 수 있게 되며, 새로운 메서드가 추가되더라도 App 클래스는 수정할 필요가 없다.

결론

이번 포스팅에서는 스프링 프로젝트에서 자주 사용되는 어노테이션이 어떻게 동작하는지 살펴보며, 리플렉션에 대해 알아보았다. 리플렉션을 통해 어노테이션의 실제 사용 방식과 그 동작 원리를 더 깊이 이해할 수 있었으며, 이를 바탕으로 기존에 활용하던 코드를 분석하고 개선할 수 있는 방법도 배울 수 있었다. 앞으로 어노테이션을 더 효과적으로 활용하기 위해 이번에 배운 내용을 프로젝트에 적용해 보면 좋을것이다.
Share article

[HootJem] 개발 기록 블로그