[SB] 1. 리플렉션(Reflection)

최재원's avatar
Mar 13, 2025
[SB] 1. 리플렉션(Reflection)
런타임 환경에서 스캔 & 분석, 자동으로 메서드 실행

📌 리플렉션의 핵심 개념

개념
설명
거울(Reflection)
프로그램이 자기 자신을 들여다볼 수 있음
런타임(Runtime)
실행 중에도 클래스, 메서드 정보를 확인할 수 있음
동적 처리(Dynamic Execution)
실행 중에 새로운 객체를 만들거나 메서드를 호출할 수 있음

📌 어노테이션을 함께 사용하면 좋은 이유

리플렉션으로 모든 메서드를 검사할 수도 있지만,
어노테이션을 사용하면 특정 메서드나 필드만 쉽게 구분하고 처리할 수 있습니다.
방법
문제점 / 장점
리플렉션만 사용
🔴 어떤 메서드를 실행할지 직접 조건을 설정해야 함 (비효율적)
리플렉션 + 어노테이션
✅ 특정 기능을 갖춘 메서드만 쉽게 식별 가능 (더 효율적)

연습 세팅

notion image
  • 외부라이브러리가 필요 없기 때문에 인텔리j로 빌드시스템 설정
 

1. 시나리오

A회사

  1. 라우팅을 해주는 프로그램을 만들어 팔고 싶다
  1. 사용자의 클래스를 모르니 인터페이스를 만들어 코드를 짠다
interface Controller { void login(); void join(); void logout(); }
  1. 라우팅 코드
class Dispatcher { Controller controller; public Dispatcher(Controller controller) { this.controller = controller; } public void routing(String path) { if (path.equals("/login")) { controller.login(); } else if (path.equals("/join")) { controller.join(); } else if (path.equals("/logout")) { controller.logout(); } } }
  1. 판매

B회사

  1. A회사의 라우팅 프로그램을 구매
  1. dispatcher에 우리 회사의 컨트롤러를 붙여서 사용하자
public class App { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String path = sc.nextLine(); UserController uc = new UserController(); Dispatcher ds = new Dispatcher(uc); ds.routing(path); } }
  1. A회사의 라우팅에는 어떤 방식을 지원하는지 확인하고 구현
class UserController implements Controller { @Override public void login() { System.out.println("Login call"); } @Override public void join() { System.out.println("Join call"); } @Override public void logout() { System.out.println("Logout call"); } }
  1. 아 우리 회사가 새로운 기능이 필요해
public void showUser() { System.out.println("show user call"); }
  1. A회사에게 전화해서 만들어 달라고 요청

A회사

  1. 요청 받은 기능을 추가함
interface Controller { void login(); void join(); void logout(); void showUser(); }
class Dispatcher { Controller controller; public Dispatcher(Controller controller) { this.controller = controller; } public void routing(String path) { if (path.equals("/login")) { controller.login(); } else if (path.equals("/join")) { controller.join(); } else if (path.equals("/logout")) { controller.logout(); } else if (path.equals("/showUser")) { controller.showUser(); } } }
  1. 다 만들었다고 B회사에 전화함

B회사

  1. 전화 내용 확인하고 기능 사용
  1. 또 새로운 기능이 필요함
  1. 다시 A회사에 전화 요청함
  1. 너무 불편해짐

전체 코드

package ex01; import java.util.Scanner; // A회사 입장에서 B회사의 어떤 코드가 추가될지 알 수 없다 // B회사는 필요할 때마다 전화해서 알려줘야 한다 interface Controller { void login(); void join(); void logout(); } // 1. A회사 class Dispatcher { Controller controller; public Dispatcher(Controller controller) { this.controller = controller; } public void routing(String path) { if (path.equals("/login")) { controller.login(); } else if (path.equals("/join")) { controller.join(); } else if (path.equals("/logout")) { controller.logout(); } } } // 2. B회사 (구매할께 - Controller 구현해서 만들어) class UserController implements Controller { @Override public void login() { System.out.println("Login call"); } @Override public void join() { System.out.println("Join call"); } @Override public void logout() { System.out.println("Logout call"); } } public class App { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String path = sc.nextLine(); UserController uc = new UserController(); Dispatcher ds = new Dispatcher(uc); ds.routing(path); } }

리플렉션(Reflection)

리플렉션(Reflection)은 Java 프로그램이 실행 중에 클래스, 메서드, 필드 등의 정보를 동적으로 분석하고 조작할 수 있도록 해주는 기능입니다. 즉, 컴파일 시점이 아니라 런타임에 클래스와 객체를 다룰 수 있는 기능입니다. heap메모리에 있는 클래스가 아니라 disk에 있는 정보를 확인함
 
리플렉션을 사용하면 다음과 같은 작업이 가능합니다.
  • 클래스 이름, 메서드, 필드, 생성자 등의 정보 가져오기
  • private 필드나 메서드에 접근하여 값 변경 및 호출
  • 동적으로 객체 생성 및 메서드 실행
  • 어노테이션 정보를 읽고 처리
 
package ex02; import java.lang.reflect.Method; public class App { public static void main(String[] args) { UserController uc = new UserController(); Class<?> ucClass = uc.getClass(); Method[] methods = ucClass.getMethods(); for (Method m : methods) { try { if (m.getName().equals("logout")) { // 같은지 분석 m.invoke(uc); // ()안에 표기된 heap 데이터에 m 함수를 call해줌 } } catch (Exception e) { throw new RuntimeException(e); } } } }
  • Class<?> ucClass = uc.getClass() uc 클래스의 정보를 가져옴(disk에 있는 정보)
    • ? → 와일드카드 타입, 모든 타입을 나타냄
    • getClass() → 클래스 정보 가져오기
  • Method[] methods = ucClass.getMethods() uc 클래스 정보에서 모든 public 메서드 목록을 가져옴(disk 있는 정보)
    • getMethods(), getDeclaredMethods() → 메서드 정보 가져오기
  • m.invoke(uc) heap에 있는 uc객체에 존재하는 m메서드를 실행
    • m → 메서드 객체
    • uc → m 이 실행될 객체
    • invoke() → 동적으로 메서드 실행

어노테이션(Annotation)

어노테이션은 코드에 추가적인 정보를 제공하는 메타데이터 입니다.
컴파일러, 프레임워크(Spring, JUnit 등), 도구들이 어노테이션을 읽고 특정 동작을 수행 할 수 있습니다.
주요 특징
  • @ 기호로 시작함. (@Override, @Deprecated 등)
  • 주석과 비슷하지만 코드에 영향을 미칠 수 있음.
  • 컴파일러에게 힌트를 주거나, 실행 중 리플렉션을 통해 읽을 수 있음.
  • 런타임(실행 시간)에도 유지 가능한 어노테이션이 있음.

메타 어노테이션

메타 어노테이션은 어노테이션을 정의할 때 사용되는 어노테이션입니다.
어노테이션
설명
@Target
어노테이션 적용 대상 지정 (메서드, 필드, 클래스 등)
@Retention
어노테이션 유지 기간 지정 (컴파일, 런타임 등)
@Documented
문서화할 때 포함 여부 지정 (Javadoc에서 사용)
@Inherited
자식 클래스에 자동 상속 가능하도록 설정
@interface
사용자 정의 어노테이션

사용자 정의 어노테이션

package ex02; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) // 언제 작동해야 하나 (런타임 or 컴파일) (시점에 따라 오류 알려줌) @Target(ElementType.METHOD) // 어느 타입위에 붙여야 하나 (class or method) public @interface RequestMapping { String value(); }

어노테이션을 정의하는 다양한 방법

1. 기본 어노테이션 작성 방법

public @interface MyAnnotation { String value(); }
속성(요소, Element) 정의: String value();
기본 사용 예시:
@MyAnnotation("Hello") public void myMethod() {

2. 여러 개의 속성(요소)을 가지는 어노테이션

어노테이션은 하나 이상의 속성(요소, Element)을 가질 수 있습니다.
public @interface MyAnnotation { String name(); int age(); }
✔ 사용 예시:
@MyAnnotation(name = "John", age = 30) public void myMethod() {}
속성이 여러 개 있을 경우, 모든 속성을 지정해야 합니다.

3. 기본값이 있는 어노테이션

속성에 기본값을 지정하면, 어노테이션을 사용할 때 해당 속성을 생략할 수 있음.
public @interface MyAnnotation { String name() default "Unknown"; int age() default 0; }
✔ 사용 예시:
@MyAnnotation // 기본값 사용 public void myMethod() {} @MyAnnotation(name = "Alice") // age는 기본값 0 public void anotherMethod() {}
기본값이 있는 경우, 생략 가능.

4. value() 속성 사용 시 생략 가능

어노테이션에 value()라는 속성만 있다면, value = "값"을 생략할 수 있음.
public @interface MyAnnotation { String value(); }
✔ 사용 예시 (생략 가능):
@MyAnnotation("Hello") // value 생략 가능 public void myMethod() {} @MyAnnotation(value = "Hello") // 동일한 결과 public void anotherMethod() {}
value 속성이 하나만 존재하면 value = "값"을 생략할 수 있음.
하지만 속성이 여러 개라면 생략할 수 없음.

어노테이션을 활용한 코드 작성

A회사

package ex02; import java.lang.reflect.Method; public class Dispatcher { UserController con; public Dispatcher(UserController con) { this.con = con; } public void routing(String path) { // /login Method[] methods = con.getClass().getMethods(); for (Method method : methods) { RequestMapping rm = method.getAnnotation(RequestMapping.class); if (rm == null) continue; // 다음 for문으로 바로 넘어감 if (rm.value().equals(path)) { try { method.invoke(con); } catch (Exception e) { throw new RuntimeException(e); } } } } }
  1. UserController에 있는 메서드 정보를 가져온다
  1. 모든 메서드들을 확인하면서 어노테이션을 확인한다
    1. RequestMapping rm = method.getAnnotation(RequestMapping.class);

      1. 메서드 시그니처

      <T extends Annotation> T getAnnotation(Class<T> annotationClass)
      제네릭 타입 T → 주어진 클래스 타입에 해당하는 어노테이션을 반환함.
      파라미터: annotationClass가져오려는 어노테이션 타입 (예: MyAnnotation.class)
      반환값: 지정한 어노테이션이 존재하면 해당 어노테이션 객체 반환, 없으면 null 반환
  1. 어노테이션에 입력된 값을 사용하여 path와 같은지 비교하고 같으면 해당 어노테이션이 등록된 메서드를 실행

B회사

package ex02; public class UserController { @RequestMapping("/login") public void login() { System.out.println("login call"); } @RequestMapping("/join") public void join() { System.out.println("join call"); } @RequestMapping("/logout") public void logout() { System.out.println("logout call"); } @RequestMapping("/userinfo") public void userinfo() { System.out.println("userinfo call"); } }
  • 사용하고 싶은 각각의 메서드에 어노테이션을 사용
  • 각 어노테이션에 힌트를 작성
    • 여기선 경로를 나타냄

2. 시나리오

리플렉션 & 어노테이션을 추가한다면

A회사

package ex03; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestMapping { String value(); }
  1. 우리 회사 프로그램에 사용할 어노테이션 작성
  1. 문자열 값을 1개만 사용할 테니 value()만 만들자
package ex03; import java.lang.reflect.Method; public class Dispatcher { Controller con; public Dispatcher(Controller con) { this.con = con; } public void routing(String path) { // /login Method[] methods = con.getClass().getMethods(); for (Method method : methods) { RequestMapping rm = method.getAnnotation(RequestMapping.class); if (rm == null) continue; if (rm.value().equals(path)) { try { method.invoke(con); } catch (Exception e) { throw new RuntimeException(e); } } } } }
  1. 컨트롤러에 있는 모든 메서드를 가져와 우리 회사 어노테이션이 있는지 확인하자
  1. 우리 회사 어노테이션에 들어간 값이 path와 같은지 비교!
  1. 같으면 이 어노테이션이 등록된 메서드를 실행하자

B회사

package ex03; public class UserController { @RequestMapping("/login") public void login() { System.out.println("login call"); } @RequestMapping("/join") public void join() { System.out.println("join call"); } @RequestMapping("/logout") public void logout() { System.out.println("logout call"); } @RequestMapping("/userinfo") public void userinfo() { System.out.println("userinfo call"); } }
  1. A회사에서 제공한 어노테이션을 사용해보자
 

패키지 리플렉션

ex04 패키지 안에 있는 모든 .class 파일 스캔

package ex04; import java.io.File; import java.net.URL; public class App { public static void main(String[] args) { // 1. @Component가 붙으면 new해서 컬렉션에 담기 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); URL packageUrl = classLoader.getResource("ex04"); File packageDir = new File(packageUrl.getFile()); for (File file : packageDir.listFiles()) { if (file.getName().endsWith(".class")) { String className = "ex04." + file.getName().replace(".class", ""); System.out.println(className); } } } }
notion image

@Component 어노테이션이 붙은 모든 클래스 set에 저장

package ex04; import java.io.File; import java.net.URL; import java.util.HashSet; import java.util.Set; public class App { public static void main(String[] args) { // 1. @Component가 붙으면 new해서 컬렉션에 담기 Set<Object> instances = new HashSet(); ClassLoader classLoader = ClassLoader.getSystemClassLoader(); URL packageUrl = classLoader.getResource("ex04"); File packageDir = new File(packageUrl.getFile()); for (File file : packageDir.listFiles()) { if (file.getName().endsWith(".class")) { String className = "ex04." + file.getName().replace(".class", ""); //System.out.println(className); try { Class cls = Class.forName(className); if (cls.isAnnotationPresent(Component.class)) { Object instance = cls.getDeclaredConstructor().newInstance();// 클래스 new instances.add(instance); } } catch (Exception e) { throw new RuntimeException(e); } } } // for 종료 for (Object instance : instances) { System.out.println(instance.getClass().getName()); } } }
notion image

실행

package ex04; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.util.HashSet; import java.util.Set; public class DispatcherServlet { public Set<Object> componentScan(String packageName) { // 1. @Component가 붙으면 new해서 컬렉션에 담기 Set<Object> instances = new HashSet(); ClassLoader classLoader = ClassLoader.getSystemClassLoader(); URL packageUrl = classLoader.getResource(packageName); File packageDir = new File(packageUrl.getFile()); for (File file : packageDir.listFiles()) { if (file.getName().endsWith(".class")) { String className = packageName + "." + file.getName().replace(".class", ""); try { Class cls = Class.forName(className); if (cls.isAnnotationPresent(Component.class)) { Object instance = cls.getDeclaredConstructor().newInstance(); instances.add(instance); } } catch (Exception e) { throw new RuntimeException(e); } } } // for 종료 return instances; } public void routing(Set<Object> instances, String path) { // /login for (Object instance : instances) { Method[] methods = instance.getClass().getMethods(); for (Method method : methods) { RequestMapping rm = method.getAnnotation(RequestMapping.class); if (rm == null) continue; // 다음 for문으로 바로 넘어감 if (rm.value().equals(path)) { try { method.invoke(instance); } catch (Exception e) { throw new RuntimeException(e); } } } } } }
package ex04; import java.util.Scanner; import java.util.Set; public class App { public static void main(String[] args) { // RequestMapping, Component, DispatcherServlet (돈 주고 삼 = SpringWeb라이브러리) Scanner sc = new Scanner(System.in); String path = sc.nextLine(); // /write DispatcherServlet dispatcherServlet = new DispatcherServlet(); Set<Object> instances = dispatcherServlet.componentScan("ex04"); dispatcherServlet.routing(instances, path); } }
notion image
 
Share article

jjack1