Contents
📌 리플렉션의 핵심 개념📌 어노테이션을 함께 사용하면 좋은 이유연습 세팅1. 시나리오A회사B회사A회사B회사전체 코드리플렉션(Reflection)어노테이션(Annotation)메타 어노테이션사용자 정의 어노테이션1. 기본 어노테이션 작성 방법2. 여러 개의 속성(요소)을 가지는 어노테이션3. 기본값이 있는 어노테이션4. value() 속성 사용 시 생략 가능어노테이션을 활용한 코드 작성1. 메서드 시그니처2. 시나리오A회사B회사패키지 리플렉션ex04 패키지 안에 있는 모든 .class 파일 스캔@Component 어노테이션이 붙은 모든 클래스 set에 저장실행런타임 환경에서 스캔 & 분석, 자동으로 메서드 실행
📌 리플렉션의 핵심 개념
개념 | 설명 |
거울(Reflection) | 프로그램이 자기 자신을 들여다볼 수 있음 |
런타임(Runtime) | 실행 중에도 클래스, 메서드 정보를 확인할 수 있음 |
동적 처리(Dynamic Execution) | 실행 중에 새로운 객체를 만들거나 메서드를 호출할 수 있음 |
📌 어노테이션을 함께 사용하면 좋은 이유
리플렉션으로 모든 메서드를 검사할 수도 있지만,
어노테이션을 사용하면 특정 메서드나 필드만 쉽게 구분하고 처리할 수 있습니다.
방법 | 문제점 / 장점 |
리플렉션만 사용 | 🔴 어떤 메서드를 실행할지 직접 조건을 설정해야 함 (비효율적) |
리플렉션 + 어노테이션 | ✅ 특정 기능을 갖춘 메서드만 쉽게 식별 가능 (더 효율적) |
연습 세팅

- 외부라이브러리가 필요 없기 때문에 인텔리j로 빌드시스템 설정
1. 시나리오
A회사
- 라우팅을 해주는 프로그램을 만들어 팔고 싶다
- 사용자의 클래스를 모르니 인터페이스를 만들어 코드를 짠다
interface Controller {
void login();
void join();
void logout();
}
- 라우팅 코드
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();
}
}
}
- 판매
B회사
- A회사의 라우팅 프로그램을 구매
- 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);
}
}
- 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");
}
}
- 아 우리 회사가 새로운 기능이 필요해
public void showUser() {
System.out.println("show user call");
}
- A회사에게 전화해서 만들어 달라고 요청
A회사
- 요청 받은 기능을 추가함
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();
}
}
}
- 다 만들었다고 B회사에 전화함
B회사
- 전화 내용 확인하고 기능 사용
- 또 새로운 기능이 필요함
- 다시 A회사에 전화 요청함
- 너무 불편해짐
전체 코드
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);
}
}
}
}
}
- UserController에 있는 메서드 정보를 가져온다
- 모든 메서드들을 확인하면서 어노테이션을 확인한다
- 어노테이션에 입력된 값을 사용하여 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개만 사용할 테니 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);
}
}
}
}
}
- 컨트롤러에 있는 모든 메서드를 가져와 우리 회사 어노테이션이 있는지 확인하자
- 우리 회사 어노테이션에 들어간 값이 path와 같은지 비교!
- 같으면 이 어노테이션이 등록된 메서드를 실행하자
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");
}
}
- 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);
}
}
}
}

@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());
}
}
}

실행
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);
}
}

Share article