리플렉션
: 내가 직접 뭔가를 만들지 않고, 이미 존재하는 걸 분석하고 조작하는 기술
📌 상황
네가 마법사라고 상상해보자
마법사들은 보통 주문(Spell)을 외워야 마법을 쓸 수 있다
그런데 리플렉션을 사용할 수 있는 마법사는 주문을 외우지 않고도 마법을 사용할 수 있다
💡 일반 마법사 (리플렉션 X)
- 마법사가 "파이어볼" 마법을 사용하려면, 반드시 "파이어볼"을 직접 외워야 한다
- 하지만 "번개 마법"을 쓰려면? 새로운 주문을 배워야 한다
- 즉, 컴파일 타임(코드를 작성할 때) 모든 주문을 알고 있어야 함
💡 리플렉션을 쓰는 마법사
- 마법책(클래스 정보)을 보면 어떤 주문이 있는지 알 수 있다
- 심지어 주문을 외우지 않고 책에서 찾아서 바로 실행할 수도 있다
- 즉, 런타임(프로그램 실행 중)에 새로운 주문도 사용할 수 있다
🎯 👉 결론 :
- 직접 코드를 작성하지 않아도, 실행 중에 필요한 정보를 찾아서 실행하는 기술
리플렉션의 단점
마법사가 마법책을 보고 주문을 찾는데, 어느 날 마법책 내용이 바뀌어 버리면?
예전에는 '파이어볼'이 3페이지에 있었는데, 이제는 7페이지로 바뀜
💡일반 마법사 (리플렉션 X) → 문제 없음
- "파이어볼은 내가 외우고 있으니까 상관없어"
💡 리플렉션 마법사 → 혼란스러움
- "파이어볼이 어디 갔지? 어제는 3페이지였는데..."
- 마법이 실행되지 않거나, 엉뚱한 주문이 실행됨
✔ 실제 리플렉션에서도 마찬가지
- 프로그램 실행 중에 클래스나 메서드 이름이 바뀌면, 리플렉션이 제대로 동작하지 않음
- 코드가 변경될 때마다 리플렉션을 사용하는 부분도 수정해야 해서 유지보수가 어려움
어노테이션
: 기본적인 정보 위에 추가적인 정보를 적어두는 개념
마법책에는 수많은 주문이 있다.
하지만 그냥 주문만 적혀 있으면 마법사가 이걸 언제, 어떻게 사용해야 할지 헷갈릴 수도 있다.
그래서 마법책에는 주석(Annotation)이 적혀 있음
💡 마법책 예제
🔥 주문: 파이어볼 (Fireball)@위험@초급자 사용 금지@마나 50 이상 필요설명: 강력한 화염구를 발사한다.사용법: 적을 향해 손을 뻗고 "파이어볼!" 외치기
위처럼 주문 위에 추가적인 정보를 적어두는 게 바로 어노테이션의 개념
마법사는 이 주석을 보고 "아, 이 주문은 위험하고 초급자는 사용하면 안 되네!" 라고 알 수 있다.
✔ 👉 실제 코드에서 어노테이션은 이런 역할
- 개발자가 추가적인 정보(메타데이터)를 제공할 수 있음
- 어노테이션을 보고 프로그램이 자동으로 특정 작업을 수행할 수도 있음
마법책 예제 | 실제 코드 어노테이션 |
@위험 | @Deprecated (더 이상 사용하지 말라는 의미) |
@초급자 사용 금지 | @Override (부모 클래스를 덮어쓸 때 사용) |
@마나 50 이상 필요 | @Transactional (트랜잭션 관리) |
@자동 시전 가능 | @Autowired (자동 의존성 주입) |
어노테이션 코드 구현
1. 어노테이션 선언
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) // 메서드가 오류 발생 시 알려줌
@Target(ElementType.METHOD) // METHOD 메서드 위에만 붙힐 수 있다. (Class 위에 붙히려면 type을 적어야함)
public @interface RequsetMapping {
String value();
}@Retention(RetentionPolicy.RUNTIME)
- Retention은 어노테이션이 어떤 시점까지 존재할지를 설정합니다.
- RUNTIME은 실행 시점에 어노테이션이 유지되도록 설정합니다.
@Target(ElementType.METHOD)
- Target은 어노테이션을 어떤 요소에 적용할지 정의합니다.
2. 클래스 선언
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) {
RequsetMapping rm = method.getAnnotation(RequsetMapping.class);
if (rm == null) continue; // 다음 for문으로 바로 넘어감
if (rm.value().equals(path)) {
try {
method.invoke(con);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
} Method[] methods = con.getClass().getMethods();
- con.getClass().getMethods()는 UserController 클래스의 모든 메서드를 반복문을 통해 순차적으로 탐색할 수 있게 반환합니다.
- getMethods()는 public 메서드만 반환합니다.
3. HTTP 요청을 처리하는 메서드들을 정의한 클래스
package ex02;
public class UserController {
@RequsetMapping("/login")
public void login() {
System.out.println("login call");
}
@RequsetMapping("/join")
public void join() {
System.out.println("join call");
}
@RequsetMapping("/logout")
public void logout() {
System.out.println("logout call");
}
@RequsetMapping("/userinfo")
public void userinfo() {
System.out.println("userinfo call");
}
}@RequsetMapping
- 어노테이션을 통해 특정 경로와 연결됩니다.
- Dispatcher 클래스에서 해당 URL로 요청이 들어왔을 때 적절한 메서드를 호출하게 됩니다.
4. 메인 메서드 실행
package ex02;
public class App {
public static void main(String[] args) {
Dispatcher ds = new Dispatcher(new UserController());
ds.routing("/userinfo");
}
}
Share article