[Java] 16. Reflection

김미숙's avatar
Jul 31, 2025
[Java] 16. Reflection

✅ 리플렉션(Reflection)의 정의

실행 중(Runtime)에 클래스, 메서드, 필드 등의 정보를 동적으로 조회하거나 수정/호출할 수 있게 해주는 기능

🔍 핵심 키워드로 설명하면

요소
설명
동적(dynamic)
컴파일 타임이 아닌 실행 시간에 처리
조회(inspect)
클래스의 구조를 조사할 수 있음 (예: 필드 목록, 메서드 목록 등)
제어(manipulate)
필드 값을 수정하거나, 메서드를 호출하거나, 객체를 생성할 수 있음

🧠 대표적인 클래스

리플렉션은 java.lang.reflect 패키지와 Class 클래스를 중심으로 동작
  • Class<?> cls = SomeClass.class;
  • cls.getDeclaredFields()
  • cls.getDeclaredMethods()
  • method.invoke(...)
  • field.setAccessible(true); field.set(obj, value);

✅ 예시

Class<?> clazz = Class.forName("com.example.User"); Object user = clazz.getDeclaredConstructor().newInstance(); Method method = clazz.getMethod("setName", String.class); method.invoke(user, "홍길동");
→ 클래스 이름만 알고 있어도, 해당 클래스의 인스턴스를 만들고 메서드를 호출할 수 있다

⚠️ 리플렉션의 단점

  • 성능 저하: 일반 호출보다 느리다
  • 컴파일 타임 안정성↓: 문법 오류를 컴파일 시점에 못 잡는다
  • 보안 제약: JVM 보안 매니저에 따라 접근이 제한될 수 있다

📝 한줄 정리

자바 리플렉션은 실행 중에 클래스 정보를 읽고 조작할 수 있게 해주는 기능으로, 프레임워크나 라이브러리 내부에서 매우 자주 사용됨

 

실습

메서드명 & 메서드의 변수와 타입 확인

UserController
package com.metacoding.ex02; // B 개발자가 만든 것 public class UserController { public void login(int n1, String s1) { System.out.println("login 호출 됨"); } public void join() { System.out.println("join 호출 됨"); } public void logout() { System.out.println("logout 호출 됨"); } }
App
package com.metacoding.ex02; import java.lang.reflect.Method; import java.lang.reflect.Parameter; // A 개발자가 만든 것 public class App { public static void main(String[] args) { String path = "/login"; Method[] methods = UserController.class.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName()); if (method.getName().equals("login")) { int paramCount = method.getParameterCount(); System.out.println(paramCount); Parameter[] params = method.getParameters(); for (Parameter param : params) { System.out.println(param.getName()); System.out.println(param.getType()); } } } } }
⬇ 실행 결과
notion image
 

예제

RequestMapping
  • 직접 어노테이션 생성
package com.metacoding.ex02; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 언제까지 유지(보존)될지를 지정하는 메타 어노테이션, RetentionPolicy -> 언제까지 유지(보존)될지를 지정하는 데 사용하는 열거형(enum) @Retention(RetentionPolicy.RUNTIME) // 메서드 위에만 작성 가능한 어노테이션, ElementType -> 어노테이션의 적용 대상을 지정할 때 사용하는 열거형(enum) @Target(ElementType.METHOD) public @interface RequestMapping { String value(); // value: 기본 키워드-> 만약 매개변수가 하나면 value를 생략할 수 있다 }
UserController
package com.metacoding.ex02; // B 개발자가 만든 것 public class UserController { @RequestMapping("/login") public void login() { System.out.println("login 호출 됨"); } @RequestMapping("/join") public void join() { System.out.println("join 호출 됨"); } @RequestMapping("/logout") public void logout() { System.out.println("logout 호출 됨"); } }
App
package com.metacoding.ex02; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Scanner; // A 개발자가 만든 것 public class App { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String path = sc.nextLine(); Method[] methods = UserController.class.getDeclaredMethods(); UserController uc = new UserController(); for (Method method : methods) { Annotation anno = method.getDeclaredAnnotation(RequestMapping.class); RequestMapping rm = (RequestMapping) anno; if (rm.value().equals(path)) { try { method.invoke(uc); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } } } }
→ UserController에 메서드가 추가되더라도, 추가된 메서드에 @RequestMapping 어노테이션만 붙어 있으면 메서드 별로 코드를 작성하지 않아도 호출할 수 있다
-> 깃발(어노테이션) 사용법만 알면 된다!
 

문제

  1. "/login" 호출할때 Model 객체 있으면 instance 넣어서 username값 출력
  1. "userinfo" 호출할 때 @Principal, SessionUser 객체 있으면 instance 넣어서 id와 username 출력
SessionUser
package com.metacoding.ex03; public class SessionUser { private int id; private String username; private static SessionUser instance = new SessionUser(); private SessionUser() { this.id = 1; this.username = "ssar"; } public static SessionUser getInstance() { return instance; } public int getId() { return id; } public String getUsername() { return username; } }
Model
package com.metacoding.ex03; import java.util.HashMap; import java.util.Map; public class Model { private static Model instance = new Model(); private Model() { attributes.put("username", "ssar"); } public static Model getInstance() { return instance; } private Map<String, Object> attributes = new HashMap<>(); public void addAttribute(String name, Object value) { attributes.put(name, value); } public Object getAttribute(String name) { return attributes.get(name); } }
Principal
package com.metacoding.ex03; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 언제까지 유지(보존)될지를 지정하는 메타 어노테이션, RetentionPolicy -> 언제까지 유지(보존)될지를 지정하는 데 사용하는 열거형(enum) @Retention(RetentionPolicy.RUNTIME) // 메서드 위에만 작성 가능한 어노테이션, ElementType -> 어노테이션의 적용 대상을 지정할 때 사용하는 열거형(enum) @Target(ElementType.PARAMETER) public @interface Principal { }
RequestMapping
package com.metacoding.ex03; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 언제까지 유지(보존)될지를 지정하는 메타 어노테이션, RetentionPolicy -> 언제까지 유지(보존)될지를 지정하는 데 사용하는 열거형(enum) @Retention(RetentionPolicy.RUNTIME) // 메서드 위에만 작성 가능한 어노테이션, ElementType -> 어노테이션의 적용 대상을 지정할 때 사용하는 열거형(enum) @Target(ElementType.METHOD) public @interface RequestMapping { String value(); // value: 기본 키워드-> 만약 매개변수가 하나면 value를 생략할 수 있다 }
UserController
package com.metacoding.ex03; // B 개발자가 만든 것 public class UserController { @RequestMapping("/userinfo") public void userinfo(@Principal SessionUser sessionUser) { System.out.println(sessionUser.getId()); System.out.println(sessionUser.getUsername()); System.out.println("userinfo 호출됨"); } @RequestMapping("/login") public void login(Model model) { System.out.println(model.getAttribute("username")); System.out.println("login 호출 됨"); } @RequestMapping("/join") public void join() { System.out.println("join 호출 됨"); } @RequestMapping("/logout") public void logout() { System.out.println("logout 호출 됨"); } }
App
package com.metacoding.ex03; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Scanner; // A 개발자가 만든 것 public class App { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String path = sc.nextLine(); // 콘솔에서 URL 입력 받기 Method[] methods = UserController.class.getDeclaredMethods(); // UserController의 모든 메서드 조회 UserController uc = new UserController(); // 컨트롤러 객체 생성 // [1] 모든 메서드 순회 for (Method method : methods) { Annotation anno = method.getDeclaredAnnotation(RequestMapping.class); // @RequestMapping 어노테이션 추출 RequestMapping rm = (RequestMapping) anno; // 다운캐스팅 // [2] 현재 메서드의 @RequestMapping 값이 요청 경로와 일치하는지 확인 if (rm.value().equals(path)) { try { // [3] 해당 메서드의 파라미터 어노테이션과 타입 정보 수집 Annotation[][] paramAnnotations = method.getParameterAnnotations(); // 파라미터의 어노테이션들 Class<?>[] paramTypes = method.getParameterTypes(); // 파라미터 타입들 Object[] argsForInvoke = new Object[paramTypes.length]; // 메서드 실행에 넘길 실제 인자들 // [4] 파라미터 수 만큼 반복 for (int i = 0; i < paramTypes.length; i++) { boolean isPrincipal = false; // [5] 현재 파라미터에 @Principal 어노테이션이 있는지 확인 for (Annotation annotation : paramAnnotations[i]) { if (annotation.annotationType().equals(Principal.class)) { isPrincipal = true; break; } } // [6] 주입할 인자 결정 if (isPrincipal || paramTypes[i].equals(SessionUser.class)) { argsForInvoke[i] = SessionUser.getInstance(); // @Principal 또는 SessionUser 타입이면 세션 유저 주입 } else if (paramTypes[i].equals(Model.class)) { argsForInvoke[i] = Model.getInstance(); // Model 타입이면 싱글톤 모델 } else { argsForInvoke[i] = null; // 처리하지 않는 타입은 null 주입 } } // [7] 준비된 인자를 이용해 컨트롤러 메서드 실행 method.invoke(uc, argsForInvoke); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); // 예외 발생 시 래핑해서 던짐 } } } } }
⬇ 실행 결과
“userinfo”
notion image
“/login”
notion image
“/join”
notion image
“/logout”
notion image
 

ElementType

✅ 정의

  • java.lang.annotation.ElementType은 어노테이션을 어떤 대상에 붙일 수 있는지를 지정하는 열거형(enum)
  • 예를 들어, 어떤 어노테이션이 클래스에만 붙을 수 있는지, 메서드에만 붙을 수 있는지 등을 결정할 때 사용

🧠 주요 용도

@Target({...}) 어노테이션의 인자로 사용됨
@Target(ElementType.METHOD) // 메서드에만 붙일 수 있음 public @interface MyAnnotation { }

📚 주요 상수 목록

상수
적용 대상 설명
TYPE
클래스, 인터페이스, enum, 애노테이션
FIELD
멤버 변수
METHOD
메서드
PARAMETER
메서드의 매개변수
CONSTRUCTOR
생성자
LOCAL_VARIABLE
지역 변수
ANNOTATION_TYPE
다른 애노테이션에 붙일 수 있음
PACKAGE
패키지
TYPE_PARAMETER
제네릭 타입 파라미터 (Java 8+)
TYPE_USE
타입이 쓰이는 모든 위치 (Java 8+)

✅ 예시

import java.lang.annotation.*; @Target({ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { }
→ 이 어노테이션은 클래스(TYPE)와 필드(FIELD)에만 사용할 수 있다

✍️ 한줄 요약

ElementType은 어노테이션이 적용될 수 있는 대상을 제한하기 위한 열거형(enum)이며, @Target에서 사용됨
 
Share article

parangdajavous